mirror of
https://github.com/openai/codex.git
synced 2026-11-13 14:46:14 +00:00
Honor environment-resolved workspace roots (#40912)
## What changed - Add effective workspace roots to `EnvironmentConfig` and use them when materializing permission profiles and filesystem context. - Preserve selection roots for thread-owned configurations while allowing a ready environment attachment to supply its resolved roots. - Propagate resolved roots to child environments and validate both command and `apply_patch` writes in owner-provided secondary workspaces. GitOrigin-RevId: 1832c168ce1fc37fa67efc2a472998ab4f448c70
This commit is contained in:
1 parent
5b92c2d2f5
commit
7625bd5665
29 files changed
+191
-67
No files matched your search
@@ -455,10 +455,10 @@ impl AgentControl {
|
|||||||
let owner_environment = parent_environments
|
let owner_environment = parent_environments
|
||||||
.turn_environments()
|
.turn_environments()
|
||||||
.find(|environment| {
|
.find(|environment| {
|
||||||
environment.selection.environment_id == *environment_id
|
let parent_selection = &environment.selection;
|
||||||
&& environment.cwd() == &selection.cwd
|
parent_selection.environment_id == selection.environment_id
|
||||||
&& environment.workspace_roots()
|
&& parent_selection.cwd == selection.cwd
|
||||||
== selection.workspace_roots.as_slice()
|
&& parent_selection.workspace_roots == selection.workspace_roots
|
||||||
})
|
})
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
invalid_environment("no longer matches a ready parent environment")
|
invalid_environment("no longer matches a ready parent environment")
|
||||||
@@ -493,8 +493,8 @@ impl AgentControl {
|
|||||||
let cwd = selection.cwd.to_abs_path().map_err(|_| {
|
let cwd = selection.cwd.to_abs_path().map_err(|_| {
|
||||||
invalid_environment("working directory is not a local absolute path")
|
invalid_environment("working directory is not a local absolute path")
|
||||||
})?;
|
})?;
|
||||||
let roots = selection
|
let roots = owner_environment
|
||||||
.workspace_roots
|
.workspace_roots()
|
||||||
.iter()
|
.iter()
|
||||||
.map(PathUri::to_abs_path)
|
.map(PathUri::to_abs_path)
|
||||||
.collect::<Result<Vec<_>, _>>()
|
.collect::<Result<Vec<_>, _>>()
|
||||||
|
|||||||
@@ -368,6 +368,7 @@ fn resolved_local_environments<const N: usize>(
|
|||||||
workspace_roots: Vec::new(),
|
workspace_roots: Vec::new(),
|
||||||
config: EnvironmentConfigState::Ready(EnvironmentConfig {
|
config: EnvironmentConfigState::Ready(EnvironmentConfig {
|
||||||
allow_login_shell: true,
|
allow_login_shell: true,
|
||||||
|
workspace_roots: Vec::new(),
|
||||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||||
windows_sandbox_private_desktop: true,
|
windows_sandbox_private_desktop: true,
|
||||||
use_legacy_landlock: false,
|
use_legacy_landlock: false,
|
||||||
|
|||||||
@@ -34,13 +34,9 @@ impl FileSystemContext {
|
|||||||
permission_profile: &PermissionProfile,
|
permission_profile: &PermissionProfile,
|
||||||
workspace_roots: &[PathUri],
|
workspace_roots: &[PathUri],
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let materialized_workspace_roots = workspace_roots
|
|
||||||
.iter()
|
|
||||||
.filter_map(|workspace_root| workspace_root.to_abs_path().ok())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
let permission_profile = permission_profile
|
let permission_profile = permission_profile
|
||||||
.clone()
|
.clone()
|
||||||
.materialize_project_roots_with_workspace_roots(&materialized_workspace_roots);
|
.materialize_project_roots_with_path_uris(workspace_roots);
|
||||||
let workspace_roots = workspace_roots
|
let workspace_roots = workspace_roots
|
||||||
.iter()
|
.iter()
|
||||||
.map(PathUri::inferred_native_path_string)
|
.map(PathUri::inferred_native_path_string)
|
||||||
|
|||||||
@@ -168,7 +168,10 @@ fn resolve_selection_config(
|
|||||||
) -> (TurnEnvironmentSelection, EnvironmentConfigOrigin) {
|
) -> (TurnEnvironmentSelection, EnvironmentConfigOrigin) {
|
||||||
let (config, origin) = match selection.config {
|
let (config, origin) = match selection.config {
|
||||||
EnvironmentConfigState::FromThread => (
|
EnvironmentConfigState::FromThread => (
|
||||||
EnvironmentConfigState::Ready(thread_config.clone()),
|
EnvironmentConfigState::Ready(thread_config_for_selection(
|
||||||
|
&selection.workspace_roots,
|
||||||
|
thread_config,
|
||||||
|
)),
|
||||||
EnvironmentConfigOrigin::Thread,
|
EnvironmentConfigOrigin::Thread,
|
||||||
),
|
),
|
||||||
config @ (EnvironmentConfigState::Ready(_)
|
config @ (EnvironmentConfigState::Ready(_)
|
||||||
@@ -179,10 +182,23 @@ fn resolve_selection_config(
|
|||||||
(selection, origin)
|
(selection, origin)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn thread_config_for_selection(
|
||||||
|
workspace_roots: &[PathUri],
|
||||||
|
thread_config: &EnvironmentConfig,
|
||||||
|
) -> EnvironmentConfig {
|
||||||
|
EnvironmentConfig {
|
||||||
|
workspace_roots: workspace_roots.to_vec(),
|
||||||
|
..thread_config.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl SelectedTurnEnvironment {
|
impl SelectedTurnEnvironment {
|
||||||
fn refresh_thread_config(&mut self, config: &EnvironmentConfig) {
|
fn refresh_thread_config(&mut self, config: &EnvironmentConfig) {
|
||||||
if self.config_origin == EnvironmentConfigOrigin::Thread {
|
if self.config_origin == EnvironmentConfigOrigin::Thread {
|
||||||
self.selection.config = EnvironmentConfigState::Ready(config.clone());
|
self.selection.config = EnvironmentConfigState::Ready(thread_config_for_selection(
|
||||||
|
&self.selection.workspace_roots,
|
||||||
|
config,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -916,6 +932,7 @@ mod tests {
|
|||||||
fn test_environment_config() -> EnvironmentConfig {
|
fn test_environment_config() -> EnvironmentConfig {
|
||||||
EnvironmentConfig {
|
EnvironmentConfig {
|
||||||
allow_login_shell: true,
|
allow_login_shell: true,
|
||||||
|
workspace_roots: Vec::new(),
|
||||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||||
windows_sandbox_private_desktop: true,
|
windows_sandbox_private_desktop: true,
|
||||||
use_legacy_landlock: false,
|
use_legacy_landlock: false,
|
||||||
@@ -1093,6 +1110,7 @@ url = "ws://127.0.0.1:8765"
|
|||||||
};
|
};
|
||||||
let expected_config = EnvironmentConfig {
|
let expected_config = EnvironmentConfig {
|
||||||
allow_login_shell: false,
|
allow_login_shell: false,
|
||||||
|
workspace_roots: Vec::new(),
|
||||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||||
windows_sandbox_private_desktop: true,
|
windows_sandbox_private_desktop: true,
|
||||||
use_legacy_landlock: false,
|
use_legacy_landlock: false,
|
||||||
@@ -1344,6 +1362,7 @@ url = "ws://127.0.0.1:8765"
|
|||||||
let cwd = AbsolutePathBuf::current_dir().expect("cwd");
|
let cwd = AbsolutePathBuf::current_dir().expect("cwd");
|
||||||
let expected_config = EnvironmentConfig {
|
let expected_config = EnvironmentConfig {
|
||||||
allow_login_shell: false,
|
allow_login_shell: false,
|
||||||
|
workspace_roots: Vec::new(),
|
||||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||||
windows_sandbox_private_desktop: true,
|
windows_sandbox_private_desktop: true,
|
||||||
use_legacy_landlock: false,
|
use_legacy_landlock: false,
|
||||||
@@ -1701,6 +1720,7 @@ url = "ws://127.0.0.1:8765"
|
|||||||
.expect("replacement environment");
|
.expect("replacement environment");
|
||||||
let child_config = EnvironmentConfig {
|
let child_config = EnvironmentConfig {
|
||||||
allow_login_shell: false,
|
allow_login_shell: false,
|
||||||
|
workspace_roots: Vec::new(),
|
||||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||||
windows_sandbox_private_desktop: true,
|
windows_sandbox_private_desktop: true,
|
||||||
use_legacy_landlock: false,
|
use_legacy_landlock: false,
|
||||||
@@ -1764,6 +1784,7 @@ url = "ws://127.0.0.1:8765"
|
|||||||
resolve_turn_environments(Arc::clone(&manager), std::slice::from_ref(&selection)).await;
|
resolve_turn_environments(Arc::clone(&manager), std::slice::from_ref(&selection)).await;
|
||||||
let parent_owner_config = EnvironmentConfig {
|
let parent_owner_config = EnvironmentConfig {
|
||||||
allow_login_shell: false,
|
allow_login_shell: false,
|
||||||
|
workspace_roots: selection.workspace_roots.clone(),
|
||||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||||
windows_sandbox_private_desktop: true,
|
windows_sandbox_private_desktop: true,
|
||||||
use_legacy_landlock: false,
|
use_legacy_landlock: false,
|
||||||
|
|||||||
@@ -649,7 +649,7 @@ async fn build_guardian_prompt_includes_parent_turn_denied_reads() -> anyhow::Re
|
|||||||
};
|
};
|
||||||
environment.config_mut().permission_profile =
|
environment.config_mut().permission_profile =
|
||||||
PermissionProfileSnapshot::legacy(environment_permission_profile);
|
PermissionProfileSnapshot::legacy(environment_permission_profile);
|
||||||
environment.selection.workspace_roots = vec![
|
environment.config_mut().workspace_roots = vec![
|
||||||
PathUri::from_abs_path(&workspace_root),
|
PathUri::from_abs_path(&workspace_root),
|
||||||
PathUri::from_abs_path(&second_workspace_root),
|
PathUri::from_abs_path(&second_workspace_root),
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -286,6 +286,7 @@ mod tests {
|
|||||||
};
|
};
|
||||||
primary.selection.cwd = PathUri::from_abs_path(&cwd);
|
primary.selection.cwd = PathUri::from_abs_path(&cwd);
|
||||||
primary.selection.workspace_roots.clear();
|
primary.selection.workspace_roots.clear();
|
||||||
|
primary.config_mut().workspace_roots.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
|||||||
@@ -1178,6 +1178,7 @@ async fn mcp_sandbox_cwd_uses_matching_server_environment_uri() -> anyhow::Resul
|
|||||||
workspace_roots: Vec::new(),
|
workspace_roots: Vec::new(),
|
||||||
config: EnvironmentConfigState::Ready(EnvironmentConfig {
|
config: EnvironmentConfigState::Ready(EnvironmentConfig {
|
||||||
allow_login_shell: true,
|
allow_login_shell: true,
|
||||||
|
workspace_roots: Vec::new(),
|
||||||
windows_sandbox_level: turn_context.windows_sandbox_level,
|
windows_sandbox_level: turn_context.windows_sandbox_level,
|
||||||
windows_sandbox_private_desktop: turn_context
|
windows_sandbox_private_desktop: turn_context
|
||||||
.config
|
.config
|
||||||
|
|||||||
@@ -142,6 +142,7 @@ impl SessionConfiguration {
|
|||||||
pub(super) fn inferred_environment_config(&self) -> EnvironmentConfig {
|
pub(super) fn inferred_environment_config(&self) -> EnvironmentConfig {
|
||||||
EnvironmentConfig {
|
EnvironmentConfig {
|
||||||
allow_login_shell: self.allow_login_shell,
|
allow_login_shell: self.allow_login_shell,
|
||||||
|
workspace_roots: Vec::new(),
|
||||||
permission_profile: self.permission_profile_state.snapshot(),
|
permission_profile: self.permission_profile_state.snapshot(),
|
||||||
shell_environment_policy: self.shell_environment_policy.clone(),
|
shell_environment_policy: self.shell_environment_policy.clone(),
|
||||||
windows_sandbox_level: self.windows_sandbox_level,
|
windows_sandbox_level: self.windows_sandbox_level,
|
||||||
@@ -167,6 +168,21 @@ impl SessionConfiguration {
|
|||||||
.materialize_project_roots_with_workspace_roots(&workspace_roots)
|
.materialize_project_roots_with_workspace_roots(&workspace_roots)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn effective_permission_profile(
|
||||||
|
&self,
|
||||||
|
environments: &[TurnEnvironmentSelection],
|
||||||
|
) -> PermissionProfile {
|
||||||
|
ThreadEnvironments::primary_config_for(environments)
|
||||||
|
.map(|config| {
|
||||||
|
config
|
||||||
|
.permission_profile
|
||||||
|
.permission_profile()
|
||||||
|
.clone()
|
||||||
|
.materialize_project_roots_with_path_uris(&config.workspace_roots)
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| self.materialized_permission_profile(environments))
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) fn active_permission_profile(&self) -> Option<ActivePermissionProfile> {
|
pub(super) fn active_permission_profile(&self) -> Option<ActivePermissionProfile> {
|
||||||
self.permission_profile_state.active_permission_profile()
|
self.permission_profile_state.active_permission_profile()
|
||||||
}
|
}
|
||||||
@@ -227,10 +243,7 @@ impl SessionConfiguration {
|
|||||||
service_tier: self.step_settings.service_tier.clone(),
|
service_tier: self.step_settings.service_tier.clone(),
|
||||||
approval_policy: self.step_settings.approval_policy.value(),
|
approval_policy: self.step_settings.approval_policy.value(),
|
||||||
approvals_reviewer: self.step_settings.approvals_reviewer,
|
approvals_reviewer: self.step_settings.approvals_reviewer,
|
||||||
permission_profile: permission_profile
|
permission_profile: self.effective_permission_profile(&environment_selections),
|
||||||
.permission_profile()
|
|
||||||
.clone()
|
|
||||||
.materialize_project_roots_with_workspace_roots(&workspace_roots),
|
|
||||||
active_permission_profile: permission_profile.active_permission_profile(),
|
active_permission_profile: permission_profile.active_permission_profile(),
|
||||||
environments: TurnEnvironmentSelections::new(
|
environments: TurnEnvironmentSelections::new(
|
||||||
self.legacy_fallback_cwd.clone(),
|
self.legacy_fallback_cwd.clone(),
|
||||||
@@ -314,13 +327,7 @@ impl SessionConfiguration {
|
|||||||
&self,
|
&self,
|
||||||
environments: &[TurnEnvironmentSelection],
|
environments: &[TurnEnvironmentSelection],
|
||||||
) -> StepSettingsConstraints<'_> {
|
) -> StepSettingsConstraints<'_> {
|
||||||
let permission_profile = ThreadEnvironments::primary_config_for(environments)
|
let permission_profile = self.effective_permission_profile(environments);
|
||||||
.map(|config| config.permission_profile.permission_profile())
|
|
||||||
.unwrap_or_else(|| self.permission_profile_state.permission_profile())
|
|
||||||
.clone()
|
|
||||||
.materialize_project_roots_with_workspace_roots(
|
|
||||||
&ThreadEnvironments::primary_workspace_roots_for(environments),
|
|
||||||
);
|
|
||||||
StepSettingsConstraints {
|
StepSettingsConstraints {
|
||||||
requirements: self
|
requirements: self
|
||||||
.original_config_do_not_use
|
.original_config_do_not_use
|
||||||
|
|||||||
@@ -9432,6 +9432,7 @@ async fn record_context_updates_use_environment_permission_profile_and_workspace
|
|||||||
let cwd = environment.cwd().clone();
|
let cwd = environment.cwd().clone();
|
||||||
let workspace_root = current_context.config.cwd.join("selected-workspace");
|
let workspace_root = current_context.config.cwd.join("selected-workspace");
|
||||||
let mut environment_config = environment.config().clone();
|
let mut environment_config = environment.config().clone();
|
||||||
|
environment_config.workspace_roots = vec![PathUri::from_abs_path(&workspace_root)];
|
||||||
environment_config.permission_profile =
|
environment_config.permission_profile =
|
||||||
PermissionProfileSnapshot::legacy(PermissionProfile::workspace_write());
|
PermissionProfileSnapshot::legacy(PermissionProfile::workspace_write());
|
||||||
current_context.environments.environments[0] =
|
current_context.environments.environments[0] =
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ impl TurnEnvironment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn workspace_roots(&self) -> &[PathUri] {
|
pub(crate) fn workspace_roots(&self) -> &[PathUri] {
|
||||||
&self.selection.workspace_roots
|
&self.config().workspace_roots
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn permission_profile(&self) -> &PermissionProfile {
|
pub(crate) fn permission_profile(&self) -> &PermissionProfile {
|
||||||
@@ -160,7 +160,7 @@ impl std::fmt::Debug for TurnEnvironment {
|
|||||||
.field("environment_id", &self.selection.environment_id)
|
.field("environment_id", &self.selection.environment_id)
|
||||||
.field("environment", &self.environment)
|
.field("environment", &self.environment)
|
||||||
.field("cwd", &self.selection.cwd)
|
.field("cwd", &self.selection.cwd)
|
||||||
.field("workspace_roots", &self.selection.workspace_roots)
|
.field("workspace_roots", &self.config().workspace_roots)
|
||||||
.field("temporary_directories", &self.temporary_directories)
|
.field("temporary_directories", &self.temporary_directories)
|
||||||
.field("shell", &self.shell)
|
.field("shell", &self.shell)
|
||||||
.field("config", self.config())
|
.field("config", self.config())
|
||||||
|
|||||||
@@ -1062,6 +1062,7 @@ async fn spawn_internal_session_preserves_parent_lineage_without_forking_history
|
|||||||
reviewer_environment.config =
|
reviewer_environment.config =
|
||||||
EnvironmentConfigState::Ready(codex_protocol::protocol::EnvironmentConfig {
|
EnvironmentConfigState::Ready(codex_protocol::protocol::EnvironmentConfig {
|
||||||
allow_login_shell: true,
|
allow_login_shell: true,
|
||||||
|
workspace_roots: reviewer_environment.workspace_roots.clone(),
|
||||||
permission_profile: config.permissions.permission_profile_state().snapshot(),
|
permission_profile: config.permissions.permission_profile_state().snapshot(),
|
||||||
shell_environment_policy: Default::default(),
|
shell_environment_policy: Default::default(),
|
||||||
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
|
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
|
||||||
|
|||||||
@@ -284,12 +284,13 @@ mod tests {
|
|||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
fn replace_primary_environment_cwd(turn: &mut crate::TurnContext, cwd: AbsolutePathBuf) {
|
fn replace_primary_environment_cwd(turn: &mut crate::TurnContext, cwd: AbsolutePathBuf) {
|
||||||
let current = turn
|
let mut current = turn
|
||||||
.environments
|
.environments
|
||||||
.turn_environments()
|
.turn_environments()
|
||||||
.next()
|
.next()
|
||||||
.cloned()
|
.cloned()
|
||||||
.expect("default local turn environment");
|
.expect("default local turn environment");
|
||||||
|
current.config_mut().workspace_roots.clear();
|
||||||
let mut selection = current.selection;
|
let mut selection = current.selection;
|
||||||
selection.cwd = PathUri::from_abs_path(&cwd);
|
selection.cwd = PathUri::from_abs_path(&cwd);
|
||||||
selection.workspace_roots.clear();
|
selection.workspace_roots.clear();
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ fn test_turn_environment(environment_id: &str) -> crate::session::turn_context::
|
|||||||
workspace_roots: Vec::new(),
|
workspace_roots: Vec::new(),
|
||||||
config: EnvironmentConfigState::Ready(EnvironmentConfig {
|
config: EnvironmentConfigState::Ready(EnvironmentConfig {
|
||||||
allow_login_shell: true,
|
allow_login_shell: true,
|
||||||
|
workspace_roots: Vec::new(),
|
||||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||||
windows_sandbox_private_desktop: true,
|
windows_sandbox_private_desktop: true,
|
||||||
use_legacy_landlock: false,
|
use_legacy_landlock: false,
|
||||||
|
|||||||
@@ -553,6 +553,7 @@ mod tests {
|
|||||||
workspace_roots: Vec::new(),
|
workspace_roots: Vec::new(),
|
||||||
config: EnvironmentConfigState::Ready(EnvironmentConfig {
|
config: EnvironmentConfigState::Ready(EnvironmentConfig {
|
||||||
allow_login_shell: true,
|
allow_login_shell: true,
|
||||||
|
workspace_roots: Vec::new(),
|
||||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||||
windows_sandbox_private_desktop: true,
|
windows_sandbox_private_desktop: true,
|
||||||
use_legacy_landlock: false,
|
use_legacy_landlock: false,
|
||||||
|
|||||||
@@ -637,7 +637,7 @@ async fn internal_guardian_sessions_require_managed_secondary_environments() {
|
|||||||
else {
|
else {
|
||||||
panic!("secondary environment should be ready");
|
panic!("secondary environment should be ready");
|
||||||
};
|
};
|
||||||
secondary.selection.workspace_roots = vec![secondary_workspace_root];
|
secondary.config_mut().workspace_roots = vec![secondary_workspace_root];
|
||||||
secondary.config_mut().permission_profile =
|
secondary.config_mut().permission_profile =
|
||||||
codex_protocol::models::PermissionProfileSnapshot::legacy(secondary_profile);
|
codex_protocol::models::PermissionProfileSnapshot::legacy(secondary_profile);
|
||||||
let turn = Arc::new(turn);
|
let turn = Arc::new(turn);
|
||||||
@@ -1086,6 +1086,7 @@ async fn zsh_fork_unified_exec_keeps_shell_parameter_when_remote_environment_ava
|
|||||||
config: EnvironmentConfigState::Ready(
|
config: EnvironmentConfigState::Ready(
|
||||||
codex_protocol::protocol::EnvironmentConfig {
|
codex_protocol::protocol::EnvironmentConfig {
|
||||||
allow_login_shell: true,
|
allow_login_shell: true,
|
||||||
|
workspace_roots: Vec::new(),
|
||||||
windows_sandbox_level: turn.windows_sandbox_level,
|
windows_sandbox_level: turn.windows_sandbox_level,
|
||||||
windows_sandbox_private_desktop: turn
|
windows_sandbox_private_desktop: turn
|
||||||
.config
|
.config
|
||||||
|
|||||||
@@ -310,8 +310,22 @@ async fn v2_residency_reload_preserves_inherited_environment_and_tools(
|
|||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
let mut owner_workspace_roots = child_environment.workspace_roots.clone();
|
||||||
|
let owner_workspace_root = test.workspace_path_uri("owner-only-root")?;
|
||||||
|
test.fs()
|
||||||
|
.create_directory(
|
||||||
|
&owner_workspace_root,
|
||||||
|
CreateDirectoryOptions {
|
||||||
|
recursive: true,
|
||||||
|
follow_symlinks: true,
|
||||||
|
},
|
||||||
|
/*sandbox*/ None,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
owner_workspace_roots.push(owner_workspace_root);
|
||||||
child_environment.config = EnvironmentConfigState::Ready(EnvironmentConfig {
|
child_environment.config = EnvironmentConfigState::Ready(EnvironmentConfig {
|
||||||
allow_login_shell: test.config.permissions.allow_login_shell,
|
allow_login_shell: test.config.permissions.allow_login_shell,
|
||||||
|
workspace_roots: owner_workspace_roots,
|
||||||
permission_profile: PermissionProfileSnapshot::legacy(child_permissions),
|
permission_profile: PermissionProfileSnapshot::legacy(child_permissions),
|
||||||
shell_environment_policy: Default::default(),
|
shell_environment_policy: Default::default(),
|
||||||
windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config),
|
windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config),
|
||||||
|
|||||||
@@ -716,6 +716,7 @@ async fn environment_command_restrictions_override_saved_prefix_approvals() -> R
|
|||||||
&selection,
|
&selection,
|
||||||
EnvironmentConfig {
|
EnvironmentConfig {
|
||||||
allow_login_shell: true,
|
allow_login_shell: true,
|
||||||
|
workspace_roots: selection.workspace_roots.clone(),
|
||||||
permission_profile: PermissionProfileSnapshot::legacy(PermissionProfile::Disabled),
|
permission_profile: PermissionProfileSnapshot::legacy(PermissionProfile::Disabled),
|
||||||
shell_environment_policy: Default::default(),
|
shell_environment_policy: Default::default(),
|
||||||
windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config),
|
windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config),
|
||||||
@@ -745,6 +746,7 @@ async fn environment_command_restrictions_override_saved_prefix_approvals() -> R
|
|||||||
&selection,
|
&selection,
|
||||||
EnvironmentConfig {
|
EnvironmentConfig {
|
||||||
allow_login_shell: true,
|
allow_login_shell: true,
|
||||||
|
workspace_roots: selection.workspace_roots.clone(),
|
||||||
permission_profile: PermissionProfileSnapshot::legacy(PermissionProfile::Disabled),
|
permission_profile: PermissionProfileSnapshot::legacy(PermissionProfile::Disabled),
|
||||||
shell_environment_policy: Default::default(),
|
shell_environment_policy: Default::default(),
|
||||||
windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config),
|
windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config),
|
||||||
@@ -853,6 +855,7 @@ async fn environment_command_policy_changes_invalidate_session_approvals() -> Re
|
|||||||
&selection,
|
&selection,
|
||||||
EnvironmentConfig {
|
EnvironmentConfig {
|
||||||
allow_login_shell: true,
|
allow_login_shell: true,
|
||||||
|
workspace_roots: selection.workspace_roots.clone(),
|
||||||
permission_profile: PermissionProfileSnapshot::legacy(
|
permission_profile: PermissionProfileSnapshot::legacy(
|
||||||
PermissionProfile::Disabled,
|
PermissionProfile::Disabled,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -824,6 +824,7 @@ async fn guardian_session_is_reused_for_consecutive_tool_reviews_without_prewarm
|
|||||||
let mut parent_environments = local_selections(test.config.cwd.clone());
|
let mut parent_environments = local_selections(test.config.cwd.clone());
|
||||||
let parent_environment_config = EnvironmentConfig {
|
let parent_environment_config = EnvironmentConfig {
|
||||||
allow_login_shell: test.config.permissions.allow_login_shell,
|
allow_login_shell: test.config.permissions.allow_login_shell,
|
||||||
|
workspace_roots: parent_environments.environments[0].workspace_roots.clone(),
|
||||||
permission_profile: PermissionProfileSnapshot::legacy(
|
permission_profile: PermissionProfileSnapshot::legacy(
|
||||||
test.config.permissions.permission_profile().clone(),
|
test.config.permissions.permission_profile().clone(),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -398,6 +398,7 @@ impl ExecutorStopHookFixture {
|
|||||||
&selection,
|
&selection,
|
||||||
EnvironmentConfig {
|
EnvironmentConfig {
|
||||||
allow_login_shell: false,
|
allow_login_shell: false,
|
||||||
|
workspace_roots: selection.workspace_roots.clone(),
|
||||||
permission_profile: PermissionProfileSnapshot::legacy(
|
permission_profile: PermissionProfileSnapshot::legacy(
|
||||||
self.test.config.permissions.permission_profile().clone(),
|
self.test.config.permissions.permission_profile().clone(),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -362,6 +362,7 @@ async fn skill_mcp_dependency_oauth_uses_configured_http_client() -> Result<()>
|
|||||||
let mut environments = local_selections(fixture.config.cwd.clone());
|
let mut environments = local_selections(fixture.config.cwd.clone());
|
||||||
environments.environments[0].config = EnvironmentConfigState::Ready(EnvironmentConfig {
|
environments.environments[0].config = EnvironmentConfigState::Ready(EnvironmentConfig {
|
||||||
allow_login_shell: fixture.config.permissions.allow_login_shell,
|
allow_login_shell: fixture.config.permissions.allow_login_shell,
|
||||||
|
workspace_roots: environments.environments[0].workspace_roots.clone(),
|
||||||
permission_profile: PermissionProfileSnapshot::legacy(PermissionProfile::Disabled),
|
permission_profile: PermissionProfileSnapshot::legacy(PermissionProfile::Disabled),
|
||||||
shell_environment_policy: Default::default(),
|
shell_environment_policy: Default::default(),
|
||||||
windows_sandbox_level: WindowsSandboxLevel::from_config(&fixture.config),
|
windows_sandbox_level: WindowsSandboxLevel::from_config(&fixture.config),
|
||||||
|
|||||||
@@ -489,6 +489,7 @@ async fn root_reconciliation_reuses_pending_apps_startup() -> Result<()> {
|
|||||||
&selection,
|
&selection,
|
||||||
EnvironmentConfig {
|
EnvironmentConfig {
|
||||||
allow_login_shell: false,
|
allow_login_shell: false,
|
||||||
|
workspace_roots: selection.workspace_roots.clone(),
|
||||||
permission_profile: PermissionProfileSnapshot::legacy(
|
permission_profile: PermissionProfileSnapshot::legacy(
|
||||||
test.config.permissions.permission_profile().clone(),
|
test.config.permissions.permission_profile().clone(),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -213,6 +213,7 @@ async fn model_visible_environment_context_preserves_foreign_workspace_roots() -
|
|||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}])
|
}])
|
||||||
.with_thread_settings(ThreadSettingsOverrides {
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
|
permission_profile: Some(PermissionProfile::workspace_write()),
|
||||||
environments: Some(TurnEnvironmentSelections::new(
|
environments: Some(TurnEnvironmentSelections::new(
|
||||||
test.config.cwd.clone(),
|
test.config.cwd.clone(),
|
||||||
vec![TurnEnvironmentSelection {
|
vec![TurnEnvironmentSelection {
|
||||||
@@ -241,6 +242,10 @@ async fn model_visible_environment_context_preserves_foreign_workspace_roots() -
|
|||||||
environment_context.contains("<workspace_roots><root>C:\\workspace</root>"),
|
environment_context.contains("<workspace_roots><root>C:\\workspace</root>"),
|
||||||
"foreign workspace root should remain visible to the model: {environment_context}"
|
"foreign workspace root should remain visible to the model: {environment_context}"
|
||||||
);
|
);
|
||||||
|
assert!(
|
||||||
|
environment_context.contains("<entry access=\"write\"><path>C:\\workspace</path>"),
|
||||||
|
"foreign workspace root should retain its permissions: {environment_context}"
|
||||||
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2020,6 +2020,7 @@ async fn owner_network_policy_follows_the_selected_remote_command() -> Result<()
|
|||||||
proxy_config.set_allowed_domains(vec![allowed_domain.to_string()]);
|
proxy_config.set_allowed_domains(vec![allowed_domain.to_string()]);
|
||||||
let owner_config = EnvironmentConfig {
|
let owner_config = EnvironmentConfig {
|
||||||
allow_login_shell: test.config.permissions.allow_login_shell,
|
allow_login_shell: test.config.permissions.allow_login_shell,
|
||||||
|
workspace_roots: remote.workspace_roots.clone(),
|
||||||
permission_profile: PermissionProfileSnapshot::legacy(if restricted {
|
permission_profile: PermissionProfileSnapshot::legacy(if restricted {
|
||||||
PermissionProfile::workspace_write()
|
PermissionProfile::workspace_write()
|
||||||
} else {
|
} else {
|
||||||
@@ -2042,6 +2043,7 @@ async fn owner_network_policy_follows_the_selected_remote_command() -> Result<()
|
|||||||
};
|
};
|
||||||
let mut primary = local(test.cwd.path().abs());
|
let mut primary = local(test.cwd.path().abs());
|
||||||
primary.config = EnvironmentConfigState::Ready(EnvironmentConfig {
|
primary.config = EnvironmentConfigState::Ready(EnvironmentConfig {
|
||||||
|
workspace_roots: primary.workspace_roots.clone(),
|
||||||
permission_profile: PermissionProfileSnapshot::legacy(PermissionProfile::Disabled),
|
permission_profile: PermissionProfileSnapshot::legacy(PermissionProfile::Disabled),
|
||||||
network_policy: None,
|
network_policy: None,
|
||||||
..owner_config.clone()
|
..owner_config.clone()
|
||||||
|
|||||||
@@ -557,6 +557,7 @@ async fn environment_permissions_follow_configuration_ownership() -> Result<()>
|
|||||||
vec![TurnEnvironmentSelection {
|
vec![TurnEnvironmentSelection {
|
||||||
config: EnvironmentConfigState::Ready(EnvironmentConfig {
|
config: EnvironmentConfigState::Ready(EnvironmentConfig {
|
||||||
allow_login_shell: test.config.permissions.allow_login_shell,
|
allow_login_shell: test.config.permissions.allow_login_shell,
|
||||||
|
workspace_roots: selection.workspace_roots.clone(),
|
||||||
permission_profile: owner_permission_profile,
|
permission_profile: owner_permission_profile,
|
||||||
shell_environment_policy: Default::default(),
|
shell_environment_policy: Default::default(),
|
||||||
windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config),
|
windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config),
|
||||||
@@ -1322,6 +1323,7 @@ async fn shared_executor_keeps_ready_capability_roots_scoped_to_each_attachment(
|
|||||||
EnvironmentConfigState::Pending,
|
EnvironmentConfigState::Pending,
|
||||||
EnvironmentConfigState::Ready(EnvironmentConfig {
|
EnvironmentConfigState::Ready(EnvironmentConfig {
|
||||||
allow_login_shell: false,
|
allow_login_shell: false,
|
||||||
|
workspace_roots: selection.workspace_roots.clone(),
|
||||||
permission_profile: permission_profile.clone(),
|
permission_profile: permission_profile.clone(),
|
||||||
shell_environment_policy: Default::default(),
|
shell_environment_policy: Default::default(),
|
||||||
windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config),
|
windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config),
|
||||||
@@ -1366,6 +1368,7 @@ async fn shared_executor_keeps_ready_capability_roots_scoped_to_each_attachment(
|
|||||||
environments: Some(vec![TurnEnvironmentSelection {
|
environments: Some(vec![TurnEnvironmentSelection {
|
||||||
config: EnvironmentConfigState::Ready(EnvironmentConfig {
|
config: EnvironmentConfigState::Ready(EnvironmentConfig {
|
||||||
allow_login_shell: true,
|
allow_login_shell: true,
|
||||||
|
workspace_roots: selection.workspace_roots.clone(),
|
||||||
permission_profile: permission_profile.clone(),
|
permission_profile: permission_profile.clone(),
|
||||||
shell_environment_policy: Default::default(),
|
shell_environment_policy: Default::default(),
|
||||||
windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config),
|
windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config),
|
||||||
@@ -1394,6 +1397,7 @@ async fn shared_executor_keeps_ready_capability_roots_scoped_to_each_attachment(
|
|||||||
vec![TurnEnvironmentSelection {
|
vec![TurnEnvironmentSelection {
|
||||||
config: EnvironmentConfigState::Ready(EnvironmentConfig {
|
config: EnvironmentConfigState::Ready(EnvironmentConfig {
|
||||||
allow_login_shell: false,
|
allow_login_shell: false,
|
||||||
|
workspace_roots: selection.workspace_roots.clone(),
|
||||||
permission_profile: permission_profile.clone(),
|
permission_profile: permission_profile.clone(),
|
||||||
shell_environment_policy: Default::default(),
|
shell_environment_policy: Default::default(),
|
||||||
windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config),
|
windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config),
|
||||||
@@ -1461,6 +1465,7 @@ async fn shared_executor_keeps_ready_capability_roots_scoped_to_each_attachment(
|
|||||||
vec![TurnEnvironmentSelection {
|
vec![TurnEnvironmentSelection {
|
||||||
config: EnvironmentConfigState::Ready(EnvironmentConfig {
|
config: EnvironmentConfigState::Ready(EnvironmentConfig {
|
||||||
allow_login_shell: false,
|
allow_login_shell: false,
|
||||||
|
workspace_roots: selection.workspace_roots.clone(),
|
||||||
permission_profile: permission_profile.clone(),
|
permission_profile: permission_profile.clone(),
|
||||||
shell_environment_policy: Default::default(),
|
shell_environment_policy: Default::default(),
|
||||||
windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config),
|
windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config),
|
||||||
@@ -1536,6 +1541,7 @@ async fn owner_network_policy_rejects_unsupported_environment_authority() -> Res
|
|||||||
.context("thread should select its executor environment")?;
|
.context("thread should select its executor environment")?;
|
||||||
let owner_config = EnvironmentConfig {
|
let owner_config = EnvironmentConfig {
|
||||||
allow_login_shell: test.config.permissions.allow_login_shell,
|
allow_login_shell: test.config.permissions.allow_login_shell,
|
||||||
|
workspace_roots: selection.workspace_roots.clone(),
|
||||||
permission_profile: PermissionProfileSnapshot::legacy(PermissionProfile::Disabled),
|
permission_profile: PermissionProfileSnapshot::legacy(PermissionProfile::Disabled),
|
||||||
shell_environment_policy: test.config.permissions.shell_environment_policy.clone(),
|
shell_environment_policy: test.config.permissions.shell_environment_policy.clone(),
|
||||||
windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config),
|
windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config),
|
||||||
@@ -1614,6 +1620,7 @@ async fn pending_attachment_installs_configuration_before_waiting_turn_resumes()
|
|||||||
config: EnvironmentConfigState::Pending,
|
config: EnvironmentConfigState::Pending,
|
||||||
..selection.clone()
|
..selection.clone()
|
||||||
};
|
};
|
||||||
|
let owner_workspace_root = selection.cwd.join("owner-workspace")?;
|
||||||
let root = |id: &str| SelectedCapabilityRoot {
|
let root = |id: &str| SelectedCapabilityRoot {
|
||||||
id: id.to_string(),
|
id: id.to_string(),
|
||||||
location: CapabilityRootLocation::Environment {
|
location: CapabilityRootLocation::Environment {
|
||||||
@@ -1623,6 +1630,7 @@ async fn pending_attachment_installs_configuration_before_waiting_turn_resumes()
|
|||||||
};
|
};
|
||||||
let owner_config = |id: &str, allow_login_shell: bool| EnvironmentConfig {
|
let owner_config = |id: &str, allow_login_shell: bool| EnvironmentConfig {
|
||||||
allow_login_shell,
|
allow_login_shell,
|
||||||
|
workspace_roots: vec![selection.cwd.clone(), owner_workspace_root.clone()],
|
||||||
permission_profile: PermissionProfileSnapshot::legacy(PermissionProfile::read_only()),
|
permission_profile: PermissionProfileSnapshot::legacy(PermissionProfile::read_only()),
|
||||||
shell_environment_policy: Default::default(),
|
shell_environment_policy: Default::default(),
|
||||||
windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config),
|
windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config),
|
||||||
@@ -1642,6 +1650,7 @@ async fn pending_attachment_installs_configuration_before_waiting_turn_resumes()
|
|||||||
let waiting = timeout(Duration::from_secs(5), start_pending_thread())
|
let waiting = timeout(Duration::from_secs(5), start_pending_thread())
|
||||||
.await
|
.await
|
||||||
.context("pending thread startup should not block")??;
|
.context("pending thread startup should not block")??;
|
||||||
|
let requested_workspace_roots = waiting.thread.config_snapshot().await.workspace_roots;
|
||||||
let independent = start_pending_thread().await?;
|
let independent = start_pending_thread().await?;
|
||||||
let failed = start_pending_thread().await?;
|
let failed = start_pending_thread().await?;
|
||||||
|
|
||||||
@@ -1757,22 +1766,19 @@ async fn pending_attachment_installs_configuration_before_waiting_turn_resumes()
|
|||||||
config: EnvironmentConfigState::Ready(waiting_config.clone()),
|
config: EnvironmentConfigState::Ready(waiting_config.clone()),
|
||||||
..pending_selection.clone()
|
..pending_selection.clone()
|
||||||
};
|
};
|
||||||
submit_thread_settings(
|
waiting
|
||||||
&waiting.thread,
|
.thread
|
||||||
ThreadSettingsOverrides {
|
.environment_ready(&pending_selection, waiting_config)
|
||||||
environments: Some(TurnEnvironmentSelections::new(
|
.await?;
|
||||||
test.config.cwd.clone(),
|
|
||||||
vec![ready_selection.clone()],
|
|
||||||
)),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
wait_for_response_request_count(&response_mock, /*expected_count*/ 2).await;
|
wait_for_response_request_count(&response_mock, /*expected_count*/ 2).await;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
waiting.thread.environment_selections().await,
|
waiting.thread.environment_selections().await,
|
||||||
vec![ready_selection]
|
vec![ready_selection]
|
||||||
);
|
);
|
||||||
|
assert_eq!(
|
||||||
|
waiting.thread.config_snapshot().await.workspace_roots,
|
||||||
|
requested_workspace_roots
|
||||||
|
);
|
||||||
|
|
||||||
let ready_request = response_mock
|
let ready_request = response_mock
|
||||||
.last_request()
|
.last_request()
|
||||||
@@ -1800,6 +1806,13 @@ async fn pending_attachment_installs_configuration_before_waiting_turn_resumes()
|
|||||||
.rfind(|text| text.contains("<ready_capability_roots>")),
|
.rfind(|text| text.contains("<ready_capability_roots>")),
|
||||||
Some("<ready_capability_roots>waiting-root</ready_capability_roots>".to_string())
|
Some("<ready_capability_roots>waiting-root</ready_capability_roots>".to_string())
|
||||||
);
|
);
|
||||||
|
assert!(
|
||||||
|
ready_request
|
||||||
|
.message_input_texts("user")
|
||||||
|
.iter()
|
||||||
|
.any(|text| text.contains(&owner_workspace_root.inferred_native_path_string())),
|
||||||
|
"waiting turn should observe owner-resolved workspace roots"
|
||||||
|
);
|
||||||
|
|
||||||
let recovered_selection = TurnEnvironmentSelection {
|
let recovered_selection = TurnEnvironmentSelection {
|
||||||
config: EnvironmentConfigState::Ready(owner_config(
|
config: EnvironmentConfigState::Ready(owner_config(
|
||||||
@@ -2276,6 +2289,7 @@ async fn deferred_executor_spawn_agent_inherits_ready_step_environments(
|
|||||||
workspace_roots: vec![PathUri::from_abs_path(&test.config.cwd)],
|
workspace_roots: vec![PathUri::from_abs_path(&test.config.cwd)],
|
||||||
config: EnvironmentConfigState::Ready(EnvironmentConfig {
|
config: EnvironmentConfigState::Ready(EnvironmentConfig {
|
||||||
allow_login_shell: test.config.permissions.allow_login_shell,
|
allow_login_shell: test.config.permissions.allow_login_shell,
|
||||||
|
workspace_roots: vec![PathUri::from_abs_path(&test.config.cwd)],
|
||||||
permission_profile: PermissionProfileSnapshot::active_with_profile_workspace_roots(
|
permission_profile: PermissionProfileSnapshot::active_with_profile_workspace_roots(
|
||||||
PermissionProfile::read_only(),
|
PermissionProfile::read_only(),
|
||||||
owner_active_profile.clone(),
|
owner_active_profile.clone(),
|
||||||
|
|||||||
@@ -801,6 +801,7 @@ async fn environment_mcp_policy_filters_runtime_config_and_model_tools(
|
|||||||
&selection,
|
&selection,
|
||||||
EnvironmentConfig {
|
EnvironmentConfig {
|
||||||
allow_login_shell: true,
|
allow_login_shell: true,
|
||||||
|
workspace_roots: selection.workspace_roots.clone(),
|
||||||
permission_profile: PermissionProfileSnapshot::legacy(
|
permission_profile: PermissionProfileSnapshot::legacy(
|
||||||
fixture.config.permissions.permission_profile().clone(),
|
fixture.config.permissions.permission_profile().clone(),
|
||||||
),
|
),
|
||||||
@@ -1743,14 +1744,11 @@ async fn stdio_mcp_tool_call_includes_sandbox_state_meta(
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.find(|selection| selection.environment_id == remote_aware_environment_id())
|
.find(|selection| selection.environment_id == remote_aware_environment_id())
|
||||||
.context("thread should select the MCP server's executor environment")?;
|
.context("thread should select the MCP server's executor environment")?;
|
||||||
let selection = TurnEnvironmentSelection {
|
let workspace_roots = vec![PathUri::parse(if cfg!(windows) {
|
||||||
workspace_roots: vec![PathUri::parse(if cfg!(windows) {
|
"file:///foreign/workspace"
|
||||||
"file:///foreign/workspace"
|
} else {
|
||||||
} else {
|
"file:///C:/workspace"
|
||||||
"file:///C:/workspace"
|
})?];
|
||||||
})?],
|
|
||||||
..selection
|
|
||||||
};
|
|
||||||
submit_thread_settings(
|
submit_thread_settings(
|
||||||
&fixture.codex,
|
&fixture.codex,
|
||||||
ThreadSettingsOverrides {
|
ThreadSettingsOverrides {
|
||||||
@@ -1765,13 +1763,13 @@ async fn stdio_mcp_tool_call_includes_sandbox_state_meta(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
let workspace_roots = selection.workspace_roots.clone();
|
|
||||||
fixture
|
fixture
|
||||||
.codex
|
.codex
|
||||||
.environment_ready(
|
.environment_ready(
|
||||||
&selection,
|
&selection,
|
||||||
EnvironmentConfig {
|
EnvironmentConfig {
|
||||||
allow_login_shell: fixture.config.permissions.allow_login_shell,
|
allow_login_shell: fixture.config.permissions.allow_login_shell,
|
||||||
|
workspace_roots: workspace_roots.clone(),
|
||||||
permission_profile: PermissionProfileSnapshot::legacy(
|
permission_profile: PermissionProfileSnapshot::legacy(
|
||||||
owner_permission_profile.clone(),
|
owner_permission_profile.clone(),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -391,6 +391,7 @@ async fn exec_command_uses_installed_environment_shell_policy_with_explicit_over
|
|||||||
&selection,
|
&selection,
|
||||||
EnvironmentConfig {
|
EnvironmentConfig {
|
||||||
allow_login_shell: true,
|
allow_login_shell: true,
|
||||||
|
workspace_roots: selection.workspace_roots.clone(),
|
||||||
permission_profile: PermissionProfileSnapshot::legacy(PermissionProfile::Disabled),
|
permission_profile: PermissionProfileSnapshot::legacy(PermissionProfile::Disabled),
|
||||||
shell_environment_policy: ShellEnvironmentPolicy {
|
shell_environment_policy: ShellEnvironmentPolicy {
|
||||||
inherit: ShellEnvironmentPolicyInherit::None,
|
inherit: ShellEnvironmentPolicyInherit::None,
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use codex_core::EnvironmentConfig;
|
||||||
|
use codex_core::windows_sandbox::WindowsSandboxLevelExt;
|
||||||
use codex_exec_server::CreateDirectoryOptions;
|
use codex_exec_server::CreateDirectoryOptions;
|
||||||
use codex_exec_server::RemoveOptions;
|
use codex_exec_server::RemoveOptions;
|
||||||
|
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||||
use codex_protocol::models::PermissionProfile;
|
use codex_protocol::models::PermissionProfile;
|
||||||
|
use codex_protocol::models::PermissionProfileSnapshot;
|
||||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||||
use codex_utils_path_uri::PathUri;
|
use codex_utils_path_uri::PathUri;
|
||||||
@@ -23,6 +27,7 @@ use core_test_support::test_codex::TestCodex;
|
|||||||
use core_test_support::test_codex::test_codex;
|
use core_test_support::test_codex::test_codex;
|
||||||
use core_test_support::test_target_os;
|
use core_test_support::test_target_os;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
use test_case::test_case;
|
||||||
use wiremock::MockServer;
|
use wiremock::MockServer;
|
||||||
|
|
||||||
const PATCH_CALL_ID: &str = "workspace-root-patch";
|
const PATCH_CALL_ID: &str = "workspace-root-patch";
|
||||||
@@ -194,9 +199,14 @@ async fn workspace_roots_allow_file_and_command_writes() -> Result<()> {
|
|||||||
remove_files(&test, &[&patch_path, &command_path]).await
|
remove_files(&test, &[&patch_path, &command_path]).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test_case(false; "thread-owned secondary root")]
|
||||||
|
#[test_case(true; "owner-resolved secondary root")]
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
async fn workspace_roots_allow_apply_patch_in_secondary_root() -> Result<()> {
|
async fn workspace_roots_allow_file_and_command_writes_in_secondary_root(
|
||||||
|
owner_resolved_roots: bool,
|
||||||
|
) -> Result<()> {
|
||||||
const SECONDARY_ROOT_NAME: &str = "secondary-workspace-root";
|
const SECONDARY_ROOT_NAME: &str = "secondary-workspace-root";
|
||||||
|
const COMMAND_CONTENTS: &str = "secondary root command";
|
||||||
|
|
||||||
skip_if_wine_exec!(
|
skip_if_wine_exec!(
|
||||||
Ok(()),
|
Ok(()),
|
||||||
@@ -205,7 +215,7 @@ async fn workspace_roots_allow_apply_patch_in_secondary_root() -> Result<()> {
|
|||||||
|
|
||||||
let server = start_mock_server().await;
|
let server = start_mock_server().await;
|
||||||
let mut builder = test_codex()
|
let mut builder = test_codex()
|
||||||
.with_config(|config| {
|
.with_config(move |config| {
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
{
|
{
|
||||||
config.cwd = dunce::canonicalize(config.cwd.as_path())
|
config.cwd = dunce::canonicalize(config.cwd.as_path())
|
||||||
@@ -220,7 +230,10 @@ async fn workspace_roots_allow_apply_patch_in_secondary_root() -> Result<()> {
|
|||||||
&config.cwd,
|
&config.cwd,
|
||||||
SECONDARY_ROOT_NAME,
|
SECONDARY_ROOT_NAME,
|
||||||
));
|
));
|
||||||
config.workspace_roots = vec![config.cwd.clone(), secondary_root];
|
config.workspace_roots = vec![config.cwd.clone()];
|
||||||
|
if !owner_resolved_roots {
|
||||||
|
config.workspace_roots.push(secondary_root);
|
||||||
|
}
|
||||||
config.set_windows_sandbox_enabled(/*value*/ true);
|
config.set_windows_sandbox_enabled(/*value*/ true);
|
||||||
})
|
})
|
||||||
.with_workspace_setup(|cwd, fs| async move {
|
.with_workspace_setup(|cwd, fs| async move {
|
||||||
@@ -245,31 +258,54 @@ async fn workspace_roots_allow_apply_patch_in_secondary_root() -> Result<()> {
|
|||||||
.parent()
|
.parent()
|
||||||
.context("workspace should have a parent")?
|
.context("workspace should have a parent")?
|
||||||
.join(&secondary_root_name)?;
|
.join(&secondary_root_name)?;
|
||||||
|
if owner_resolved_roots {
|
||||||
|
let selection = test
|
||||||
|
.codex
|
||||||
|
.environment_selections()
|
||||||
|
.await
|
||||||
|
.into_iter()
|
||||||
|
.next()
|
||||||
|
.context("thread should select its executor environment")?;
|
||||||
|
test.codex
|
||||||
|
.environment_ready(
|
||||||
|
&selection,
|
||||||
|
EnvironmentConfig {
|
||||||
|
allow_login_shell: test.config.permissions.allow_login_shell,
|
||||||
|
workspace_roots: vec![selection.cwd.clone(), secondary_root.clone()],
|
||||||
|
permission_profile: PermissionProfileSnapshot::legacy(
|
||||||
|
workspace_roots_profile(),
|
||||||
|
),
|
||||||
|
shell_environment_policy: Default::default(),
|
||||||
|
windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config),
|
||||||
|
windows_sandbox_private_desktop: test
|
||||||
|
.config
|
||||||
|
.permissions
|
||||||
|
.windows_sandbox_private_desktop,
|
||||||
|
use_legacy_landlock: test.config.features.use_legacy_landlock(),
|
||||||
|
exec_policy: None,
|
||||||
|
mcp_policy: None,
|
||||||
|
network_policy: None,
|
||||||
|
selected_capability_roots: Vec::new(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
let patch = format!(
|
let patch = format!(
|
||||||
"*** Begin Patch\n*** Add File: ../{secondary_root_name}/secondary-root-patch.txt\n+secondary root\n*** End Patch\n"
|
"*** Begin Patch\n*** Add File: ../{secondary_root_name}/secondary-root-patch.txt\n+secondary root\n*** End Patch\n"
|
||||||
);
|
);
|
||||||
let response_mock = mount_sse_sequence(
|
let response_mock = mount_patch_and_command_calls(
|
||||||
&server,
|
&server,
|
||||||
vec![
|
&patch,
|
||||||
sse(vec![
|
&format!("../{secondary_root_name}/secondary-root-command.txt"),
|
||||||
ev_response_created("resp-1"),
|
COMMAND_CONTENTS,
|
||||||
ev_apply_patch_custom_tool_call(PATCH_CALL_ID, &patch),
|
|
||||||
ev_completed("resp-1"),
|
|
||||||
]),
|
|
||||||
sse(vec![
|
|
||||||
ev_response_created("resp-2"),
|
|
||||||
ev_assistant_message("msg-1", "done"),
|
|
||||||
ev_completed("resp-2"),
|
|
||||||
]),
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
.await;
|
.await?;
|
||||||
|
|
||||||
submit_workspace_turn(&test, "write in the secondary workspace root").await?;
|
submit_workspace_turn(&test, "write in the secondary workspace root").await?;
|
||||||
|
|
||||||
let request = response_mock
|
let request = response_mock
|
||||||
.last_request()
|
.last_request()
|
||||||
.context("model should receive the apply_patch result")?;
|
.context("model should receive the apply_patch and command results")?;
|
||||||
let (patch_output, patch_success) = request
|
let (patch_output, patch_success) = request
|
||||||
.custom_tool_call_output_content_and_success(PATCH_CALL_ID)
|
.custom_tool_call_output_content_and_success(PATCH_CALL_ID)
|
||||||
.context("patch result should be present")?;
|
.context("patch result should be present")?;
|
||||||
@@ -282,6 +318,15 @@ async fn workspace_roots_allow_apply_patch_in_secondary_root() -> Result<()> {
|
|||||||
);
|
);
|
||||||
let patched_file = secondary_root.join("secondary-root-patch.txt")?;
|
let patched_file = secondary_root.join("secondary-root-patch.txt")?;
|
||||||
assert_eq!(read_file(&test, &patched_file).await?, "secondary root\n");
|
assert_eq!(read_file(&test, &patched_file).await?, "secondary root\n");
|
||||||
|
let (_, command_success) = request
|
||||||
|
.function_call_output_content_and_success(COMMAND_CALL_ID)
|
||||||
|
.context("command result should be present")?;
|
||||||
|
assert_ne!(command_success, Some(false));
|
||||||
|
let command_file = secondary_root.join("secondary-root-command.txt")?;
|
||||||
|
assert_eq!(
|
||||||
|
read_file(&test, &command_file).await?.trim_end(),
|
||||||
|
COMMAND_CONTENTS
|
||||||
|
);
|
||||||
|
|
||||||
test.fs()
|
test.fs()
|
||||||
.remove(
|
.remove(
|
||||||
|
|||||||
@@ -153,6 +153,7 @@ async fn read_only_permissions_preserve_parent_environments_and_denied_reads() -
|
|||||||
.expect("parent should have an execution environment");
|
.expect("parent should have an execution environment");
|
||||||
environment.config = EnvironmentConfigState::Ready(EnvironmentConfig {
|
environment.config = EnvironmentConfigState::Ready(EnvironmentConfig {
|
||||||
allow_login_shell: true,
|
allow_login_shell: true,
|
||||||
|
workspace_roots: environment.workspace_roots.clone(),
|
||||||
permission_profile: PermissionProfileSnapshot::legacy(parent_profile.clone()),
|
permission_profile: PermissionProfileSnapshot::legacy(parent_profile.clone()),
|
||||||
shell_environment_policy: Default::default(),
|
shell_environment_policy: Default::default(),
|
||||||
windows_sandbox_level: WindowsSandboxLevel::from_config(&parent_config),
|
windows_sandbox_level: WindowsSandboxLevel::from_config(&parent_config),
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ use crate::mcp_policy::EnvironmentMcpPolicy;
|
|||||||
use crate::models::PermissionProfileSnapshot;
|
use crate::models::PermissionProfileSnapshot;
|
||||||
use codex_execpolicy::RequirementsExecPolicy;
|
use codex_execpolicy::RequirementsExecPolicy;
|
||||||
use codex_network_proxy::EnvironmentNetworkPolicy;
|
use codex_network_proxy::EnvironmentNetworkPolicy;
|
||||||
|
use codex_utils_path_uri::PathUri;
|
||||||
|
|
||||||
/// Configuration supplied for a thread's selected environment.
|
/// Configuration supplied for a thread's selected environment.
|
||||||
#[allow(clippy::large_enum_variant)]
|
#[allow(clippy::large_enum_variant)]
|
||||||
@@ -25,6 +26,8 @@ pub enum EnvironmentConfigState {
|
|||||||
pub struct EnvironmentConfig {
|
pub struct EnvironmentConfig {
|
||||||
/// Whether shell tools may start login shells in this environment.
|
/// Whether shell tools may start login shells in this environment.
|
||||||
pub allow_login_shell: bool,
|
pub allow_login_shell: bool,
|
||||||
|
/// Effective workspace roots resolved for this environment attachment.
|
||||||
|
pub workspace_roots: Vec<PathUri>,
|
||||||
/// Resolved permissions for this thread's environment attachment.
|
/// Resolved permissions for this thread's environment attachment.
|
||||||
pub permission_profile: PermissionProfileSnapshot,
|
pub permission_profile: PermissionProfileSnapshot,
|
||||||
/// Controls which environment variables shell commands may inherit.
|
/// Controls which environment variables shell commands may inherit.
|
||||||
@@ -50,6 +53,7 @@ impl std::fmt::Debug for EnvironmentConfig {
|
|||||||
formatter
|
formatter
|
||||||
.debug_struct("EnvironmentConfig")
|
.debug_struct("EnvironmentConfig")
|
||||||
.field("allow_login_shell", &self.allow_login_shell)
|
.field("allow_login_shell", &self.allow_login_shell)
|
||||||
|
.field("workspace_roots", &self.workspace_roots)
|
||||||
.field("permission_profile", &self.permission_profile)
|
.field("permission_profile", &self.permission_profile)
|
||||||
.field("shell_environment_policy", &"<redacted>")
|
.field("shell_environment_policy", &"<redacted>")
|
||||||
.field("windows_sandbox_level", &self.windows_sandbox_level)
|
.field("windows_sandbox_level", &self.windows_sandbox_level)
|
||||||
|
|||||||
Reference in new issue
Block a user