Centralize turn environment selection state (#38461)

## What changed

- Store `TurnEnvironmentSelection` directly on each resolved `TurnEnvironment`
  instead of copying its environment ID, working directory, and workspace roots
  into separate fields.
- Update environment resolution, tool execution, approvals, and related tests to
  access selection state through the retained value.

GitOrigin-RevId: ab723d21e112b8af5bcaf1f772a5e376553312bc
This commit is contained in:
sayan-oai
2026-08-14 00:22:54 +00:00
committed by copyberry
parent 18bbb585e7
commit 535795f7d1
31 changed files with 159 additions and 146 deletions
+2 -2
View File
@@ -65,7 +65,7 @@ pub(crate) async fn load_project_instructions(
match read_agents_md(
config,
filesystem.as_ref(),
&turn_environment.environment_id,
&turn_environment.selection.environment_id,
turn_environment.cwd(),
remaining,
)
@@ -80,7 +80,7 @@ pub(crate) async fn load_project_instructions(
Ok(None) => {}
Err(e) => {
error!(
environment_id = turn_environment.environment_id,
environment_id = turn_environment.selection.environment_id,
"error trying to find AGENTS.md docs: {e:#}"
);
}
+6 -3
View File
@@ -23,6 +23,7 @@ use codex_exec_server::RemoveOptions;
use codex_extension_api::UserInstructions;
use codex_features::Feature;
use codex_protocol::models::PermissionProfile;
use codex_protocol::protocol::TurnEnvironmentSelection;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_path_uri::PathUri;
use core_test_support::PathBufExt;
@@ -332,13 +333,15 @@ fn resolved_local_environments<const N: usize>(
.into_iter()
.map(|(environment_id, cwd)| {
TurnEnvironmentState::Ready(TurnEnvironment::new(
environment_id.to_string(),
TurnEnvironmentSelection {
environment_id: environment_id.to_string(),
cwd: PathUri::from_abs_path(&cwd),
workspace_roots: Vec::new(),
},
Arc::new(
Environment::create_for_tests(/*exec_server_url*/ None)
.expect("local environment"),
),
PathUri::from_abs_path(&cwd),
Vec::new(),
/*shell*/ None,
TurnEnvironmentConfig {
allow_login_shell: true,
@@ -342,7 +342,7 @@ fn environment_states(snapshot: &TurnEnvironmentSnapshot) -> BTreeMap<String, En
.enumerate()
.map(|(index, environment)| {
(
environment.environment_id.clone(),
environment.selection.environment_id.clone(),
EnvironmentState {
cwd: environment.cwd().clone(),
status: EnvironmentStatus::Available,
+14 -13
View File
@@ -506,10 +506,8 @@ impl TurnEnvironmentState {
match resolved {
Some(Ok(environment)) => {
let mut turn_environment = TurnEnvironment::new(
starting.selection.environment_id,
starting.selection,
environment.environment,
starting.selection.cwd,
starting.selection.workspace_roots,
environment.shell,
starting.config,
);
@@ -578,7 +576,7 @@ impl TurnEnvironmentSnapshot {
self.turn_environments()
.map(|environment| {
(
environment.environment_id.clone(),
environment.selection.environment_id.clone(),
Some(Arc::clone(&environment.environment)),
)
})
@@ -932,6 +930,7 @@ url = "ws://127.0.0.1:8765"
resolved
.primary()
.expect("primary environment")
.selection
.environment_id,
"local"
);
@@ -1344,10 +1343,8 @@ url = "ws://127.0.0.1:8765"
.expect("inherited environment"),
);
let inherited = TurnEnvironment::new(
selection.environment_id.clone(),
selection.clone(),
Arc::clone(&inherited_environment),
selection.cwd.clone(),
Vec::new(),
/*shell*/ None,
test_environment_config(),
);
@@ -1495,10 +1492,12 @@ url = "ws://127.0.0.1:8765"
);
let remote = TurnEnvironmentSnapshot {
environments: vec![TurnEnvironmentState::Ready(TurnEnvironment::new(
REMOTE_ENVIRONMENT_ID.to_string(),
TurnEnvironmentSelection {
environment_id: REMOTE_ENVIRONMENT_ID.to_string(),
cwd: remote_cwd_uri.clone(),
workspace_roots: Vec::new(),
},
remote_environment.clone(),
remote_cwd_uri.clone(),
Vec::new(),
/*shell*/ None,
test_environment_config(),
))],
@@ -1506,10 +1505,12 @@ url = "ws://127.0.0.1:8765"
let multiple = TurnEnvironmentSnapshot {
environments: vec![
TurnEnvironmentState::Ready(TurnEnvironment::new(
REMOTE_ENVIRONMENT_ID.to_string(),
TurnEnvironmentSelection {
environment_id: REMOTE_ENVIRONMENT_ID.to_string(),
cwd: remote_cwd_uri,
workspace_roots: Vec::new(),
},
remote_environment,
remote_cwd_uri,
Vec::new(),
/*shell*/ None,
test_environment_config(),
)),
+4 -12
View File
@@ -15,7 +15,6 @@ use crate::guardian::review::guardian_review_session_config;
use crate::guardian::review::routes_approval_to_guardian_with_reviewer;
use crate::session::session::Session;
use crate::session::turn_context::TurnContext;
use crate::session::turn_context::TurnEnvironment;
use crate::test_support;
use codex_analytics::GuardianApprovalRequestSource;
use codex_config::ConfigLayerStack;
@@ -631,17 +630,10 @@ async fn build_guardian_prompt_includes_parent_turn_denied_reads() -> anyhow::Re
};
environment.config.permission_profile =
PermissionProfileSnapshot::legacy(environment_permission_profile);
*environment = TurnEnvironment::new(
environment.environment_id.clone(),
Arc::clone(&environment.environment),
environment.cwd().clone(),
vec![
PathUri::from_abs_path(&workspace_root),
PathUri::from_abs_path(&second_workspace_root),
],
environment.shell.clone(),
environment.config.clone(),
);
environment.selection.workspace_roots = vec![
PathUri::from_abs_path(&workspace_root),
PathUri::from_abs_path(&second_workspace_root),
];
let session = Arc::new(session);
let turn = Arc::new(turn);
seed_guardian_parent_history(&session, &turn).await;
+4 -11
View File
@@ -161,10 +161,10 @@ async fn build_uploaded_argument_value(
.join(file_path)
.map_err(|error| contextualize_error(error.to_string()))?;
let additional_permissions = merge_permission_profiles(
sess.granted_session_permissions(&turn_environment.environment_id)
sess.granted_session_permissions(&turn_environment.selection.environment_id)
.await
.as_ref(),
sess.granted_turn_permissions(&turn_environment.environment_id)
sess.granted_turn_permissions(&turn_environment.selection.environment_id)
.await
.as_ref(),
);
@@ -271,7 +271,6 @@ mod tests {
use crate::environment_selection::TurnEnvironmentState;
use crate::session::tests::make_session_and_context;
use crate::session::turn_context::TurnContext;
use crate::session::turn_context::TurnEnvironment;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_path_uri::PathUri;
use pretty_assertions::assert_eq;
@@ -285,14 +284,8 @@ mod tests {
else {
panic!("expected ready primary environment");
};
*primary = TurnEnvironment::new(
primary.environment_id.clone(),
Arc::clone(&primary.environment),
PathUri::from_abs_path(&cwd),
Vec::new(),
primary.shell.clone(),
primary.config.clone(),
);
primary.selection.cwd = PathUri::from_abs_path(&cwd);
primary.selection.workspace_roots.clear();
}
#[tokio::test]
+1 -1
View File
@@ -802,7 +802,7 @@ fn sandbox_cwd_for_mcp_server(step_context: &StepContext, environment_id: &str)
if let Some(environment) = step_context
.environments
.turn_environments()
.find(|environment| environment.environment_id == environment_id)
.find(|environment| environment.selection.environment_id == environment_id)
{
return Some(environment.cwd().clone());
}
+6 -3
View File
@@ -29,6 +29,7 @@ use codex_protocol::protocol::AskForApproval;
use codex_protocol::protocol::EventMsg;
use codex_protocol::protocol::GranularApprovalConfig;
use codex_protocol::protocol::McpInvocation;
use codex_protocol::protocol::TurnEnvironmentSelection;
use codex_utils_path_uri::PathUri;
use core_test_support::hooks::trusted_config_layer_stack;
use core_test_support::responses::ev_assistant_message;
@@ -1106,10 +1107,12 @@ async fn mcp_sandbox_cwd_uses_matching_server_environment_uri() -> anyhow::Resul
.environments
.environments
.push(TurnEnvironmentState::Ready(TurnEnvironment::new(
"remote".to_string(),
TurnEnvironmentSelection {
environment_id: "remote".to_string(),
cwd: secondary_cwd.clone(),
workspace_roots: Vec::new(),
},
environment,
secondary_cwd.clone(),
Vec::new(),
/*shell*/ None,
TurnEnvironmentConfig {
allow_login_shell: true,
+1 -1
View File
@@ -392,7 +392,7 @@ impl Session {
sandbox.windows_sandbox_private_desktop =
config.permissions.windows_sandbox_private_desktop;
sandbox.use_legacy_landlock = config.features.use_legacy_landlock();
(environment.environment_id.clone(), sandbox)
(environment.selection.environment_id.clone(), sandbox)
})
.collect::<HashMap<_, _>>()
} else {
+1 -1
View File
@@ -170,7 +170,7 @@ impl Session {
.turn_environments()
.map(|environment| {
(
environment.environment_id.clone(),
environment.selection.environment_id.clone(),
environment.cwd().clone(),
)
})
+1 -1
View File
@@ -3160,7 +3160,7 @@ impl Session {
.turn_environments()
.map(|environment| {
(
environment.environment_id.clone(),
environment.selection.environment_id.clone(),
turn_context.file_system_sandbox_context(
/*additional_permissions*/ None,
environment,
+39 -24
View File
@@ -6640,10 +6640,12 @@ async fn request_permissions_tool_resolves_relative_paths_against_selected_envir
.clone();
turn_context_mut.environments.environments[0] =
TurnEnvironmentState::Ready(TurnEnvironment::new(
"remote".to_string(),
TurnEnvironmentSelection {
environment_id: "remote".to_string(),
cwd: PathUri::from_abs_path(&environment_cwd),
workspace_roots: Vec::new(),
},
current_environment.environment,
PathUri::from_abs_path(&environment_cwd),
Vec::new(),
current_environment.shell,
current_environment.config,
));
@@ -7237,10 +7239,12 @@ async fn primary_environment_uses_first_turn_environment() {
.environments
.environments
.push(TurnEnvironmentState::Ready(TurnEnvironment::new(
"second".to_string(),
TurnEnvironmentSelection {
environment_id: "second".to_string(),
cwd: second_cwd_uri.clone(),
workspace_roots: Vec::new(),
},
Arc::clone(&first_environment.environment),
second_cwd_uri.clone(),
Vec::new(),
/*shell*/ None,
first_environment.config.clone(),
)));
@@ -7250,14 +7254,15 @@ async fn primary_environment_uses_first_turn_environment() {
.environments
.primary()
.expect("primary environment")
.selection
.environment_id,
first_environment.environment_id
first_environment.selection.environment_id
);
assert_eq!(
turn_context
.environments
.turn_environments()
.find(|environment| environment.environment_id == "second")
.find(|environment| environment.selection.environment_id == "second")
.expect("second environment")
.cwd(),
&second_cwd_uri
@@ -8645,10 +8650,12 @@ async fn conflicting_ready_environment_root_ids_keep_first_location() {
)
.expect("report environment ready");
turn_environments.push(TurnEnvironment::new(
environment_id.clone(),
TurnEnvironmentSelection {
environment_id: environment_id.clone(),
cwd: local_environment.cwd().clone(),
workspace_roots: local_environment.workspace_roots().to_vec(),
},
environment,
local_environment.cwd().clone(),
local_environment.workspace_roots().to_vec(),
local_environment.shell.clone(),
local_environment.config.clone(),
));
@@ -8710,7 +8717,7 @@ async fn capability_discovery_uses_environment_permission_profile() {
));
let expected_sandbox = turn_context
.file_system_sandbox_context(/*additional_permissions*/ None, &environment);
let environment_id = environment.environment_id.clone();
let environment_id = environment.selection.environment_id.clone();
turn_context.environments.environments[0] = TurnEnvironmentState::Ready(environment);
let discovery = session
@@ -8902,10 +8909,12 @@ async fn record_context_updates_emits_environment_item_for_cwd_changes() {
.clone();
current_context.environments.environments[0] =
TurnEnvironmentState::Ready(TurnEnvironment::new(
environment.environment_id,
TurnEnvironmentSelection {
environment_id: environment.selection.environment_id,
cwd: PathUri::from_abs_path(&cwd),
workspace_roots: Vec::new(),
},
environment.environment,
PathUri::from_abs_path(&cwd),
Vec::new(),
environment.shell,
environment.config,
));
@@ -8959,10 +8968,12 @@ async fn record_context_updates_use_environment_permission_profile_and_workspace
PermissionProfileSnapshot::legacy(PermissionProfile::workspace_write());
current_context.environments.environments[0] =
TurnEnvironmentState::Ready(TurnEnvironment::new(
environment.environment_id,
TurnEnvironmentSelection {
environment_id: environment.selection.environment_id,
cwd,
workspace_roots: vec![PathUri::from_abs_path(&workspace_root)],
},
environment.environment,
cwd,
vec![PathUri::from_abs_path(&workspace_root)],
environment.shell,
environment_config,
));
@@ -9032,10 +9043,12 @@ async fn record_context_updates_omits_environment_item_when_disabled() {
.clone();
current_context.environments.environments[0] =
TurnEnvironmentState::Ready(TurnEnvironment::new(
environment.environment_id,
TurnEnvironmentSelection {
environment_id: environment.selection.environment_id,
cwd: PathUri::from_abs_path(&test_path_buf("/new-repo").abs()),
workspace_roots: Vec::new(),
},
environment.environment,
PathUri::from_abs_path(&test_path_buf("/new-repo").abs()),
Vec::new(),
environment.shell,
environment.config,
));
@@ -9498,10 +9511,12 @@ async fn turn_context_item_stores_local_cwd() {
.clone();
let cwd = PathUri::parse("file:///C:/windows").expect("Windows cwd URI");
turn_context.environments.environments[0] = TurnEnvironmentState::Ready(TurnEnvironment::new(
"remote".to_string(),
TurnEnvironmentSelection {
environment_id: "remote".to_string(),
cwd,
workspace_roots: Vec::new(),
},
environment.environment,
cwd,
Vec::new(),
environment.shell,
environment.config,
));
+2 -2
View File
@@ -588,7 +588,7 @@ async fn turn_diff_display_roots(step_context: &StepContext) -> Vec<(String, Pat
.ok()
.flatten()
.unwrap_or_else(|| cwd.clone());
display_roots.push((turn_environment.environment_id.clone(), root));
display_roots.push((turn_environment.selection.environment_id.clone(), root));
}
display_roots
}
@@ -897,7 +897,7 @@ async fn build_extension_turn_input_items(
.turn_environments()
.enumerate()
.map(|(index, environment)| TurnInputEnvironment {
environment_id: environment.environment_id.clone(),
environment_id: environment.selection.environment_id.clone(),
cwd: environment.cwd().clone(),
is_primary: index == 0,
})
+10 -20
View File
@@ -42,10 +42,8 @@ pub(crate) struct TurnEnvironmentConfig {
#[derive(Clone)]
pub(crate) struct TurnEnvironment {
pub(crate) environment_id: String,
pub(crate) selection: TurnEnvironmentSelection,
pub(crate) environment: Arc<Environment>,
cwd: PathUri,
workspace_roots: Vec<PathUri>,
pub(crate) shell: Option<shell::Shell>,
pub(crate) config: TurnEnvironmentConfig,
pub(crate) shell_snapshot: ShellSnapshotTask,
@@ -53,18 +51,14 @@ pub(crate) struct TurnEnvironment {
impl TurnEnvironment {
pub(crate) fn new(
environment_id: String,
selection: TurnEnvironmentSelection,
environment: Arc<Environment>,
cwd: PathUri,
workspace_roots: Vec<PathUri>,
shell: Option<shell::Shell>,
config: TurnEnvironmentConfig,
) -> Self {
Self {
environment_id,
selection,
environment,
cwd,
workspace_roots,
shell,
config,
shell_snapshot: futures::future::ready(None).boxed().shared(),
@@ -72,7 +66,7 @@ impl TurnEnvironment {
}
pub(crate) fn shell_snapshot(&self, cwd: &AbsolutePathBuf) -> Option<AbsolutePathBuf> {
if self.cwd != PathUri::from_abs_path(cwd) {
if self.selection.cwd != PathUri::from_abs_path(cwd) {
return None;
}
self.shell_snapshot
@@ -82,11 +76,11 @@ impl TurnEnvironment {
}
pub(crate) fn cwd(&self) -> &PathUri {
&self.cwd
&self.selection.cwd
}
pub(crate) fn workspace_roots(&self) -> &[PathUri] {
&self.workspace_roots
&self.selection.workspace_roots
}
pub(crate) fn permission_profile(&self) -> &PermissionProfile {
@@ -109,21 +103,17 @@ impl TurnEnvironment {
}
pub(crate) fn selection(&self) -> TurnEnvironmentSelection {
TurnEnvironmentSelection {
environment_id: self.environment_id.clone(),
cwd: self.cwd.clone(),
workspace_roots: self.workspace_roots.clone(),
}
self.selection.clone()
}
}
impl std::fmt::Debug for TurnEnvironment {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TurnEnvironment")
.field("environment_id", &self.environment_id)
.field("environment_id", &self.selection.environment_id)
.field("environment", &self.environment)
.field("cwd", &self.cwd)
.field("workspace_roots", &self.workspace_roots)
.field("cwd", &self.selection.cwd)
.field("workspace_roots", &self.selection.workspace_roots)
.field("shell", &self.shell)
.field("config", &self.config)
.finish_non_exhaustive()
@@ -282,7 +282,7 @@ async fn effective_patch_permissions(
crate::tools::handlers::EffectiveAdditionalPermissions,
codex_protocol::permissions::FileSystemSandboxPolicy,
)> {
let environment_id = environment.environment_id.as_str();
let environment_id = environment.selection.environment_id.as_str();
let file_paths = file_paths_for_action(action);
let native_cwd = cwd.to_abs_path()?;
let granted_permissions = merge_permission_profiles(
@@ -565,7 +565,7 @@ async fn execute_verified_patch(
let emitter = ToolEmitter::apply_patch_for_environment(
changes.clone(),
apply.auto_approved,
turn_environment.environment_id.clone(),
turn_environment.selection.environment_id.clone(),
);
let event_ctx = ToolEventCtx::new(
tool_ctx.session.as_ref(),
@@ -150,7 +150,7 @@ async fn to_extension_call(invocation: &ToolInvocation) -> ExtensionToolCall {
};
let additional_permissions = apply_granted_turn_permissions(
invocation.session.as_ref(),
&environment.environment_id,
&environment.selection.environment_id,
native_cwd.as_path(),
SandboxPermissions::UseDefault,
/*additional_permissions*/ None,
@@ -161,7 +161,7 @@ async fn to_extension_call(invocation: &ToolInvocation) -> ExtensionToolCall {
.turn
.file_system_sandbox_context(additional_permissions, environment);
environments.push(ToolEnvironment {
environment_id: environment.environment_id.clone(),
environment_id: environment.selection.environment_id.clone(),
cwd: native_cwd,
file_system: environment.environment.get_filesystem(),
file_system_sandbox_context,
+1 -1
View File
@@ -175,7 +175,7 @@ fn resolve_tool_environment<'a>(
|environment_id| {
environments
.turn_environments()
.find(|environment| environment.environment_id == environment_id)
.find(|environment| environment.selection.environment_id == environment_id)
.map(Some)
.ok_or_else(|| {
FunctionCallError::RespondToModel(format!(
+1 -1
View File
@@ -95,7 +95,7 @@ async fn run_exec_like(args: RunExecLikeArgs) -> Result<FunctionToolOutput, Func
let requested_additional_permissions = additional_permissions.clone();
let effective_additional_permissions = apply_granted_turn_permissions(
session.as_ref(),
&turn_environment.environment_id,
&turn_environment.selection.environment_id,
exec_params.cwd.as_path(),
exec_params.sandbox_permissions,
additional_permissions,
@@ -125,7 +125,7 @@ impl ShellCommandHandler {
capture_policy: ExecCapturePolicy::ShellTool,
env,
network: turn_context.network.clone(),
network_environment_id: Some(turn_environment.environment_id.clone()),
network_environment_id: Some(turn_environment.selection.environment_id.clone()),
sandbox_permissions,
windows_sandbox_level: turn_context.windows_sandbox_level,
windows_sandbox_private_desktop: turn_context
@@ -25,6 +25,7 @@ use crate::tools::handlers::ShellCommandHandler;
use crate::tools::hook_names::HookToolName;
use crate::tools::registry::CoreToolRuntime;
use crate::turn_diff_tracker::TurnDiffTracker;
use codex_protocol::protocol::TurnEnvironmentSelection;
use codex_shell_command::is_safe_command::is_known_safe_command;
use codex_shell_command::powershell::try_find_powershell_executable_blocking;
use codex_shell_command::powershell::try_find_pwsh_executable_blocking;
@@ -103,7 +104,11 @@ async fn shell_command_handler_to_exec_params_uses_selected_environment() {
let expected_cwd = selected_cwd.join("subdir");
let active_permission_profile = ActivePermissionProfile::new("selected-profile");
let selected_environment = TurnEnvironment::new(
"selected-environment".to_string(),
TurnEnvironmentSelection {
environment_id: "selected-environment".to_string(),
cwd: PathUri::from_abs_path(&selected_cwd),
workspace_roots: Vec::new(),
},
Arc::clone(
&turn_context
.environments
@@ -111,8 +116,6 @@ async fn shell_command_handler_to_exec_params_uses_selected_environment() {
.expect("primary environment")
.environment,
),
PathUri::from_abs_path(&selected_cwd),
Vec::new(),
Some(selected_shell),
TurnEnvironmentConfig {
allow_login_shell: true,
@@ -197,7 +197,7 @@ impl ExecCommandHandler {
&hook_command,
&cwd,
native_cwd.as_ref(),
&turn_environment.environment_id,
&turn_environment.selection.environment_id,
)
.await;
let shell_mode =
@@ -217,13 +217,13 @@ impl ExecCommandHandler {
let Some(remote_shell) = turn_environment.shell.as_ref() else {
return Err(FunctionCallError::RespondToModel(format!(
"environment `{}` does not report a shell",
turn_environment.environment_id
turn_environment.selection.environment_id
)));
};
if detect_shell_type(Path::new(&requested_shell)) != Some(remote_shell.shell_type) {
return Err(FunctionCallError::RespondToModel(format!(
"environment `{}` only supports `{}`",
turn_environment.environment_id,
turn_environment.selection.environment_id,
remote_shell.name()
)));
}
@@ -258,7 +258,7 @@ impl ExecCommandHandler {
let permission_cwd = native_cwd.as_ref().unwrap_or(&turn.config.cwd);
let effective_additional_permissions = apply_granted_turn_permissions(
context.session.as_ref(),
&turn_environment.environment_id,
&turn_environment.selection.environment_id,
permission_cwd.as_path(),
sandbox_permissions,
additional_permissions,
@@ -285,11 +285,12 @@ mod tests {
.next()
.cloned()
.expect("default local turn environment");
let mut selection = current.selection;
selection.cwd = PathUri::from_abs_path(&cwd);
selection.workspace_roots.clear();
turn.environments.environments[0] = TurnEnvironmentState::Ready(TurnEnvironment::new(
current.environment_id,
selection,
current.environment,
PathUri::from_abs_path(&cwd),
Vec::new(),
current.shell,
current.config,
));
@@ -123,7 +123,7 @@ impl ToolExecutor<ToolInvocation> for WaitForEnvironmentHandler {
let already_ready = step_context
.environments
.turn_environments()
.any(|environment| environment.environment_id == environment_id);
.any(|environment| environment.selection.environment_id == environment_id);
if !already_ready {
let Some(environment) = step_context
.environments
+3 -3
View File
@@ -604,7 +604,7 @@ impl NetworkApprovalService {
active_turn
.as_ref()
.and_then(|(turn_context, _)| turn_context.environments.primary())
.map(|environment| environment.environment_id.clone())
.map(|environment| environment.selection.environment_id.clone())
}) else {
return NetworkDecision::deny(REASON_NOT_ALLOWED);
};
@@ -666,7 +666,7 @@ impl NetworkApprovalService {
turn_context
.environments
.turn_environments()
.find(|environment| environment.environment_id == environment_id)
.find(|environment| environment.selection.environment_id == environment_id)
.map(TurnEnvironment::permission_profile)
});
if !permission_profile.is_some_and(permission_profile_allows_network_approval_flow) {
@@ -705,7 +705,7 @@ impl NetworkApprovalService {
turn_context
.environments
.turn_environments()
.find(|environment| environment.environment_id == environment_id)
.find(|environment| environment.selection.environment_id == environment_id)
.and_then(|environment| environment.cwd().to_abs_path().ok())
.unwrap_or_else(|| {
#[allow(deprecated)]
@@ -74,7 +74,7 @@ impl ApplyPatchRuntime {
fn build_approval_action(req: &ApplyPatchRequest, call_id: &str) -> ApprovalAction {
ApprovalAction::ApplyPatch {
id: call_id.to_string(),
environment_id: req.turn_environment.environment_id.clone(),
environment_id: req.turn_environment.selection.environment_id.clone(),
cwd: req.action.cwd.clone(),
files: req.file_paths.clone(),
patch: req.action.patch.clone(),
@@ -8,6 +8,7 @@ use codex_protocol::models::FileSystemPermissions;
use codex_protocol::models::PermissionProfile;
use codex_protocol::permissions::NetworkSandboxPolicy;
use codex_protocol::protocol::GranularApprovalConfig;
use codex_protocol::protocol::TurnEnvironmentSelection;
use codex_sandboxing::SandboxManager;
use codex_sandboxing::SandboxType;
use codex_sandboxing::policy_transforms::effective_file_system_sandbox_policy;
@@ -18,10 +19,12 @@ use pretty_assertions::assert_eq;
use std::collections::HashMap;
fn test_turn_environment(environment_id: &str) -> crate::session::turn_context::TurnEnvironment {
crate::session::turn_context::TurnEnvironment::new(
environment_id.to_string(),
TurnEnvironmentSelection {
environment_id: environment_id.to_string(),
cwd: PathUri::from_abs_path(&std::env::temp_dir().abs()),
workspace_roots: Vec::new(),
},
std::sync::Arc::new(codex_exec_server::Environment::default_for_tests()),
PathUri::from_abs_path(&std::env::temp_dir().abs()),
Vec::new(),
/*shell*/ None,
TurnEnvironmentConfig {
allow_login_shell: true,
+3 -3
View File
@@ -129,7 +129,7 @@ impl Approvable<ShellRequest> for ShellRuntime {
) -> std::io::Result<ApprovalAction> {
Ok(ApprovalAction::Shell {
id: call_id.to_string(),
environment_id: req.turn_environment.environment_id.clone(),
environment_id: req.turn_environment.selection.environment_id.clone(),
command: req.command.clone(),
hook_command: req.hook_command.clone(),
cwd: PathUri::from_abs_path(&req.cwd),
@@ -186,7 +186,7 @@ impl ToolRuntime<ShellRequest, ExecToolCallOutput> for ShellRuntime {
tty: None,
},
command: req.hook_command.clone(),
environment_id: req.turn_environment.environment_id.clone(),
environment_id: req.turn_environment.selection.environment_id.clone(),
permission_profile: req.turn_environment.permission_profile().clone(),
})
}
@@ -307,7 +307,7 @@ impl ToolRuntime<ShellRequest, ExecToolCallOutput> for ShellRuntime {
command,
options,
managed_network,
Some(&req.turn_environment.environment_id),
Some(&req.turn_environment.selection.environment_id),
)
.map_err(ToolError::Codex)?;
execute_env(env, Self::stdout_stream(ctx))
@@ -149,7 +149,7 @@ pub(super) async fn try_run_zsh_fork(
command,
options,
managed_network_for_sandbox_permissions(req.network.as_ref(), req.sandbox_permissions),
Some(&req.turn_environment.environment_id),
Some(&req.turn_environment.selection.environment_id),
)
.map_err(ToolError::Codex)?;
let crate::sandboxing::ExecRequest {
@@ -244,7 +244,7 @@ pub(super) async fn try_run_zsh_fork(
session: Arc::clone(&ctx.session),
review_context: GuardianReviewContext::from(&ctx.step_context),
call_id: ctx.call_id.clone(),
environment_id: req.turn_environment.environment_id.clone(),
environment_id: req.turn_environment.selection.environment_id.clone(),
source: GuardianCommandSource::Shell,
tool_name: ctx.tool_name.clone(),
approval_policy: ctx.step_context.turn.approval_policy(),
@@ -331,7 +331,7 @@ pub(crate) async fn prepare_unified_exec_zsh_fork(
session: Arc::clone(&ctx.session),
review_context: GuardianReviewContext::from(&ctx.step_context),
call_id: ctx.call_id.clone(),
environment_id: req.turn_environment.environment_id.clone(),
environment_id: req.turn_environment.selection.environment_id.clone(),
source: GuardianCommandSource::UnifiedExec,
tool_name: ctx.tool_name.clone(),
approval_policy: ctx.step_context.turn.approval_policy(),
@@ -4,6 +4,7 @@ use crate::session::turn_context::TurnEnvironmentConfig;
use crate::tools::approvals::ApprovalCacheKey;
use codex_exec_server::Environment;
use codex_protocol::models::PermissionProfile;
use codex_protocol::protocol::TurnEnvironmentSelection;
use codex_utils_path_uri::PathUri;
use pretty_assertions::assert_eq;
use std::sync::Arc;
@@ -15,10 +16,12 @@ async fn approval_key_uses_path_uri_and_includes_environment_id() {
let mut request = ShellRequest {
command: vec!["echo".to_string(), "hello".to_string()],
turn_environment: TurnEnvironment::new(
"remote".to_string(),
TurnEnvironmentSelection {
environment_id: "remote".to_string(),
cwd: PathUri::from_abs_path(&cwd),
workspace_roots: Vec::new(),
},
Arc::new(Environment::default_for_tests()),
PathUri::from_abs_path(&cwd),
Vec::new(),
/*shell*/ None,
TurnEnvironmentConfig {
allow_login_shell: true,
@@ -61,7 +64,7 @@ async fn approval_key_uses_path_uri_and_includes_environment_id() {
additional_permissions: request.additional_permissions.clone(),
})]
);
request.turn_environment.environment_id = "other".to_string();
request.turn_environment.selection.environment_id = "other".to_string();
let other_key = runtime
.approval_action(&request, "call-1")
.expect("build approval action")
@@ -168,7 +168,7 @@ impl Approvable<UnifiedExecRequest> for UnifiedExecRuntime<'_> {
) -> std::io::Result<ApprovalAction> {
Ok(ApprovalAction::ExecCommand {
id: call_id.to_string(),
environment_id: req.turn_environment.environment_id.clone(),
environment_id: req.turn_environment.selection.environment_id.clone(),
command: req.command.clone(),
hook_command: req.hook_command.clone(),
cwd: req.cwd.clone(),
@@ -237,7 +237,7 @@ impl<'a> ToolRuntime<UnifiedExecRequest, UnifiedExecAttempt> for UnifiedExecRunt
tty: Some(req.tty),
},
command: req.hook_command.clone(),
environment_id: req.turn_environment.environment_id.clone(),
environment_id: req.turn_environment.selection.environment_id.clone(),
permission_profile: req.turn_environment.permission_profile().clone(),
})
}
@@ -325,12 +325,12 @@ impl<'a> ToolRuntime<UnifiedExecRequest, UnifiedExecAttempt> for UnifiedExecRunt
let prepared = network
.prepare_for_optional_environment(
env,
Some(&req.turn_environment.environment_id),
Some(&req.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.environment_id
req.turn_environment.selection.environment_id
))))
})?;
(prepared.env, Some(prepared.sandbox_context), None)
@@ -419,7 +419,7 @@ impl<'a> ToolRuntime<UnifiedExecRequest, UnifiedExecAttempt> for UnifiedExecRunt
command,
options,
managed_network,
Some(&req.turn_environment.environment_id),
Some(&req.turn_environment.selection.environment_id),
)
.map_err(ToolError::Codex)?;
exec_env.exec_server_env_config = req.exec_server_env_config.clone();
@@ -495,7 +495,7 @@ impl<'a> ToolRuntime<UnifiedExecRequest, UnifiedExecAttempt> for UnifiedExecRunt
attempt,
managed_network,
network_proxy_launch,
/*environment_id*/ Some(&req.turn_environment.environment_id),
/*environment_id*/ Some(&req.turn_environment.selection.environment_id),
req.exec_server_env_config.clone(),
windows_sandbox_proxy_settings_mode,
req.tty,
@@ -520,6 +520,7 @@ mod tests {
use codex_exec_server::Environment;
use codex_exec_server::LOCAL_ENVIRONMENT_ID;
use codex_protocol::models::PermissionProfile;
use codex_protocol::protocol::TurnEnvironmentSelection;
use codex_tools::ZshForkConfig;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_path_uri::PathUri;
@@ -530,10 +531,12 @@ mod tests {
fn test_turn_environment(cwd: PathUri) -> TurnEnvironment {
TurnEnvironment::new(
LOCAL_ENVIRONMENT_ID.to_string(),
TurnEnvironmentSelection {
environment_id: LOCAL_ENVIRONMENT_ID.to_string(),
cwd,
workspace_roots: Vec::new(),
},
Arc::new(Environment::default_for_tests()),
cwd,
Vec::new(),
/*shell*/ None,
TurnEnvironmentConfig {
allow_login_shell: true,
@@ -578,12 +581,12 @@ mod tests {
proposed_execpolicy_amendment: None,
},
);
request.turn_environment.environment_id = "remote".to_string();
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.environment_id = "other".to_string();
request.turn_environment.selection.environment_id = "other".to_string();
let other_key = runtime
.approval_action(&request, "call-1")
.expect("build approval action")
+8 -5
View File
@@ -24,6 +24,7 @@ use codex_protocol::openai_models::WebSearchToolType;
use codex_protocol::protocol::MultiAgentVersion;
use codex_protocol::protocol::SessionSource;
use codex_protocol::protocol::SubAgentSource;
use codex_protocol::protocol::TurnEnvironmentSelection;
use codex_tools::DiscoverablePluginInfo;
use codex_tools::DiscoverableTool;
use codex_tools::ResponsesApiNamespaceTool;
@@ -373,7 +374,7 @@ fn duplicate_primary_environment(turn: &mut TurnContext) {
.primary()
.expect("primary environment")
.clone();
second_environment.environment_id = "secondary".to_string();
second_environment.selection.environment_id = "secondary".to_string();
turn.environments
.environments
.push(TurnEnvironmentState::Ready(second_environment));
@@ -752,7 +753,7 @@ async fn shell_command_is_not_registered_without_a_single_local_environment() {
else {
panic!("primary environment should be ready");
};
environment.environment_id = "remote".to_string();
environment.selection.environment_id = "remote".to_string();
environment.environment = Arc::new(
codex_exec_server::Environment::create_for_tests(Some(
"ws://127.0.0.1:1/remote-exec-server".to_string(),
@@ -902,15 +903,17 @@ async fn zsh_fork_unified_exec_keeps_shell_parameter_when_remote_environment_ava
.environments
.push(TurnEnvironmentState::Ready(
crate::session::turn_context::TurnEnvironment::new(
"remote".to_string(),
TurnEnvironmentSelection {
environment_id: "remote".to_string(),
cwd: remote_cwd,
workspace_roots: Vec::new(),
},
Arc::new(
codex_exec_server::Environment::create_for_tests(Some(
"ws://127.0.0.1:1/remote-exec-server".to_string(),
))
.expect("remote test environment"),
),
remote_cwd,
Vec::new(),
/*shell*/ None,
crate::session::turn_context::TurnEnvironmentConfig {
allow_login_shell: true,