mirror of
https://github.com/openai/codex.git
synced 2026-06-01 19:02:59 +00:00
Use selected turn environments for runtime context (#20281)
## Summary - make selected turn environments the source of truth for session runtime cwd and MCP runtime environment selection - keep local/no-selection fallback behavior intact - add coverage for duplicate selected environments, cwd resolution, and MCP runtime environment selection ## Validation - git diff --check - rustfmt was run on touched Rust files during the implementation workflow CI should provide the full Bazel/test signal. --------- Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
@@ -30,6 +30,7 @@ use tokio::time::timeout;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::environment_selection::ResolvedTurnEnvironments;
|
||||
use crate::guardian::GuardianApprovalRequest;
|
||||
use crate::guardian::new_guardian_review_id;
|
||||
use crate::guardian::routes_approval_to_guardian;
|
||||
@@ -47,7 +48,6 @@ use crate::session::SUBMISSION_CHANNEL_CAPACITY;
|
||||
use crate::session::emit_subagent_session_started;
|
||||
use crate::session::session::Session;
|
||||
use crate::session::turn_context::TurnContext;
|
||||
use crate::session::turn_context::TurnEnvironment;
|
||||
use codex_login::AuthManager;
|
||||
use codex_models_manager::manager::SharedModelsManager;
|
||||
use codex_protocol::error::CodexErr;
|
||||
@@ -94,11 +94,9 @@ pub(crate) async fn run_codex_thread_interactive(
|
||||
inherited_exec_policy: Some(Arc::clone(&parent_session.services.exec_policy)),
|
||||
parent_rollout_thread_trace: codex_rollout_trace::ThreadTraceContext::disabled(),
|
||||
parent_trace: None,
|
||||
environments: parent_ctx
|
||||
.environments
|
||||
.iter()
|
||||
.map(TurnEnvironment::selection)
|
||||
.collect(),
|
||||
environment_selections: ResolvedTurnEnvironments {
|
||||
turn_environments: parent_ctx.environments.clone(),
|
||||
},
|
||||
analytics_events_client: Some(parent_session.services.analytics_events_client.clone()),
|
||||
thread_store: Arc::clone(&parent_session.services.thread_store),
|
||||
}))
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
|
||||
use codex_exec_server::Environment;
|
||||
use codex_exec_server::EnvironmentManager;
|
||||
use codex_exec_server::ExecutorFileSystem;
|
||||
use codex_protocol::error::CodexErr;
|
||||
use codex_protocol::error::Result as CodexResult;
|
||||
use codex_protocol::protocol::TurnEnvironmentSelection;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
|
||||
use crate::session::turn_context::TurnEnvironment;
|
||||
|
||||
pub(crate) fn default_thread_environment_selections(
|
||||
environment_manager: &EnvironmentManager,
|
||||
cwd: &AbsolutePathBuf,
|
||||
@@ -21,42 +24,61 @@ pub(crate) fn default_thread_environment_selections(
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn validate_environment_selections(
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct ResolvedTurnEnvironments {
|
||||
pub(crate) turn_environments: Vec<TurnEnvironment>,
|
||||
}
|
||||
|
||||
impl ResolvedTurnEnvironments {
|
||||
pub(crate) fn to_selections(&self) -> Vec<TurnEnvironmentSelection> {
|
||||
self.turn_environments
|
||||
.iter()
|
||||
.map(TurnEnvironment::selection)
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn primary_turn_environment(&self) -> Option<&TurnEnvironment> {
|
||||
self.turn_environments.first()
|
||||
}
|
||||
|
||||
pub(crate) fn primary_environment(&self) -> Option<Arc<codex_exec_server::Environment>> {
|
||||
self.primary_turn_environment()
|
||||
.map(|environment| Arc::clone(&environment.environment))
|
||||
}
|
||||
|
||||
pub(crate) fn primary_filesystem(&self) -> Option<Arc<dyn ExecutorFileSystem>> {
|
||||
self.primary_turn_environment()
|
||||
.map(|environment| environment.environment.get_filesystem())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn resolve_environment_selections(
|
||||
environment_manager: &EnvironmentManager,
|
||||
environments: &[TurnEnvironmentSelection],
|
||||
) -> CodexResult<()> {
|
||||
) -> CodexResult<ResolvedTurnEnvironments> {
|
||||
let mut seen_environment_ids = HashSet::with_capacity(environments.len());
|
||||
let mut turn_environments = Vec::with_capacity(environments.len());
|
||||
for selected_environment in environments {
|
||||
if environment_manager
|
||||
.get_environment(&selected_environment.environment_id)
|
||||
.is_none()
|
||||
{
|
||||
if !seen_environment_ids.insert(selected_environment.environment_id.as_str()) {
|
||||
return Err(CodexErr::InvalidRequest(format!(
|
||||
"unknown turn environment id `{}`",
|
||||
"duplicate turn environment id `{}`",
|
||||
selected_environment.environment_id
|
||||
)));
|
||||
}
|
||||
let environment_id = selected_environment.environment_id.clone();
|
||||
let environment = environment_manager
|
||||
.get_environment(&environment_id)
|
||||
.ok_or_else(|| {
|
||||
CodexErr::InvalidRequest(format!("unknown turn environment id `{environment_id}`"))
|
||||
})?;
|
||||
turn_environments.push(TurnEnvironment {
|
||||
environment_id,
|
||||
environment,
|
||||
cwd: selected_environment.cwd.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn selected_primary_environment(
|
||||
environment_manager: &EnvironmentManager,
|
||||
environments: &[TurnEnvironmentSelection],
|
||||
) -> CodexResult<Option<Arc<Environment>>> {
|
||||
environments
|
||||
.first()
|
||||
.map(|selected_environment| {
|
||||
environment_manager
|
||||
.get_environment(&selected_environment.environment_id)
|
||||
.ok_or_else(|| {
|
||||
CodexErr::InvalidRequest(format!(
|
||||
"unknown turn environment id `{}`",
|
||||
selected_environment.environment_id
|
||||
))
|
||||
})
|
||||
})
|
||||
.transpose()
|
||||
Ok(ResolvedTurnEnvironments { turn_environments })
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -105,4 +127,51 @@ mod tests {
|
||||
Vec::<TurnEnvironmentSelection>::new()
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn resolve_environment_selections_rejects_duplicate_ids() {
|
||||
let cwd = AbsolutePathBuf::current_dir().expect("cwd");
|
||||
let manager = EnvironmentManager::default_for_tests();
|
||||
|
||||
let err = resolve_environment_selections(
|
||||
&manager,
|
||||
&[
|
||||
TurnEnvironmentSelection {
|
||||
environment_id: "local".to_string(),
|
||||
cwd: cwd.clone(),
|
||||
},
|
||||
TurnEnvironmentSelection {
|
||||
environment_id: "local".to_string(),
|
||||
cwd: cwd.join("other"),
|
||||
},
|
||||
],
|
||||
)
|
||||
.expect_err("duplicate environment id should fail");
|
||||
|
||||
assert!(err.to_string().contains("duplicate"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn resolved_environment_selections_use_first_selection_as_primary() {
|
||||
let cwd = AbsolutePathBuf::current_dir().expect("cwd");
|
||||
let selected_cwd = cwd.join("selected");
|
||||
let manager = EnvironmentManager::default_for_tests();
|
||||
|
||||
let resolved = resolve_environment_selections(
|
||||
&manager,
|
||||
&[TurnEnvironmentSelection {
|
||||
environment_id: "local".to_string(),
|
||||
cwd: selected_cwd,
|
||||
}],
|
||||
)
|
||||
.expect("environment selections should resolve");
|
||||
|
||||
assert_eq!(
|
||||
resolved
|
||||
.primary_turn_environment()
|
||||
.expect("primary environment")
|
||||
.environment_id,
|
||||
"local"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,6 +221,19 @@ impl Session {
|
||||
let mcp_servers = with_codex_apps_mcp(mcp_servers, auth.as_ref(), &mcp_config);
|
||||
let auth_statuses =
|
||||
compute_auth_statuses(mcp_servers.iter(), store_mode, auth.as_ref()).await;
|
||||
let mcp_runtime_environment = match turn_context.primary_environment() {
|
||||
Some(turn_environment) => McpRuntimeEnvironment::new(
|
||||
Arc::clone(&turn_environment.environment),
|
||||
turn_environment.cwd.to_path_buf(),
|
||||
),
|
||||
None => McpRuntimeEnvironment::new(
|
||||
self.services
|
||||
.environment_manager
|
||||
.default_environment()
|
||||
.unwrap_or_else(|| self.services.environment_manager.local_environment()),
|
||||
turn_context.cwd.to_path_buf(),
|
||||
),
|
||||
};
|
||||
{
|
||||
let mut guard = self.services.mcp_startup_cancellation_token.lock().await;
|
||||
guard.cancel();
|
||||
@@ -234,13 +247,7 @@ impl Session {
|
||||
turn_context.sub_id.clone(),
|
||||
self.get_tx_event(),
|
||||
turn_context.permission_profile(),
|
||||
McpRuntimeEnvironment::new(
|
||||
turn_context
|
||||
.environment
|
||||
.clone()
|
||||
.unwrap_or_else(|| self.services.environment_manager.local_environment()),
|
||||
turn_context.cwd.to_path_buf(),
|
||||
),
|
||||
mcp_runtime_environment,
|
||||
config.codex_home.to_path_buf(),
|
||||
codex_apps_tools_cache_key(auth.as_ref()),
|
||||
tool_plugin_provenance,
|
||||
|
||||
@@ -30,8 +30,7 @@ use crate::context::NetworkRuleSaved;
|
||||
use crate::context::PermissionsInstructions;
|
||||
use crate::context::PersonalitySpecInstructions;
|
||||
use crate::default_skill_metadata_budget;
|
||||
use crate::environment_selection::selected_primary_environment;
|
||||
use crate::environment_selection::validate_environment_selections;
|
||||
use crate::environment_selection::ResolvedTurnEnvironments;
|
||||
use crate::exec_policy::ExecPolicyManager;
|
||||
use crate::installation_id::resolve_installation_id;
|
||||
use crate::parse_turn_item;
|
||||
@@ -113,7 +112,6 @@ use codex_protocol::protocol::SubAgentSource;
|
||||
use codex_protocol::protocol::TurnAbortReason;
|
||||
use codex_protocol::protocol::TurnContextItem;
|
||||
use codex_protocol::protocol::TurnContextNetworkItem;
|
||||
use codex_protocol::protocol::TurnEnvironmentSelection;
|
||||
use codex_protocol::protocol::W3cTraceContext;
|
||||
use codex_protocol::request_permissions::PermissionGrantScope;
|
||||
use codex_protocol::request_permissions::RequestPermissionProfile;
|
||||
@@ -410,7 +408,7 @@ pub(crate) struct CodexSpawnArgs {
|
||||
pub(crate) parent_rollout_thread_trace: ThreadTraceContext,
|
||||
pub(crate) user_shell_override: Option<shell::Shell>,
|
||||
pub(crate) parent_trace: Option<W3cTraceContext>,
|
||||
pub(crate) environments: Vec<TurnEnvironmentSelection>,
|
||||
pub(crate) environment_selections: ResolvedTurnEnvironments,
|
||||
pub(crate) analytics_events_client: Option<AnalyticsEventsClient>,
|
||||
pub(crate) thread_store: Arc<dyn ThreadStore>,
|
||||
}
|
||||
@@ -467,18 +465,13 @@ impl Codex {
|
||||
inherited_exec_policy,
|
||||
parent_rollout_thread_trace,
|
||||
parent_trace: _,
|
||||
environments,
|
||||
environment_selections,
|
||||
analytics_events_client,
|
||||
thread_store,
|
||||
} = args;
|
||||
let (tx_sub, rx_sub) = async_channel::bounded(SUBMISSION_CHANNEL_CAPACITY);
|
||||
let (tx_event, rx_event) = async_channel::unbounded();
|
||||
validate_environment_selections(environment_manager.as_ref(), &environments)?;
|
||||
let environment =
|
||||
selected_primary_environment(environment_manager.as_ref(), &environments)?;
|
||||
let fs = environment
|
||||
.as_ref()
|
||||
.map(|environment| environment.get_filesystem());
|
||||
let fs = environment_selections.primary_filesystem();
|
||||
let plugins_input = config.plugins_config_input();
|
||||
let plugin_outcome = plugins_manager.plugins_for_config(&plugins_input).await;
|
||||
let effective_skill_roots = plugin_outcome.effective_skill_roots();
|
||||
@@ -501,8 +494,9 @@ impl Codex {
|
||||
let _ = config.features.disable(Feature::Collab);
|
||||
}
|
||||
|
||||
let primary_environment = environment_selections.primary_environment();
|
||||
let user_instructions = AgentsMdManager::new(&config)
|
||||
.user_instructions(environment.as_deref())
|
||||
.user_instructions(primary_environment.as_deref())
|
||||
.await;
|
||||
|
||||
let exec_policy = if crate::guardian::is_guardian_reviewer_source(&session_source) {
|
||||
@@ -614,7 +608,7 @@ impl Codex {
|
||||
cwd: config.cwd.clone(),
|
||||
codex_home: config.codex_home.clone(),
|
||||
thread_name: None,
|
||||
environments,
|
||||
environments: environment_selections.to_selections(),
|
||||
original_config_do_not_use: Arc::clone(&config),
|
||||
metrics_service_name,
|
||||
app_server_client_name: None,
|
||||
|
||||
@@ -123,7 +123,6 @@ pub(super) async fn spawn_review_thread(
|
||||
reasoning_effort,
|
||||
reasoning_summary,
|
||||
session_source,
|
||||
environment: parent_turn_context.environment.clone(),
|
||||
environments: parent_turn_context.environments.clone(),
|
||||
tools_config,
|
||||
features: parent_turn_context.features.clone(),
|
||||
|
||||
@@ -3,6 +3,7 @@ use crate::goals::GoalRuntimeState;
|
||||
use codex_otel::LEGACY_NOTIFY_CONFIGURED_METRIC;
|
||||
use codex_protocol::permissions::FileSystemPath;
|
||||
use codex_protocol::permissions::FileSystemSpecialPath;
|
||||
use codex_protocol::protocol::TurnEnvironmentSelection;
|
||||
use tokio::sync::Semaphore;
|
||||
|
||||
/// Context for an initialized model agent
|
||||
@@ -207,12 +208,7 @@ impl SessionConfiguration {
|
||||
.unwrap_or_else(|| self.cwd.clone());
|
||||
|
||||
let cwd_changed = absolute_cwd.as_path() != self.cwd.as_path();
|
||||
next_configuration.cwd = absolute_cwd.clone();
|
||||
if cwd_changed
|
||||
&& let Some(primary_environment) = next_configuration.environments.first_mut()
|
||||
{
|
||||
primary_environment.cwd = absolute_cwd;
|
||||
}
|
||||
next_configuration.cwd = absolute_cwd;
|
||||
|
||||
if let Some(permission_profile) = updates.permission_profile.clone() {
|
||||
let active_permission_profile =
|
||||
@@ -962,6 +958,31 @@ impl Session {
|
||||
cancel_guard.cancel();
|
||||
*cancel_guard = CancellationToken::new();
|
||||
}
|
||||
let turn_environment = crate::environment_selection::resolve_environment_selections(
|
||||
sess.services.environment_manager.as_ref(),
|
||||
&session_configuration.environments,
|
||||
)
|
||||
.map_err(|err| {
|
||||
CodexErr::InvalidRequest(err.to_string().replace(
|
||||
"unknown turn environment id",
|
||||
"unknown stored MCP environment id",
|
||||
))
|
||||
})?
|
||||
.primary_turn_environment()
|
||||
.cloned();
|
||||
let mcp_runtime_environment = match turn_environment {
|
||||
Some(turn_environment) => McpRuntimeEnvironment::new(
|
||||
Arc::clone(&turn_environment.environment),
|
||||
turn_environment.cwd.to_path_buf(),
|
||||
),
|
||||
None => McpRuntimeEnvironment::new(
|
||||
sess.services
|
||||
.environment_manager
|
||||
.default_environment()
|
||||
.unwrap_or_else(|| sess.services.environment_manager.local_environment()),
|
||||
session_configuration.cwd.to_path_buf(),
|
||||
),
|
||||
};
|
||||
let (mcp_connection_manager, cancel_token) = McpConnectionManager::new(
|
||||
&mcp_servers,
|
||||
config.mcp_oauth_credentials_store_mode,
|
||||
@@ -970,13 +991,7 @@ impl Session {
|
||||
INITIAL_SUBMIT_ID.to_owned(),
|
||||
tx_event.clone(),
|
||||
session_configuration.permission_profile(),
|
||||
McpRuntimeEnvironment::new(
|
||||
sess.services
|
||||
.environment_manager
|
||||
.default_environment()
|
||||
.unwrap_or_else(|| sess.services.environment_manager.local_environment()),
|
||||
session_configuration.cwd.to_path_buf(),
|
||||
),
|
||||
mcp_runtime_environment,
|
||||
config.codex_home.to_path_buf(),
|
||||
codex_apps_tools_cache_key(auth),
|
||||
tool_plugin_provenance,
|
||||
|
||||
@@ -47,6 +47,7 @@ use codex_protocol::permissions::FileSystemSandboxPolicy;
|
||||
use codex_protocol::permissions::FileSystemSpecialPath;
|
||||
use codex_protocol::protocol::NonSteerableTurnKind;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
use codex_protocol::protocol::TurnEnvironmentSelection;
|
||||
use codex_protocol::request_permissions::PermissionGrantScope;
|
||||
use codex_protocol::request_permissions::RequestPermissionProfile;
|
||||
use tracing::Span;
|
||||
@@ -3289,7 +3290,7 @@ async fn session_configuration_apply_preserves_absolute_cwd_write_root_on_cwd_up
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn session_update_settings_keeps_runtime_cwds_absolute() {
|
||||
async fn session_update_settings_does_not_rewrite_sticky_environment_cwds() {
|
||||
let (session, turn_context) = make_session_and_context().await;
|
||||
let updated_cwd = turn_context.cwd.join("project");
|
||||
std::fs::create_dir_all(updated_cwd.as_path()).expect("create project dir");
|
||||
@@ -3315,6 +3316,91 @@ async fn session_update_settings_keeps_runtime_cwds_absolute() {
|
||||
assert_eq!(next_turn.config.cwd, updated_cwd);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn relative_cwd_update_without_environments_resolves_under_session_cwd() {
|
||||
let (session, _turn_context) = make_session_and_context().await;
|
||||
let original_cwd = {
|
||||
let mut state = session.state.lock().await;
|
||||
state.session_configuration.environments = Vec::new();
|
||||
state.session_configuration.cwd.clone()
|
||||
};
|
||||
let updated_cwd = original_cwd.join("project");
|
||||
std::fs::create_dir_all(updated_cwd.as_path()).expect("create project dir");
|
||||
|
||||
session
|
||||
.update_settings(SessionSettingsUpdate {
|
||||
cwd: Some(PathBuf::from("project")),
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.expect("cwd update should succeed");
|
||||
|
||||
let state = session.state.lock().await;
|
||||
assert_eq!(state.session_configuration.cwd, updated_cwd);
|
||||
assert!(state.session_configuration.environments.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn cwd_update_does_not_rewrite_sticky_environment_cwd() {
|
||||
let (session, _turn_context) = make_session_and_context().await;
|
||||
let (original_cwd, environment_cwd) = {
|
||||
let mut state = session.state.lock().await;
|
||||
let original_cwd = state.session_configuration.cwd.clone();
|
||||
let environment_cwd = original_cwd.join("environment");
|
||||
state.session_configuration.environments = vec![TurnEnvironmentSelection {
|
||||
environment_id: codex_exec_server::LOCAL_ENVIRONMENT_ID.to_string(),
|
||||
cwd: environment_cwd.clone(),
|
||||
}];
|
||||
(original_cwd, environment_cwd)
|
||||
};
|
||||
let updated_cwd = original_cwd.join("project");
|
||||
std::fs::create_dir_all(updated_cwd.as_path()).expect("create project dir");
|
||||
|
||||
session
|
||||
.update_settings(SessionSettingsUpdate {
|
||||
cwd: Some(PathBuf::from("project")),
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.expect("cwd update should succeed");
|
||||
|
||||
let state = session.state.lock().await;
|
||||
assert_eq!(state.session_configuration.cwd, updated_cwd);
|
||||
assert_eq!(
|
||||
state.session_configuration.environments[0].cwd,
|
||||
environment_cwd
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn absolute_cwd_update_with_turn_environment_is_allowed() {
|
||||
let (session, _turn_context, _rx) = make_session_and_context_with_rx().await;
|
||||
let absolute_cwd = {
|
||||
let state = session.state.lock().await;
|
||||
state.session_configuration.cwd.join("absolute-turn")
|
||||
};
|
||||
std::fs::create_dir_all(absolute_cwd.as_path()).expect("create absolute turn dir");
|
||||
|
||||
let turn_context = session
|
||||
.new_turn_with_sub_id(
|
||||
"sub-1".to_string(),
|
||||
SessionSettingsUpdate {
|
||||
cwd: Some(absolute_cwd.to_path_buf()),
|
||||
environments: Some(vec![TurnEnvironmentSelection {
|
||||
environment_id: codex_exec_server::LOCAL_ENVIRONMENT_ID.to_string(),
|
||||
cwd: absolute_cwd.clone(),
|
||||
}]),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await
|
||||
.expect("absolute cwd with explicit environments should succeed");
|
||||
|
||||
assert_eq!(turn_context.cwd, absolute_cwd);
|
||||
assert_eq!(turn_context.config.cwd, absolute_cwd);
|
||||
assert_eq!(turn_context.environments.len(), 1);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn session_new_fails_when_zsh_fork_enabled_without_zsh_path() {
|
||||
let codex_home = tempfile::tempdir().expect("create temp dir");
|
||||
@@ -3594,7 +3680,6 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
|
||||
model_info,
|
||||
&models_manager,
|
||||
/*network*/ None,
|
||||
Some(environment),
|
||||
turn_environments,
|
||||
session_configuration.cwd.clone(),
|
||||
"turn_id".to_string(),
|
||||
@@ -4333,23 +4418,24 @@ async fn turn_environments_set_primary_environment() {
|
||||
|
||||
let turn_environments = &turn_context.environments;
|
||||
assert_eq!(turn_environments.len(), 1);
|
||||
let turn_environment = turn_context
|
||||
.primary_environment()
|
||||
.expect("primary environment should be set");
|
||||
assert!(std::sync::Arc::ptr_eq(
|
||||
turn_context
|
||||
.environment
|
||||
.as_ref()
|
||||
.expect("primary environment should be set"),
|
||||
&turn_environment.environment,
|
||||
&turn_environments[0].environment
|
||||
));
|
||||
assert!(!turn_context.environments.is_empty());
|
||||
assert_eq!(turn_context.cwd.as_path(), selected_cwd.as_path());
|
||||
assert_eq!(turn_context.config.cwd.as_path(), selected_cwd.as_path());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn default_turn_uses_stored_thread_environments() {
|
||||
async fn default_turn_overlays_session_cwd_onto_stored_thread_environments() {
|
||||
let (session, _turn_context, _rx) = make_session_and_context_with_rx().await;
|
||||
let session_cwd = session.get_config().await.cwd.clone();
|
||||
let selected_cwd =
|
||||
AbsolutePathBuf::try_from(session.get_config().await.cwd.as_path().join("selected"))
|
||||
.expect("absolute path");
|
||||
AbsolutePathBuf::try_from(session_cwd.as_path().join("selected")).expect("absolute path");
|
||||
|
||||
{
|
||||
let mut state = session.state.lock().await;
|
||||
@@ -4363,15 +4449,15 @@ async fn default_turn_uses_stored_thread_environments() {
|
||||
|
||||
let turn_environments = &turn_context.environments;
|
||||
assert_eq!(turn_environments.len(), 1);
|
||||
let turn_environment = turn_context
|
||||
.primary_environment()
|
||||
.expect("primary environment should be set");
|
||||
assert!(std::sync::Arc::ptr_eq(
|
||||
turn_context
|
||||
.environment
|
||||
.as_ref()
|
||||
.expect("primary environment should be set"),
|
||||
&turn_environment.environment,
|
||||
&turn_environments[0].environment
|
||||
));
|
||||
assert_eq!(turn_context.cwd, selected_cwd);
|
||||
assert_eq!(turn_context.config.cwd, selected_cwd);
|
||||
assert_eq!(turn_context.cwd, session_cwd);
|
||||
assert_eq!(turn_context.config.cwd, session_cwd);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -4386,54 +4472,42 @@ async fn default_turn_honors_empty_stored_thread_environments() {
|
||||
|
||||
let turn_context = session.new_default_turn().await;
|
||||
|
||||
assert!(turn_context.environment.is_none());
|
||||
assert!(turn_context.primary_environment().is_none());
|
||||
assert!(turn_context.environments.is_empty());
|
||||
assert_eq!(turn_context.cwd, session_cwd);
|
||||
assert_eq!(turn_context.config.cwd, session_cwd);
|
||||
assert_eq!(turn_context.environments.len(), 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn multiple_turn_environments_use_first_as_primary_environment() {
|
||||
let (session, _turn_context, _rx) = make_session_and_context_with_rx().await;
|
||||
let session_cwd = session.get_config().await.cwd.clone();
|
||||
let first_cwd =
|
||||
AbsolutePathBuf::try_from(session_cwd.as_path().join("first")).expect("absolute path");
|
||||
let second_cwd =
|
||||
AbsolutePathBuf::try_from(session_cwd.as_path().join("second")).expect("absolute path");
|
||||
async fn primary_environment_uses_first_turn_environment() {
|
||||
let (_session, mut turn_context) = make_session_and_context().await;
|
||||
let first_environment = turn_context.environments[0].clone();
|
||||
let second_cwd = turn_context.cwd.join("second");
|
||||
turn_context.environments.push(TurnEnvironment {
|
||||
environment_id: "second".to_string(),
|
||||
environment: Arc::clone(&first_environment.environment),
|
||||
cwd: second_cwd.clone(),
|
||||
});
|
||||
|
||||
let turn_context = session
|
||||
.new_turn_with_sub_id(
|
||||
"sub-1".to_string(),
|
||||
SessionSettingsUpdate {
|
||||
environments: Some(vec![
|
||||
TurnEnvironmentSelection {
|
||||
environment_id: "local".to_string(),
|
||||
cwd: first_cwd.clone(),
|
||||
},
|
||||
TurnEnvironmentSelection {
|
||||
environment_id: "local".to_string(),
|
||||
cwd: second_cwd.clone(),
|
||||
},
|
||||
]),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await
|
||||
.expect("turn should start");
|
||||
|
||||
let turn_environments = &turn_context.environments;
|
||||
assert_eq!(turn_environments.len(), 2);
|
||||
assert_eq!(turn_environments[0].cwd, first_cwd);
|
||||
assert_eq!(turn_environments[1].cwd, second_cwd);
|
||||
assert!(std::sync::Arc::ptr_eq(
|
||||
assert_eq!(
|
||||
turn_context
|
||||
.environment
|
||||
.as_ref()
|
||||
.expect("primary environment should be set"),
|
||||
&turn_environments[0].environment
|
||||
));
|
||||
assert_eq!(turn_context.cwd, first_cwd);
|
||||
assert_eq!(turn_context.config.cwd, first_cwd);
|
||||
.primary_environment()
|
||||
.expect("primary environment")
|
||||
.environment_id,
|
||||
first_environment.environment_id
|
||||
);
|
||||
assert_eq!(
|
||||
turn_context
|
||||
.environments
|
||||
.iter()
|
||||
.find(|environment| environment.environment_id == "second")
|
||||
.expect("second environment")
|
||||
.cwd,
|
||||
second_cwd
|
||||
);
|
||||
assert_eq!(turn_context.environments.len(), 2);
|
||||
assert_eq!(turn_context.environments[1].cwd, second_cwd);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -4451,15 +4525,19 @@ async fn empty_turn_environments_clear_primary_environment() {
|
||||
.await
|
||||
.expect("turn should start");
|
||||
|
||||
assert!(turn_context.environment.is_none());
|
||||
assert!(turn_context.primary_environment().is_none());
|
||||
assert!(turn_context.environments.is_empty());
|
||||
assert_eq!(turn_context.cwd, session.get_config().await.cwd);
|
||||
assert_eq!(turn_context.config.cwd, session.get_config().await.cwd);
|
||||
assert_eq!(turn_context.environments.len(), 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn unknown_turn_environment_returns_error() {
|
||||
let (session, _turn_context, _rx) = make_session_and_context_with_rx().await;
|
||||
let original_configuration = {
|
||||
let state = session.state.lock().await;
|
||||
state.session_configuration.clone()
|
||||
};
|
||||
|
||||
let err = session
|
||||
.new_turn_with_sub_id(
|
||||
@@ -4467,7 +4545,7 @@ async fn unknown_turn_environment_returns_error() {
|
||||
SessionSettingsUpdate {
|
||||
environments: Some(vec![TurnEnvironmentSelection {
|
||||
environment_id: "missing".to_string(),
|
||||
cwd: session.get_config().await.cwd.clone(),
|
||||
cwd: original_configuration.cwd.clone(),
|
||||
}]),
|
||||
..Default::default()
|
||||
},
|
||||
@@ -4475,8 +4553,58 @@ async fn unknown_turn_environment_returns_error() {
|
||||
.await
|
||||
.expect_err("unknown environment should fail");
|
||||
|
||||
let current_configuration = {
|
||||
let state = session.state.lock().await;
|
||||
state.session_configuration.clone()
|
||||
};
|
||||
assert!(matches!(err, CodexErr::InvalidRequest(_)));
|
||||
assert!(err.to_string().contains("missing"));
|
||||
assert_eq!(current_configuration.cwd, original_configuration.cwd);
|
||||
assert_eq!(
|
||||
current_configuration.environments,
|
||||
original_configuration.environments
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn duplicate_turn_environment_returns_error_without_mutating_session() {
|
||||
let (session, _turn_context, _rx) = make_session_and_context_with_rx().await;
|
||||
let original_configuration = {
|
||||
let state = session.state.lock().await;
|
||||
state.session_configuration.clone()
|
||||
};
|
||||
|
||||
let err = session
|
||||
.new_turn_with_sub_id(
|
||||
"sub-1".to_string(),
|
||||
SessionSettingsUpdate {
|
||||
environments: Some(vec![
|
||||
TurnEnvironmentSelection {
|
||||
environment_id: "local".to_string(),
|
||||
cwd: original_configuration.cwd.clone(),
|
||||
},
|
||||
TurnEnvironmentSelection {
|
||||
environment_id: "local".to_string(),
|
||||
cwd: original_configuration.cwd.join("second"),
|
||||
},
|
||||
]),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await
|
||||
.expect_err("duplicate environment should fail");
|
||||
|
||||
let current_configuration = {
|
||||
let state = session.state.lock().await;
|
||||
state.session_configuration.clone()
|
||||
};
|
||||
assert!(matches!(err, CodexErr::InvalidRequest(_)));
|
||||
assert!(err.to_string().contains("duplicate"));
|
||||
assert_eq!(current_configuration.cwd, original_configuration.cwd);
|
||||
assert_eq!(
|
||||
current_configuration.environments,
|
||||
original_configuration.environments
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -5033,7 +5161,6 @@ where
|
||||
model_info,
|
||||
&models_manager,
|
||||
/*network*/ None,
|
||||
Some(environment),
|
||||
turn_environments,
|
||||
session_configuration.cwd.clone(),
|
||||
"turn_id".to_string(),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use super::*;
|
||||
use crate::compact::InitialContextInjection;
|
||||
use crate::environment_selection::ResolvedTurnEnvironments;
|
||||
use crate::exec::ExecCapturePolicy;
|
||||
use crate::exec::ExecParams;
|
||||
use crate::exec_policy::ExecPolicyManager;
|
||||
@@ -754,7 +755,9 @@ async fn guardian_subagent_does_not_inherit_parent_exec_policy_rules() {
|
||||
parent_rollout_thread_trace: codex_rollout_trace::ThreadTraceContext::disabled(),
|
||||
user_shell_override: None,
|
||||
parent_trace: None,
|
||||
environments: Vec::new(),
|
||||
environment_selections: ResolvedTurnEnvironments {
|
||||
turn_environments: Vec::new(),
|
||||
},
|
||||
analytics_events_client: None,
|
||||
thread_store,
|
||||
})
|
||||
|
||||
@@ -59,7 +59,6 @@ pub(crate) struct TurnContext {
|
||||
pub(crate) reasoning_effort: Option<ReasoningEffortConfig>,
|
||||
pub(crate) reasoning_summary: ReasoningSummaryConfig,
|
||||
pub(crate) session_source: SessionSource,
|
||||
pub(crate) environment: Option<Arc<Environment>>,
|
||||
pub(crate) environments: Vec<TurnEnvironment>,
|
||||
/// The session's absolute working directory. All relative paths provided
|
||||
/// by the model as well as sandbox policies are resolved against this path
|
||||
@@ -106,6 +105,10 @@ impl TurnContext {
|
||||
self.permission_profile.network_sandbox_policy()
|
||||
}
|
||||
|
||||
pub(crate) fn primary_environment(&self) -> Option<&TurnEnvironment> {
|
||||
self.environments.first()
|
||||
}
|
||||
|
||||
pub(crate) fn sandbox_policy(&self) -> SandboxPolicy {
|
||||
let file_system_sandbox_policy = self.file_system_sandbox_policy();
|
||||
let network_sandbox_policy = self.network_sandbox_policy();
|
||||
@@ -230,7 +233,6 @@ impl TurnContext {
|
||||
reasoning_effort,
|
||||
reasoning_summary: self.reasoning_summary,
|
||||
session_source: self.session_source.clone(),
|
||||
environment: self.environment.clone(),
|
||||
environments: self.environments.clone(),
|
||||
cwd: self.cwd.clone(),
|
||||
current_date: self.current_date.clone(),
|
||||
@@ -432,7 +434,6 @@ impl Session {
|
||||
model_info: ModelInfo,
|
||||
models_manager: &SharedModelsManager,
|
||||
network: Option<NetworkProxy>,
|
||||
environment: Option<Arc<Environment>>,
|
||||
environments: Vec<TurnEnvironment>,
|
||||
cwd: AbsolutePathBuf,
|
||||
sub_id: String,
|
||||
@@ -474,7 +475,7 @@ impl Session {
|
||||
)
|
||||
.with_web_search_config(per_turn_config.web_search_config.clone())
|
||||
.with_allow_login_shell(per_turn_config.permissions.allow_login_shell)
|
||||
.with_has_environment(environment.is_some())
|
||||
.with_has_environment(!environments.is_empty())
|
||||
.with_spawn_agent_usage_hint(per_turn_config.multi_agent_v2.usage_hint_enabled)
|
||||
.with_spawn_agent_usage_hint_text(per_turn_config.multi_agent_v2.usage_hint_text.clone())
|
||||
.with_hide_spawn_agent_metadata(per_turn_config.multi_agent_v2.hide_spawn_agent_metadata)
|
||||
@@ -522,7 +523,6 @@ impl Session {
|
||||
reasoning_effort,
|
||||
reasoning_summary,
|
||||
session_source,
|
||||
environment,
|
||||
environments,
|
||||
cwd,
|
||||
current_date: Some(current_date),
|
||||
@@ -564,10 +564,16 @@ impl Session {
|
||||
let mut state = self.state.lock().await;
|
||||
match state.session_configuration.clone().apply(&updates) {
|
||||
Ok(next) => {
|
||||
let effective_environments = updates
|
||||
let mut effective_environments = updates
|
||||
.environments
|
||||
.clone()
|
||||
.unwrap_or_else(|| next.environments.clone());
|
||||
if updates.environments.is_none() {
|
||||
Self::overlay_runtime_cwd_on_primary_environment(
|
||||
&mut effective_environments,
|
||||
&next.cwd,
|
||||
);
|
||||
}
|
||||
let turn_environments =
|
||||
self.resolve_turn_environments(&effective_environments)?;
|
||||
let previous_cwd = state.session_configuration.cwd.clone();
|
||||
@@ -641,27 +647,11 @@ impl Session {
|
||||
&self,
|
||||
environments: &[TurnEnvironmentSelection],
|
||||
) -> CodexResult<Vec<TurnEnvironment>> {
|
||||
let mut turn_environments = Vec::with_capacity(environments.len());
|
||||
for selected_environment in environments {
|
||||
let environment_id = selected_environment.environment_id.clone();
|
||||
let environment = self
|
||||
.services
|
||||
.environment_manager
|
||||
.get_environment(&environment_id)
|
||||
.ok_or_else(|| {
|
||||
CodexErr::InvalidRequest(format!(
|
||||
"unknown turn environment id `{environment_id}`"
|
||||
))
|
||||
})?;
|
||||
let cwd = selected_environment.cwd.clone();
|
||||
turn_environments.push(TurnEnvironment {
|
||||
environment_id,
|
||||
environment,
|
||||
cwd,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(turn_environments)
|
||||
crate::environment_selection::resolve_environment_selections(
|
||||
self.services.environment_manager.as_ref(),
|
||||
environments,
|
||||
)
|
||||
.map(|resolved| resolved.turn_environments)
|
||||
}
|
||||
|
||||
async fn new_turn_from_configuration(
|
||||
@@ -672,8 +662,6 @@ impl Session {
|
||||
turn_environments: Vec<TurnEnvironment>,
|
||||
) -> Arc<TurnContext> {
|
||||
let primary_turn_environment = turn_environments.first();
|
||||
let environment = primary_turn_environment
|
||||
.map(|turn_environment| Arc::clone(&turn_environment.environment));
|
||||
let cwd = primary_turn_environment
|
||||
.map(|turn_environment| turn_environment.cwd.clone())
|
||||
.unwrap_or_else(|| session_configuration.cwd.clone());
|
||||
@@ -700,9 +688,8 @@ impl Session {
|
||||
.await;
|
||||
let effective_skill_roots = plugin_outcome.effective_skill_roots();
|
||||
let skills_input = skills_load_input_from_config(&per_turn_config, effective_skill_roots);
|
||||
let fs = environment
|
||||
.as_ref()
|
||||
.map(|environment| environment.get_filesystem());
|
||||
let fs = primary_turn_environment
|
||||
.map(|turn_environment| turn_environment.environment.get_filesystem());
|
||||
let skills_outcome = Arc::new(
|
||||
self.services
|
||||
.skills_manager
|
||||
@@ -731,7 +718,6 @@ impl Session {
|
||||
)
|
||||
.then(|| started_proxy.proxy())
|
||||
}),
|
||||
environment,
|
||||
turn_environments,
|
||||
cwd,
|
||||
sub_id,
|
||||
@@ -773,14 +759,18 @@ impl Session {
|
||||
let state = self.state.lock().await;
|
||||
state.session_configuration.clone()
|
||||
};
|
||||
let turn_environments =
|
||||
match self.resolve_turn_environments(&session_configuration.environments) {
|
||||
Ok(turn_environments) => turn_environments,
|
||||
Err(err) => {
|
||||
warn!("failed to resolve stored session environments: {err}");
|
||||
Vec::new()
|
||||
}
|
||||
};
|
||||
let mut effective_environments = session_configuration.environments.clone();
|
||||
Self::overlay_runtime_cwd_on_primary_environment(
|
||||
&mut effective_environments,
|
||||
&session_configuration.cwd,
|
||||
);
|
||||
let turn_environments = match self.resolve_turn_environments(&effective_environments) {
|
||||
Ok(turn_environments) => turn_environments,
|
||||
Err(err) => {
|
||||
warn!("failed to resolve stored session environments: {err}");
|
||||
Vec::new()
|
||||
}
|
||||
};
|
||||
|
||||
self.new_turn_from_configuration(
|
||||
sub_id,
|
||||
@@ -790,4 +780,15 @@ impl Session {
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
fn overlay_runtime_cwd_on_primary_environment(
|
||||
environments: &mut [TurnEnvironmentSelection],
|
||||
runtime_cwd: &AbsolutePathBuf,
|
||||
) {
|
||||
if let Some(turn_environment) = environments.first_mut()
|
||||
&& turn_environment.cwd != *runtime_cwd
|
||||
{
|
||||
turn_environment.cwd = runtime_cwd.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,7 @@ use crate::codex_thread::CodexThread;
|
||||
use crate::config::Config;
|
||||
use crate::config::ThreadStoreConfig;
|
||||
use crate::environment_selection::default_thread_environment_selections;
|
||||
use crate::environment_selection::selected_primary_environment;
|
||||
use crate::environment_selection::validate_environment_selections;
|
||||
use crate::environment_selection::resolve_environment_selections;
|
||||
use crate::file_watcher::FileWatcher;
|
||||
use crate::mcp::McpManager;
|
||||
use crate::rollout::RolloutRecorder;
|
||||
@@ -433,7 +432,8 @@ impl ThreadManager {
|
||||
&self,
|
||||
environments: &[TurnEnvironmentSelection],
|
||||
) -> CodexResult<()> {
|
||||
validate_environment_selections(self.state.environment_manager.as_ref(), environments)
|
||||
resolve_environment_selections(self.state.environment_manager.as_ref(), environments)
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
pub fn get_models_manager(&self) -> SharedModelsManager {
|
||||
@@ -1098,16 +1098,16 @@ impl ThreadManagerState {
|
||||
threads.remove(&resumed.conversation_id);
|
||||
}
|
||||
}
|
||||
let environment =
|
||||
selected_primary_environment(self.environment_manager.as_ref(), &environments)?;
|
||||
let watch_registration = match environment.as_ref() {
|
||||
Some(environment) if !environment.is_remote() => {
|
||||
let environment_selections =
|
||||
resolve_environment_selections(self.environment_manager.as_ref(), &environments)?;
|
||||
let watch_registration = match environment_selections.primary_turn_environment() {
|
||||
Some(turn_environment) if !turn_environment.environment.is_remote() => {
|
||||
self.skills_watcher
|
||||
.register_config(
|
||||
&config,
|
||||
self.skills_manager.as_ref(),
|
||||
self.plugins_manager.as_ref(),
|
||||
Some(environment.get_filesystem()),
|
||||
Some(turn_environment.environment.get_filesystem()),
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -1139,7 +1139,7 @@ impl ThreadManagerState {
|
||||
parent_rollout_thread_trace,
|
||||
user_shell_override,
|
||||
parent_trace,
|
||||
environments,
|
||||
environment_selections,
|
||||
analytics_events_client: self.analytics_events_client.clone(),
|
||||
thread_store: Arc::clone(&self.thread_store),
|
||||
})
|
||||
|
||||
@@ -363,13 +363,14 @@ impl ToolHandler for ApplyPatchHandler {
|
||||
// Avoid building temporary ExecParams/command vectors; derive directly from inputs.
|
||||
let cwd = turn.cwd.clone();
|
||||
let command = vec!["apply_patch".to_string(), patch_input.clone()];
|
||||
let Some(environment) = turn.environment.as_ref() else {
|
||||
let Some(turn_environment) = turn.primary_environment() else {
|
||||
return Err(FunctionCallError::RespondToModel(
|
||||
"apply_patch is unavailable in this session".to_string(),
|
||||
));
|
||||
};
|
||||
let fs = environment.get_filesystem();
|
||||
let sandbox = environment
|
||||
let fs = turn_environment.environment.get_filesystem();
|
||||
let sandbox = turn_environment
|
||||
.environment
|
||||
.is_remote()
|
||||
.then(|| turn.file_system_sandbox_context(/*additional_permissions*/ None));
|
||||
match codex_apply_patch::maybe_parse_apply_patch_verified(
|
||||
@@ -474,9 +475,8 @@ pub(crate) async fn intercept_apply_patch(
|
||||
tool_name: &str,
|
||||
) -> Result<Option<FunctionToolOutput>, FunctionCallError> {
|
||||
let sandbox = turn
|
||||
.environment
|
||||
.as_ref()
|
||||
.filter(|env| env.is_remote())
|
||||
.primary_environment()
|
||||
.filter(|env| env.environment.is_remote())
|
||||
.map(|_| turn.file_system_sandbox_context(/*additional_permissions*/ None));
|
||||
match codex_apply_patch::maybe_parse_apply_patch_verified(command, cwd, fs, sandbox.as_ref())
|
||||
.await
|
||||
|
||||
@@ -412,12 +412,12 @@ impl ShellHandler {
|
||||
} = args;
|
||||
|
||||
let mut exec_params = exec_params;
|
||||
let Some(environment) = turn.environment.as_ref() else {
|
||||
let Some(turn_environment) = turn.primary_environment() else {
|
||||
return Err(FunctionCallError::RespondToModel(
|
||||
"shell is unavailable in this session".to_string(),
|
||||
));
|
||||
};
|
||||
let fs = environment.get_filesystem();
|
||||
let fs = turn_environment.environment.get_filesystem();
|
||||
|
||||
let dependency_env = session.dependency_env().await;
|
||||
if !dependency_env.is_empty() {
|
||||
|
||||
@@ -196,12 +196,12 @@ impl ToolHandler for UnifiedExecHandler {
|
||||
}
|
||||
};
|
||||
|
||||
let Some(environment) = turn.environment.as_ref() else {
|
||||
let Some(turn_environment) = turn.primary_environment() else {
|
||||
return Err(FunctionCallError::RespondToModel(
|
||||
"unified exec is unavailable in this session".to_string(),
|
||||
));
|
||||
};
|
||||
let fs = environment.get_filesystem();
|
||||
let fs = turn_environment.environment.get_filesystem();
|
||||
|
||||
let manager: &UnifiedExecProcessManager = &session.services.unified_exec_manager;
|
||||
let context = UnifiedExecContext::new(session.clone(), turn.clone(), call_id.clone());
|
||||
|
||||
@@ -88,16 +88,18 @@ impl ToolHandler for ViewImageHandler {
|
||||
};
|
||||
|
||||
let abs_path = turn.resolve_path(Some(args.path));
|
||||
let Some(environment) = turn.environment.as_ref() else {
|
||||
let Some(environment) = turn.primary_environment() else {
|
||||
return Err(FunctionCallError::RespondToModel(
|
||||
"view_image is unavailable in this session".to_string(),
|
||||
));
|
||||
};
|
||||
let sandbox = environment
|
||||
.environment
|
||||
.is_remote()
|
||||
.then(|| turn.file_system_sandbox_context(/*additional_permissions*/ None));
|
||||
|
||||
let metadata = environment
|
||||
.environment
|
||||
.get_filesystem()
|
||||
.get_metadata(&abs_path, sandbox.as_ref())
|
||||
.await
|
||||
@@ -115,6 +117,7 @@ impl ToolHandler for ViewImageHandler {
|
||||
)));
|
||||
}
|
||||
let file_bytes = environment
|
||||
.environment
|
||||
.get_filesystem()
|
||||
.read_file(&abs_path, sandbox.as_ref())
|
||||
.await
|
||||
|
||||
@@ -191,11 +191,11 @@ impl ToolRuntime<ApplyPatchRequest, ExecToolCallOutput> for ApplyPatchRuntime {
|
||||
attempt: &SandboxAttempt<'_>,
|
||||
ctx: &ToolCtx,
|
||||
) -> Result<ExecToolCallOutput, ToolError> {
|
||||
let environment = ctx.turn.environment.as_ref().ok_or_else(|| {
|
||||
let turn_environment = ctx.turn.primary_environment().ok_or_else(|| {
|
||||
ToolError::Rejected("apply_patch is unavailable in this session".to_string())
|
||||
})?;
|
||||
let started_at = Instant::now();
|
||||
let fs = environment.get_filesystem();
|
||||
let fs = turn_environment.environment.get_filesystem();
|
||||
let sandbox = Self::file_system_sandbox_context_for_attempt(req, attempt);
|
||||
let mut stdout = Vec::new();
|
||||
let mut stderr = Vec::new();
|
||||
|
||||
@@ -254,9 +254,8 @@ impl<'a> ToolRuntime<UnifiedExecRequest, UnifiedExecProcess> for UnifiedExecRunt
|
||||
}
|
||||
let environment_is_remote = ctx
|
||||
.turn
|
||||
.environment
|
||||
.as_ref()
|
||||
.is_some_and(|environment| environment.is_remote());
|
||||
.primary_environment()
|
||||
.is_some_and(|turn_environment| turn_environment.environment.is_remote());
|
||||
let command = if environment_is_remote {
|
||||
base_command.to_vec()
|
||||
} else {
|
||||
@@ -293,12 +292,12 @@ impl<'a> ToolRuntime<UnifiedExecRequest, UnifiedExecProcess> for UnifiedExecRunt
|
||||
.await?
|
||||
{
|
||||
Some(prepared) => {
|
||||
let Some(environment) = ctx.turn.environment.as_ref() else {
|
||||
let Some(turn_environment) = ctx.turn.primary_environment() else {
|
||||
return Err(ToolError::Rejected(
|
||||
"exec_command is unavailable in this session".to_string(),
|
||||
));
|
||||
};
|
||||
if environment.is_remote() {
|
||||
if turn_environment.environment.is_remote() {
|
||||
return Err(ToolError::Rejected(
|
||||
"unified_exec zsh-fork is not supported when exec_server_url is configured".to_string(),
|
||||
));
|
||||
@@ -310,7 +309,7 @@ impl<'a> ToolRuntime<UnifiedExecRequest, UnifiedExecProcess> for UnifiedExecRunt
|
||||
&prepared.exec_request,
|
||||
req.tty,
|
||||
prepared.spawn_lifecycle,
|
||||
environment.as_ref(),
|
||||
turn_environment.environment.as_ref(),
|
||||
)
|
||||
.await
|
||||
.map_err(|err| match err {
|
||||
@@ -338,7 +337,7 @@ impl<'a> ToolRuntime<UnifiedExecRequest, UnifiedExecProcess> for UnifiedExecRunt
|
||||
.env_for(command, options, managed_network)
|
||||
.map_err(|err| ToolError::Codex(err.into()))?;
|
||||
exec_env.exec_server_env_config = req.exec_server_env_config.clone();
|
||||
let Some(environment) = ctx.turn.environment.as_ref() else {
|
||||
let Some(turn_environment) = ctx.turn.primary_environment() else {
|
||||
return Err(ToolError::Rejected(
|
||||
"exec_command is unavailable in this session".to_string(),
|
||||
));
|
||||
@@ -349,7 +348,7 @@ impl<'a> ToolRuntime<UnifiedExecRequest, UnifiedExecProcess> for UnifiedExecRunt
|
||||
&exec_env,
|
||||
req.tty,
|
||||
Box::new(NoopSpawnLifecycle),
|
||||
environment.as_ref(),
|
||||
turn_environment.environment.as_ref(),
|
||||
)
|
||||
.await
|
||||
.map_err(|err| match err {
|
||||
|
||||
@@ -96,7 +96,10 @@ async fn exec_command_with_tty(
|
||||
&request,
|
||||
tty,
|
||||
Box::new(NoopSpawnLifecycle),
|
||||
turn.environment.as_ref().expect("turn environment"),
|
||||
turn.primary_environment()
|
||||
.expect("turn environment")
|
||||
.environment
|
||||
.as_ref(),
|
||||
)
|
||||
.await?,
|
||||
);
|
||||
@@ -591,7 +594,7 @@ async fn remote_exec_server_rejects_inherited_fd_launches() -> anyhow::Result<()
|
||||
|
||||
let remote_test_env = remote_test_env().await?;
|
||||
let (_, mut turn) = make_session_and_context().await;
|
||||
turn.environment = Some(Arc::new(remote_test_env.environment().clone()));
|
||||
turn.environments[0].environment = Arc::new(remote_test_env.environment().clone());
|
||||
|
||||
let request = test_exec_request(
|
||||
&turn,
|
||||
@@ -609,7 +612,10 @@ async fn remote_exec_server_rejects_inherited_fd_launches() -> anyhow::Result<()
|
||||
Box::new(TestSpawnLifecycle {
|
||||
inherited_fds: vec![42],
|
||||
}),
|
||||
turn.environment.as_ref().expect("turn environment"),
|
||||
turn.primary_environment()
|
||||
.expect("turn environment")
|
||||
.environment
|
||||
.as_ref(),
|
||||
)
|
||||
.await
|
||||
.expect_err("expected inherited fd rejection");
|
||||
|
||||
Reference in New Issue
Block a user