mirror of
https://github.com/openai/codex.git
synced 2026-10-18 17:18:52 +00:00
Carry environment config in turn selections (#38521)
## What changed - Add `EnvironmentConfigState` to `TurnEnvironmentSelection` so each environment attachment can inherit thread configuration or carry ready configuration for its login-shell policy and selected capability roots. - Apply ready configuration during thread creation and thread-settings updates while reusing the existing environment connection. - Validate selection configuration before applying settings, rejecting unsupported pending state and invalid capability roots. ## Testing - Cover attachment-scoped capability roots, ready configuration at thread startup and turn updates, and rejection of invalid selections without changing existing settings. GitOrigin-RevId: 86cf8f2148fdaa746ea0dd12890ab7b1577d7587
This commit is contained in:
@@ -442,6 +442,7 @@ use codex_protocol::protocol::ConversationSpeechParams;
|
||||
use codex_protocol::protocol::ConversationStartParams;
|
||||
use codex_protocol::protocol::ConversationStartTransport;
|
||||
use codex_protocol::protocol::ConversationTextParams;
|
||||
use codex_protocol::protocol::EnvironmentConfigState;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
#[cfg(test)]
|
||||
use codex_protocol::protocol::GitInfo as CoreGitInfo;
|
||||
@@ -637,6 +638,7 @@ fn resolve_turn_environment_selections(
|
||||
environment_id,
|
||||
cwd,
|
||||
workspace_roots,
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
});
|
||||
}
|
||||
thread_manager
|
||||
|
||||
@@ -112,6 +112,7 @@ pub use codex_protocol::error::Result as CodexResult;
|
||||
pub use codex_protocol::models::PermissionProfile;
|
||||
pub use codex_protocol::openai_models::ModelPreset;
|
||||
pub use codex_protocol::protocol::AskForApproval;
|
||||
pub use codex_protocol::protocol::EnvironmentConfigState;
|
||||
pub use codex_protocol::protocol::EventMsg;
|
||||
pub use codex_protocol::protocol::Op;
|
||||
pub use codex_protocol::protocol::SessionConfiguredEvent;
|
||||
|
||||
@@ -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::EnvironmentConfigState;
|
||||
use codex_protocol::protocol::TurnEnvironmentSelection;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use codex_utils_path_uri::PathUri;
|
||||
@@ -337,6 +338,7 @@ fn resolved_local_environments<const N: usize>(
|
||||
environment_id: environment_id.to_string(),
|
||||
cwd: PathUri::from_abs_path(&cwd),
|
||||
workspace_roots: Vec::new(),
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
},
|
||||
Arc::new(
|
||||
Environment::create_for_tests(/*exec_server_url*/ None)
|
||||
|
||||
@@ -17,6 +17,7 @@ use codex_protocol::capabilities::SelectedCapabilityRoot;
|
||||
use codex_protocol::error::CodexErr;
|
||||
use codex_protocol::error::Result as CodexResult;
|
||||
use codex_protocol::protocol::EnvironmentConfig;
|
||||
use codex_protocol::protocol::EnvironmentConfigState;
|
||||
use codex_protocol::protocol::EnvironmentConnectionEvent;
|
||||
use codex_protocol::protocol::Event;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
@@ -46,6 +47,7 @@ pub(crate) fn default_thread_environment_selections(
|
||||
environment_id,
|
||||
cwd: PathUri::from_abs_path(cwd),
|
||||
workspace_roots: workspace_roots.iter().map(PathUri::from_abs_path).collect(),
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
@@ -80,17 +82,25 @@ pub(crate) struct StartingTurnEnvironment {
|
||||
}
|
||||
|
||||
impl SelectedTurnEnvironment {
|
||||
fn has_installed_environment_config(&self) -> bool {
|
||||
self.config.selected_capability_roots.is_some()
|
||||
}
|
||||
|
||||
// Thread settings still own permissions, but cannot overwrite shell policy
|
||||
// that was installed specifically for this environment attachment.
|
||||
fn update_thread_config(&mut self, config: &TurnEnvironmentConfig) {
|
||||
if !self.has_installed_environment_config() {
|
||||
self.config.allow_login_shell = config.allow_login_shell;
|
||||
}
|
||||
self.config.permission_profile = config.permission_profile.clone();
|
||||
fn apply_configuration(
|
||||
&mut self,
|
||||
selection_config: EnvironmentConfigState,
|
||||
thread_config: &TurnEnvironmentConfig,
|
||||
) {
|
||||
self.config = match &selection_config {
|
||||
EnvironmentConfigState::FromThread => thread_config.clone(),
|
||||
EnvironmentConfigState::Ready(config) => TurnEnvironmentConfig {
|
||||
allow_login_shell: config.allow_login_shell,
|
||||
// temp read from thread_config; will go away once perms on EnvironmentConfig,
|
||||
// then we can just assign passed config directly
|
||||
permission_profile: thread_config.permission_profile.clone(),
|
||||
selected_capability_roots: Some(config.selected_capability_roots.clone()),
|
||||
},
|
||||
EnvironmentConfigState::Pending => {
|
||||
unreachable!("pending environment configuration is not supported yet")
|
||||
}
|
||||
};
|
||||
self.selection.config = selection_config;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,7 +166,10 @@ impl ThreadEnvironments {
|
||||
};
|
||||
// Child threads get their own settings, but inherit any
|
||||
// environment-owned policy that was already installed.
|
||||
inherited_environment.update_thread_config(&thread_environment_config);
|
||||
inherited_environment.apply_configuration(
|
||||
inherited_environment.selection.config.clone(),
|
||||
&thread_environment_config,
|
||||
);
|
||||
Some(inherited_environment)
|
||||
})
|
||||
.collect();
|
||||
@@ -182,13 +195,18 @@ impl ThreadEnvironments {
|
||||
if !seen_environment_ids.insert(selected_environment.environment_id.as_str()) {
|
||||
continue;
|
||||
}
|
||||
if let Some(environment) = previous
|
||||
.iter()
|
||||
.find(|environment| environment.selection == *selected_environment)
|
||||
&& !matches!(environment.resolution.clone().now_or_never(), Some(Err(_)))
|
||||
if let Some(environment) = previous.iter().find(|environment| {
|
||||
let previous = &environment.selection;
|
||||
previous.environment_id == selected_environment.environment_id
|
||||
&& previous.cwd == selected_environment.cwd
|
||||
&& previous.workspace_roots == selected_environment.workspace_roots
|
||||
}) && !matches!(environment.resolution.clone().now_or_never(), Some(Err(_)))
|
||||
{
|
||||
let mut environment = environment.clone();
|
||||
environment.update_thread_config(thread_environment_config);
|
||||
environment.apply_configuration(
|
||||
selected_environment.config.clone(),
|
||||
thread_environment_config,
|
||||
);
|
||||
next.push(environment);
|
||||
continue;
|
||||
}
|
||||
@@ -224,13 +242,18 @@ impl ThreadEnvironments {
|
||||
.remote_handle();
|
||||
drop(tokio::spawn(resolution_task));
|
||||
let resolution = resolution.boxed().shared();
|
||||
next.push(SelectedTurnEnvironment {
|
||||
let mut selected = SelectedTurnEnvironment {
|
||||
selection: selected_environment.clone(),
|
||||
config: thread_environment_config.clone(),
|
||||
environment,
|
||||
connection_events_task,
|
||||
resolution,
|
||||
});
|
||||
};
|
||||
selected.apply_configuration(
|
||||
selected_environment.config.clone(),
|
||||
thread_environment_config,
|
||||
);
|
||||
next.push(selected);
|
||||
}
|
||||
let removed_connection_tasks = previous
|
||||
.iter()
|
||||
@@ -290,7 +313,7 @@ impl ThreadEnvironments {
|
||||
.iter()
|
||||
.map(|environment| {
|
||||
let mut environment = environment.clone();
|
||||
environment.update_thread_config(config);
|
||||
environment.apply_configuration(environment.selection.config.clone(), config);
|
||||
environment
|
||||
})
|
||||
.collect();
|
||||
@@ -315,8 +338,8 @@ impl ThreadEnvironments {
|
||||
)));
|
||||
};
|
||||
|
||||
environment.config.allow_login_shell = config.allow_login_shell;
|
||||
environment.config.selected_capability_roots = Some(config.selected_capability_roots);
|
||||
let thread_config = environment.config.clone();
|
||||
environment.apply_configuration(EnvironmentConfigState::Ready(config), &thread_config);
|
||||
self.environments.store(Arc::new(environments));
|
||||
Ok(())
|
||||
}
|
||||
@@ -787,6 +810,7 @@ mod tests {
|
||||
environment_id: REMOTE_ENVIRONMENT_ID.to_string(),
|
||||
cwd: cwd_uri.clone(),
|
||||
workspace_roots: vec![cwd_uri],
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
}]
|
||||
);
|
||||
}
|
||||
@@ -820,11 +844,13 @@ url = "ws://127.0.0.1:8765"
|
||||
environment_id: LOCAL_ENVIRONMENT_ID.to_string(),
|
||||
cwd: cwd_uri.clone(),
|
||||
workspace_roots: vec![cwd_uri.clone()],
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
},
|
||||
TurnEnvironmentSelection {
|
||||
environment_id: REMOTE_ENVIRONMENT_ID.to_string(),
|
||||
cwd: cwd_uri.clone(),
|
||||
workspace_roots: vec![cwd_uri],
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
},
|
||||
]
|
||||
);
|
||||
@@ -870,6 +896,7 @@ url = "ws://127.0.0.1:8765"
|
||||
environment_id: LOCAL_ENVIRONMENT_ID.to_string(),
|
||||
cwd: PathUri::from_abs_path(&cwd),
|
||||
workspace_roots: Vec::new(),
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
}],
|
||||
&expected_config,
|
||||
);
|
||||
@@ -890,6 +917,7 @@ url = "ws://127.0.0.1:8765"
|
||||
environment_id: LOCAL_ENVIRONMENT_ID.to_string(),
|
||||
cwd: cwd_uri.clone(),
|
||||
workspace_roots: Vec::new(),
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
};
|
||||
|
||||
let resolved = resolve_turn_environments(
|
||||
@@ -900,6 +928,7 @@ url = "ws://127.0.0.1:8765"
|
||||
environment_id: LOCAL_ENVIRONMENT_ID.to_string(),
|
||||
cwd: cwd_uri.join("other").expect("other cwd URI"),
|
||||
workspace_roots: Vec::new(),
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
},
|
||||
],
|
||||
)
|
||||
@@ -921,6 +950,7 @@ url = "ws://127.0.0.1:8765"
|
||||
environment_id: "local".to_string(),
|
||||
cwd: selected_cwd_uri,
|
||||
workspace_roots: Vec::new(),
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
}],
|
||||
)
|
||||
.await;
|
||||
@@ -960,6 +990,7 @@ url = "ws://127.0.0.1:8765"
|
||||
environment_id: LOCAL_ENVIRONMENT_ID.to_string(),
|
||||
cwd: cwd_uri.clone(),
|
||||
workspace_roots: Vec::new(),
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
};
|
||||
|
||||
let resolved = resolve_turn_environments(
|
||||
@@ -969,6 +1000,7 @@ url = "ws://127.0.0.1:8765"
|
||||
environment_id: "missing".to_string(),
|
||||
cwd: cwd_uri,
|
||||
workspace_roots: Vec::new(),
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
},
|
||||
local.clone(),
|
||||
],
|
||||
@@ -997,6 +1029,7 @@ url = "ws://127.0.0.1:8765"
|
||||
environment_id: REMOTE_ENVIRONMENT_ID.to_string(),
|
||||
cwd: PathUri::from_abs_path(&AbsolutePathBuf::current_dir().expect("cwd")),
|
||||
workspace_roots: Vec::new(),
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
};
|
||||
let environments = Arc::new(ThreadEnvironments::new(
|
||||
manager,
|
||||
@@ -1056,11 +1089,13 @@ url = "ws://127.0.0.1:8765"
|
||||
environment_id: REMOTE_ENVIRONMENT_ID.to_string(),
|
||||
cwd: cwd.clone(),
|
||||
workspace_roots: Vec::new(),
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
};
|
||||
let local = TurnEnvironmentSelection {
|
||||
environment_id: LOCAL_ENVIRONMENT_ID.to_string(),
|
||||
cwd,
|
||||
workspace_roots: Vec::new(),
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
};
|
||||
let turn_environments = ThreadEnvironments::new(
|
||||
manager,
|
||||
@@ -1163,6 +1198,7 @@ url = "ws://127.0.0.1:8765"
|
||||
environment_id: REMOTE_ENVIRONMENT_ID.to_string(),
|
||||
cwd: PathUri::from_abs_path(&AbsolutePathBuf::current_dir().expect("cwd")),
|
||||
workspace_roots: Vec::new(),
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
};
|
||||
let environments = ThreadEnvironments::new(
|
||||
Arc::clone(&manager),
|
||||
@@ -1248,6 +1284,7 @@ url = "ws://127.0.0.1:8765"
|
||||
environment_id: REMOTE_ENVIRONMENT_ID.to_string(),
|
||||
cwd: PathUri::from_abs_path(&cwd),
|
||||
workspace_roots: Vec::new(),
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
};
|
||||
let (tx_event, rx_event) = async_channel::unbounded();
|
||||
let environments = Arc::new(ThreadEnvironments::new(
|
||||
@@ -1337,6 +1374,7 @@ url = "ws://127.0.0.1:8765"
|
||||
environment_id: REMOTE_ENVIRONMENT_ID.to_string(),
|
||||
cwd: PathUri::from_abs_path(&cwd),
|
||||
workspace_roots: Vec::new(),
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
};
|
||||
let inherited_environment = Arc::new(
|
||||
Environment::create_for_tests(Some("ws://127.0.0.1:8765".to_string()))
|
||||
@@ -1391,6 +1429,7 @@ url = "ws://127.0.0.1:8765"
|
||||
environment_id: LOCAL_ENVIRONMENT_ID.to_string(),
|
||||
cwd: PathUri::from_abs_path(&cwd),
|
||||
workspace_roots: Vec::new(),
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
};
|
||||
let root = |id: &str| SelectedCapabilityRoot {
|
||||
id: id.to_string(),
|
||||
@@ -1482,6 +1521,7 @@ url = "ws://127.0.0.1:8765"
|
||||
environment_id: LOCAL_ENVIRONMENT_ID.to_string(),
|
||||
cwd: cwd_uri.clone(),
|
||||
workspace_roots: Vec::new(),
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
}],
|
||||
)
|
||||
.await;
|
||||
@@ -1496,6 +1536,7 @@ url = "ws://127.0.0.1:8765"
|
||||
environment_id: REMOTE_ENVIRONMENT_ID.to_string(),
|
||||
cwd: remote_cwd_uri.clone(),
|
||||
workspace_roots: Vec::new(),
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
},
|
||||
remote_environment.clone(),
|
||||
/*shell*/ None,
|
||||
@@ -1509,6 +1550,7 @@ url = "ws://127.0.0.1:8765"
|
||||
environment_id: REMOTE_ENVIRONMENT_ID.to_string(),
|
||||
cwd: remote_cwd_uri,
|
||||
workspace_roots: Vec::new(),
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
},
|
||||
remote_environment,
|
||||
/*shell*/ None,
|
||||
@@ -1535,6 +1577,7 @@ url = "ws://127.0.0.1:8765"
|
||||
environment_id: LOCAL_ENVIRONMENT_ID.to_string(),
|
||||
cwd: PathUri::from_abs_path(&cwd),
|
||||
workspace_roots: Vec::new(),
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
},
|
||||
config: test_environment_config(),
|
||||
resolution: futures::future::pending().boxed().shared(),
|
||||
|
||||
@@ -26,6 +26,7 @@ use codex_hooks::HooksConfig;
|
||||
use codex_model_provider::create_model_provider;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
use codex_protocol::protocol::EnvironmentConfigState;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
use codex_protocol::protocol::GranularApprovalConfig;
|
||||
use codex_protocol::protocol::McpInvocation;
|
||||
@@ -1111,6 +1112,7 @@ async fn mcp_sandbox_cwd_uses_matching_server_environment_uri() -> anyhow::Resul
|
||||
environment_id: "remote".to_string(),
|
||||
cwd: secondary_cwd.clone(),
|
||||
workspace_roots: Vec::new(),
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
},
|
||||
environment,
|
||||
/*shell*/ None,
|
||||
|
||||
@@ -6,6 +6,7 @@ use codex_protocol::capabilities::CapabilityRootLocation;
|
||||
use codex_protocol::error::CodexErr;
|
||||
use codex_protocol::error::Result as CodexResult;
|
||||
use codex_protocol::protocol::EnvironmentConfig;
|
||||
use codex_protocol::protocol::EnvironmentConfigState;
|
||||
use codex_protocol::protocol::TurnEnvironmentSelection;
|
||||
|
||||
use crate::config::ConstraintResult;
|
||||
@@ -13,6 +14,51 @@ use crate::session::session::Session;
|
||||
use crate::session::session::SessionConfiguration;
|
||||
use crate::session::session::SessionSettingsUpdate;
|
||||
|
||||
pub(super) fn validate_environment_selections(
|
||||
selections: &[TurnEnvironmentSelection],
|
||||
) -> CodexResult<()> {
|
||||
for selection in selections {
|
||||
match &selection.config {
|
||||
EnvironmentConfigState::FromThread => {}
|
||||
EnvironmentConfigState::Pending => {
|
||||
return Err(CodexErr::InvalidRequest(
|
||||
"pending environment configuration is not supported yet".to_string(),
|
||||
));
|
||||
}
|
||||
EnvironmentConfigState::Ready(config) => {
|
||||
validate_environment_config(selection, config)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_environment_config(
|
||||
selection: &TurnEnvironmentSelection,
|
||||
config: &EnvironmentConfig,
|
||||
) -> CodexResult<()> {
|
||||
if config.selected_capability_roots.len() > MAX_SELECTED_CAPABILITY_ROOTS {
|
||||
return Err(CodexErr::InvalidRequest(format!(
|
||||
"environment readiness contains more than {MAX_SELECTED_CAPABILITY_ROOTS} selected capability roots"
|
||||
)));
|
||||
}
|
||||
|
||||
let mut root_ids = HashSet::with_capacity(config.selected_capability_roots.len());
|
||||
for root in &config.selected_capability_roots {
|
||||
let CapabilityRootLocation::Environment { environment_id, .. } = &root.location;
|
||||
if root.id.trim().is_empty()
|
||||
|| environment_id != &selection.environment_id
|
||||
|| !root_ids.insert(root.id.as_str())
|
||||
{
|
||||
return Err(CodexErr::InvalidRequest(format!(
|
||||
"selected capability roots must have unique non-empty IDs and belong to environment `{}`",
|
||||
selection.environment_id
|
||||
)));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl Session {
|
||||
pub(super) fn apply_session_settings(
|
||||
&self,
|
||||
@@ -27,25 +73,7 @@ impl Session {
|
||||
selection: &TurnEnvironmentSelection,
|
||||
config: EnvironmentConfig,
|
||||
) -> CodexResult<()> {
|
||||
if config.selected_capability_roots.len() > MAX_SELECTED_CAPABILITY_ROOTS {
|
||||
return Err(CodexErr::InvalidRequest(format!(
|
||||
"environment readiness contains more than {MAX_SELECTED_CAPABILITY_ROOTS} selected capability roots"
|
||||
)));
|
||||
}
|
||||
|
||||
let mut root_ids = HashSet::with_capacity(config.selected_capability_roots.len());
|
||||
for root in &config.selected_capability_roots {
|
||||
let CapabilityRootLocation::Environment { environment_id, .. } = &root.location;
|
||||
if root.id.trim().is_empty()
|
||||
|| environment_id != &selection.environment_id
|
||||
|| !root_ids.insert(root.id.as_str())
|
||||
{
|
||||
return Err(CodexErr::InvalidRequest(format!(
|
||||
"selected capability roots must have unique non-empty IDs and belong to environment `{}`",
|
||||
selection.environment_id
|
||||
)));
|
||||
}
|
||||
}
|
||||
validate_environment_config(selection, &config)?;
|
||||
|
||||
// grab session lock so installation can't race w/ thread settings updates
|
||||
let _state = self.state.lock().await;
|
||||
|
||||
@@ -709,7 +709,7 @@ impl Session {
|
||||
user_shell_override,
|
||||
};
|
||||
session_configuration
|
||||
.validate_auto_review_requirement(&environment_selections)
|
||||
.validate(&environment_selections)
|
||||
.map_err(|err| CodexErr::InvalidRequest(err.to_string()))?;
|
||||
|
||||
// Generate a unique ID for the lifetime of this session.
|
||||
|
||||
@@ -243,7 +243,22 @@ impl SessionConfiguration {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn validate_auto_review_requirement(
|
||||
pub(super) fn validate(
|
||||
&self,
|
||||
environments: &[TurnEnvironmentSelection],
|
||||
) -> ConstraintResult<()> {
|
||||
self.validate_auto_review_requirement(environments)?;
|
||||
super::environment::validate_environment_selections(environments).map_err(|error| {
|
||||
ConstraintError::InvalidValue {
|
||||
field_name: "environments",
|
||||
candidate: "environment configuration".to_string(),
|
||||
allowed: format!("valid selected capability roots ({error})"),
|
||||
requirement_source: codex_config::RequirementSource::Unknown,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn validate_auto_review_requirement(
|
||||
&self,
|
||||
environments: &[TurnEnvironmentSelection],
|
||||
) -> ConstraintResult<()> {
|
||||
@@ -463,7 +478,7 @@ impl SessionConfiguration {
|
||||
.map_or(current_environments, |environments| {
|
||||
environments.environments.as_slice()
|
||||
});
|
||||
next_configuration.validate_auto_review_requirement(next_environments)?;
|
||||
next_configuration.validate(next_environments)?;
|
||||
Ok(next_configuration)
|
||||
}
|
||||
|
||||
|
||||
@@ -66,6 +66,7 @@ use codex_protocol::permissions::FileSystemPath;
|
||||
use codex_protocol::permissions::FileSystemSandboxEntry;
|
||||
use codex_protocol::permissions::FileSystemSandboxPolicy;
|
||||
use codex_protocol::permissions::FileSystemSpecialPath;
|
||||
use codex_protocol::protocol::EnvironmentConfigState;
|
||||
use codex_protocol::protocol::ErrorEvent;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
use codex_protocol::protocol::TurnEnvironmentSelections;
|
||||
@@ -6644,6 +6645,7 @@ async fn request_permissions_tool_resolves_relative_paths_against_selected_envir
|
||||
environment_id: "remote".to_string(),
|
||||
cwd: PathUri::from_abs_path(&environment_cwd),
|
||||
workspace_roots: Vec::new(),
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
},
|
||||
current_environment.environment,
|
||||
current_environment.shell,
|
||||
@@ -7243,6 +7245,7 @@ async fn primary_environment_uses_first_turn_environment() {
|
||||
environment_id: "second".to_string(),
|
||||
cwd: second_cwd_uri.clone(),
|
||||
workspace_roots: Vec::new(),
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
},
|
||||
Arc::clone(&first_environment.environment),
|
||||
/*shell*/ None,
|
||||
@@ -8654,6 +8657,7 @@ async fn conflicting_ready_environment_root_ids_keep_first_location() {
|
||||
environment_id: environment_id.clone(),
|
||||
cwd: local_environment.cwd().clone(),
|
||||
workspace_roots: local_environment.workspace_roots().to_vec(),
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
},
|
||||
environment,
|
||||
local_environment.shell.clone(),
|
||||
@@ -8913,6 +8917,7 @@ async fn record_context_updates_emits_environment_item_for_cwd_changes() {
|
||||
environment_id: environment.selection.environment_id,
|
||||
cwd: PathUri::from_abs_path(&cwd),
|
||||
workspace_roots: Vec::new(),
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
},
|
||||
environment.environment,
|
||||
environment.shell,
|
||||
@@ -8972,6 +8977,7 @@ async fn record_context_updates_use_environment_permission_profile_and_workspace
|
||||
environment_id: environment.selection.environment_id,
|
||||
cwd,
|
||||
workspace_roots: vec![PathUri::from_abs_path(&workspace_root)],
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
},
|
||||
environment.environment,
|
||||
environment.shell,
|
||||
@@ -9047,6 +9053,7 @@ async fn record_context_updates_omits_environment_item_when_disabled() {
|
||||
environment_id: environment.selection.environment_id,
|
||||
cwd: PathUri::from_abs_path(&test_path_buf("/new-repo").abs()),
|
||||
workspace_roots: Vec::new(),
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
},
|
||||
environment.environment,
|
||||
environment.shell,
|
||||
@@ -9515,6 +9522,7 @@ async fn turn_context_item_stores_local_cwd() {
|
||||
environment_id: "remote".to_string(),
|
||||
cwd,
|
||||
workspace_roots: Vec::new(),
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
},
|
||||
environment.environment,
|
||||
environment.shell,
|
||||
|
||||
@@ -25,6 +25,7 @@ use codex_protocol::models::ReasoningItemReasoningSummary;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
use codex_protocol::openai_models::ModelsResponse;
|
||||
use codex_protocol::protocol::AgentMessageEvent;
|
||||
use codex_protocol::protocol::EnvironmentConfigState;
|
||||
use codex_protocol::protocol::InternalSessionSource;
|
||||
use codex_protocol::protocol::SessionMeta;
|
||||
use codex_protocol::protocol::SessionMetaLine;
|
||||
@@ -1143,6 +1144,7 @@ async fn resume_and_fork_do_not_restore_thread_environments_from_rollout() {
|
||||
environment_id: "local".to_string(),
|
||||
cwd: PathUri::from_abs_path(&selected_cwd),
|
||||
workspace_roots: Vec::new(),
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
}];
|
||||
let default_cwd = config.cwd.clone();
|
||||
let mut source_config = config.clone();
|
||||
|
||||
@@ -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::EnvironmentConfigState;
|
||||
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;
|
||||
@@ -108,6 +109,7 @@ async fn shell_command_handler_to_exec_params_uses_selected_environment() {
|
||||
environment_id: "selected-environment".to_string(),
|
||||
cwd: PathUri::from_abs_path(&selected_cwd),
|
||||
workspace_roots: Vec::new(),
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
},
|
||||
Arc::clone(
|
||||
&turn_context
|
||||
|
||||
@@ -7,6 +7,7 @@ use codex_protocol::models::AdditionalPermissionProfile;
|
||||
use codex_protocol::models::FileSystemPermissions;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||
use codex_protocol::protocol::EnvironmentConfigState;
|
||||
use codex_protocol::protocol::GranularApprovalConfig;
|
||||
use codex_protocol::protocol::TurnEnvironmentSelection;
|
||||
use codex_sandboxing::SandboxManager;
|
||||
@@ -23,6 +24,7 @@ fn test_turn_environment(environment_id: &str) -> crate::session::turn_context::
|
||||
environment_id: environment_id.to_string(),
|
||||
cwd: PathUri::from_abs_path(&std::env::temp_dir().abs()),
|
||||
workspace_roots: Vec::new(),
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
},
|
||||
std::sync::Arc::new(codex_exec_server::Environment::default_for_tests()),
|
||||
/*shell*/ None,
|
||||
|
||||
@@ -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::EnvironmentConfigState;
|
||||
use codex_protocol::protocol::TurnEnvironmentSelection;
|
||||
use codex_utils_path_uri::PathUri;
|
||||
use pretty_assertions::assert_eq;
|
||||
@@ -20,6 +21,7 @@ async fn approval_key_uses_path_uri_and_includes_environment_id() {
|
||||
environment_id: "remote".to_string(),
|
||||
cwd: PathUri::from_abs_path(&cwd),
|
||||
workspace_roots: Vec::new(),
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
},
|
||||
Arc::new(Environment::default_for_tests()),
|
||||
/*shell*/ None,
|
||||
|
||||
@@ -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::EnvironmentConfigState;
|
||||
use codex_protocol::protocol::TurnEnvironmentSelection;
|
||||
use codex_tools::ZshForkConfig;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
@@ -535,6 +536,7 @@ mod tests {
|
||||
environment_id: LOCAL_ENVIRONMENT_ID.to_string(),
|
||||
cwd,
|
||||
workspace_roots: Vec::new(),
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
},
|
||||
Arc::new(Environment::default_for_tests()),
|
||||
/*shell*/ None,
|
||||
|
||||
@@ -21,6 +21,7 @@ use codex_protocol::openai_models::ConfigShellToolType;
|
||||
use codex_protocol::openai_models::InputModality;
|
||||
use codex_protocol::openai_models::ToolMode;
|
||||
use codex_protocol::openai_models::WebSearchToolType;
|
||||
use codex_protocol::protocol::EnvironmentConfigState;
|
||||
use codex_protocol::protocol::MultiAgentVersion;
|
||||
use codex_protocol::protocol::SessionSource;
|
||||
use codex_protocol::protocol::SubAgentSource;
|
||||
@@ -907,6 +908,7 @@ async fn zsh_fork_unified_exec_keeps_shell_parameter_when_remote_environment_ava
|
||||
environment_id: "remote".to_string(),
|
||||
cwd: remote_cwd,
|
||||
workspace_roots: Vec::new(),
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
},
|
||||
Arc::new(
|
||||
codex_exec_server::Environment::create_for_tests(Some(
|
||||
|
||||
@@ -47,6 +47,7 @@ use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::openai_models::ModelInfo;
|
||||
use codex_protocol::openai_models::ModelsResponse;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
use codex_protocol::protocol::EnvironmentConfigState;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
use codex_protocol::protocol::RealtimeConversationVersion as RealtimeWsVersion;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
@@ -118,6 +119,7 @@ pub fn local(cwd: AbsolutePathBuf) -> TurnEnvironmentSelection {
|
||||
environment_id: codex_exec_server::LOCAL_ENVIRONMENT_ID.to_string(),
|
||||
cwd: PathUri::from_abs_path(&cwd),
|
||||
workspace_roots: vec![PathUri::from_abs_path(&cwd)],
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,6 +152,7 @@ impl TestEnv {
|
||||
environment_id: codex_exec_server::REMOTE_ENVIRONMENT_ID.to_string(),
|
||||
cwd: PathUri::from_abs_path(&cwd),
|
||||
workspace_roots: vec![PathUri::from_abs_path(&cwd)],
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
},
|
||||
None => local(cwd.clone()),
|
||||
};
|
||||
@@ -218,6 +221,7 @@ pub async fn test_env() -> Result<TestEnv> {
|
||||
environment_id: codex_exec_server::REMOTE_ENVIRONMENT_ID.to_string(),
|
||||
cwd: cwd_uri.clone(),
|
||||
workspace_roots: vec![cwd_uri.clone()],
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
};
|
||||
let cwd = if remote_env == TestEnvironment::WineExec {
|
||||
// TODO(anp): Convert `Config::cwd` to `LegacyAppPathString` and remove this
|
||||
|
||||
@@ -9,6 +9,7 @@ use codex_protocol::config_types::ModeKind;
|
||||
use codex_protocol::config_types::Settings;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
use codex_protocol::protocol::EnvironmentConfigState;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
use codex_protocol::protocol::ExecCommandStatus;
|
||||
use codex_protocol::protocol::TurnEnvironmentSelection;
|
||||
@@ -115,6 +116,7 @@ async fn windows_exec_server_runs_with_native_shell_and_cwd() -> Result<()> {
|
||||
environment_id: REMOTE_ENVIRONMENT_ID.to_string(),
|
||||
cwd: cwd.clone(),
|
||||
workspace_roots: vec![cwd],
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
}
|
||||
}],
|
||||
);
|
||||
|
||||
@@ -10,6 +10,7 @@ use codex_features::Feature;
|
||||
use codex_history::RolloutItem;
|
||||
use codex_history::RolloutLine;
|
||||
use codex_home::CodexHomeUserInstructionsProvider;
|
||||
use codex_protocol::protocol::EnvironmentConfigState;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
use codex_protocol::protocol::Op;
|
||||
use codex_protocol::protocol::TurnEnvironmentSelection;
|
||||
@@ -658,11 +659,13 @@ async fn multi_environment_project_instructions_share_one_byte_budget() -> Resul
|
||||
environment_id: REMOTE_ENVIRONMENT_ID.to_string(),
|
||||
cwd: PathUri::from_abs_path(&test.config.cwd),
|
||||
workspace_roots: vec![PathUri::from_abs_path(&test.config.cwd)],
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
},
|
||||
TurnEnvironmentSelection {
|
||||
environment_id: LOCAL_ENVIRONMENT_ID.to_string(),
|
||||
cwd: PathUri::from_host_native_path(local_root.path())?,
|
||||
workspace_roots: vec![PathUri::from_host_native_path(local_root.path())?],
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
},
|
||||
]),
|
||||
..StartThreadOptions::new(test.config.clone())
|
||||
@@ -736,11 +739,13 @@ async fn multi_environment_thread_loads_every_project_and_keeps_creation_snapsho
|
||||
environment_id: REMOTE_ENVIRONMENT_ID.to_string(),
|
||||
cwd: PathUri::from_abs_path(&test.config.cwd),
|
||||
workspace_roots: vec![PathUri::from_abs_path(&test.config.cwd)],
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
},
|
||||
TurnEnvironmentSelection {
|
||||
environment_id: LOCAL_ENVIRONMENT_ID.to_string(),
|
||||
cwd: PathUri::from_host_native_path(local_root.path())?,
|
||||
workspace_roots: vec![PathUri::from_host_native_path(local_root.path())?],
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
},
|
||||
]),
|
||||
..StartThreadOptions::new(test.config.clone())
|
||||
|
||||
@@ -31,6 +31,7 @@ use codex_protocol::permissions::FileSystemSandboxPolicy;
|
||||
use codex_protocol::permissions::FileSystemSpecialPath;
|
||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
use codex_protocol::protocol::EnvironmentConfigState;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
use codex_protocol::protocol::ThreadSettingsOverrides;
|
||||
@@ -1886,6 +1887,7 @@ async fn apply_patch_turn_diff_tracks_local_and_remote_environment_paths() -> Re
|
||||
environment_id: REMOTE_ENVIRONMENT_ID.to_string(),
|
||||
cwd: PathUri::from_abs_path(&shared_cwd),
|
||||
workspace_roots: vec![PathUri::from_abs_path(&shared_cwd)],
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
},
|
||||
];
|
||||
test.codex
|
||||
|
||||
@@ -23,6 +23,7 @@ use codex_protocol::config_types::ModeKind;
|
||||
use codex_protocol::config_types::Settings;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
use codex_protocol::protocol::EnvironmentConfigState;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
use codex_protocol::protocol::ThreadSettingsOverrides;
|
||||
use codex_protocol::protocol::TurnEnvironmentSelection;
|
||||
@@ -217,6 +218,7 @@ async fn model_visible_environment_context_preserves_foreign_workspace_roots() -
|
||||
environment_id: LOCAL_ENVIRONMENT_ID.to_string(),
|
||||
cwd: PathUri::from_abs_path(&test.config.cwd),
|
||||
workspace_roots: vec![foreign_root],
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
}],
|
||||
)),
|
||||
..Default::default()
|
||||
|
||||
@@ -9,6 +9,7 @@ use codex_features::Feature;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
use codex_protocol::protocol::EnvironmentConfigState;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
use codex_protocol::protocol::ThreadSettingsOverrides;
|
||||
use codex_protocol::protocol::TurnEnvironmentSelection;
|
||||
@@ -190,11 +191,13 @@ async fn two_exec_servers_isolate_workspace_write_roots() -> Result<()> {
|
||||
environment_id: FIRST_ENVIRONMENT_ID.to_string(),
|
||||
cwd: first_workspace_uri.clone(),
|
||||
workspace_roots: vec![first_workspace_uri],
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
},
|
||||
TurnEnvironmentSelection {
|
||||
environment_id: SECOND_ENVIRONMENT_ID.to_string(),
|
||||
cwd: second_workspace_uri.clone(),
|
||||
workspace_roots: vec![second_workspace_uri],
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
},
|
||||
];
|
||||
let permission_profile = PermissionProfile::workspace_write_with(
|
||||
|
||||
@@ -19,6 +19,7 @@ use codex_protocol::models::NetworkPermissions;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
use codex_protocol::protocol::EnvironmentConfigState;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
use codex_protocol::protocol::ExecApprovalRequestEvent;
|
||||
use codex_protocol::protocol::GuardianAssessmentStatus;
|
||||
@@ -1837,6 +1838,7 @@ async fn approved_network_host_for_one_environment_still_prompts_in_another() ->
|
||||
environment_id: REMOTE_ENVIRONMENT_ID.to_string(),
|
||||
cwd: PathUri::from_abs_path(&remote_cwd),
|
||||
workspace_roots: vec![PathUri::from_abs_path(&remote_cwd)],
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||
use codex_protocol::protocol::ApplyPatchApprovalRequestEvent;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
use codex_protocol::protocol::ENVIRONMENTS_INSTRUCTIONS_OPEN_TAG;
|
||||
use codex_protocol::protocol::EnvironmentConfigState;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
use codex_protocol::protocol::Op;
|
||||
use codex_protocol::protocol::ReviewDecision;
|
||||
@@ -415,6 +416,7 @@ async fn explicit_remote_shell_runs_in_remote_cwd() -> Result<()> {
|
||||
environment_id: REMOTE_ENVIRONMENT_ID.to_string(),
|
||||
cwd: PathUri::from_abs_path(&test.config.cwd),
|
||||
workspace_roots: vec![PathUri::from_abs_path(&test.config.cwd)],
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
}]),
|
||||
)
|
||||
.await?;
|
||||
@@ -693,6 +695,7 @@ async fn deferred_executor_promotes_primary_environment_when_startup_completes()
|
||||
environment_id: REMOTE_ENVIRONMENT_ID.to_string(),
|
||||
cwd: PathUri::from_abs_path(&test.config.cwd),
|
||||
workspace_roots: vec![PathUri::from_abs_path(&test.config.cwd)],
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
};
|
||||
|
||||
test.submit_turn_with_environments(
|
||||
@@ -1005,36 +1008,69 @@ async fn shared_executor_keeps_ready_capability_roots_scoped_to_each_attachment(
|
||||
},
|
||||
};
|
||||
|
||||
for config in [
|
||||
EnvironmentConfigState::Pending,
|
||||
EnvironmentConfigState::Ready(EnvironmentConfig {
|
||||
allow_login_shell: false,
|
||||
selected_capability_roots: vec![root("duplicate"), root("duplicate")],
|
||||
}),
|
||||
] {
|
||||
let invalid_selection = TurnEnvironmentSelection {
|
||||
config,
|
||||
..selection.clone()
|
||||
};
|
||||
assert!(
|
||||
test.codex
|
||||
.preview_thread_settings_overrides(CodexThreadSettingsOverrides {
|
||||
environments: Some(TurnEnvironmentSelections::new(
|
||||
test.config.cwd.clone(),
|
||||
vec![invalid_selection],
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.is_err()
|
||||
);
|
||||
assert_eq!(
|
||||
test.codex.environment_selections().await,
|
||||
vec![selection.clone()]
|
||||
);
|
||||
}
|
||||
|
||||
let mut second_thread_init = ExtensionDataInit::new();
|
||||
second_thread_init.insert(vec![root("startup-root")]);
|
||||
let second = test
|
||||
.thread_manager
|
||||
.start_thread(StartThreadOptions {
|
||||
environments: Some(vec![selection.clone()]),
|
||||
environments: Some(vec![TurnEnvironmentSelection {
|
||||
config: EnvironmentConfigState::Ready(EnvironmentConfig {
|
||||
allow_login_shell: true,
|
||||
selected_capability_roots: vec![root("startup-root"), root("second-root")],
|
||||
}),
|
||||
..selection.clone()
|
||||
}]),
|
||||
thread_extension_init: second_thread_init,
|
||||
..StartThreadOptions::new(test.config.clone())
|
||||
})
|
||||
.await?;
|
||||
|
||||
test.codex
|
||||
.environment_ready(
|
||||
&selection,
|
||||
EnvironmentConfig {
|
||||
allow_login_shell: false,
|
||||
selected_capability_roots: vec![root("first-root")],
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
second
|
||||
.thread
|
||||
.environment_ready(
|
||||
&selection,
|
||||
EnvironmentConfig {
|
||||
allow_login_shell: true,
|
||||
selected_capability_roots: vec![root("startup-root"), root("second-root")],
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
submit_thread_settings(
|
||||
&test.codex,
|
||||
ThreadSettingsOverrides {
|
||||
environments: Some(TurnEnvironmentSelections::new(
|
||||
test.config.cwd.clone(),
|
||||
vec![TurnEnvironmentSelection {
|
||||
config: EnvironmentConfigState::Ready(EnvironmentConfig {
|
||||
allow_login_shell: false,
|
||||
selected_capability_roots: vec![root("first-root")],
|
||||
}),
|
||||
..selection.clone()
|
||||
}],
|
||||
)),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
assert_eq!(
|
||||
test.codex.inspect_selected_capability_roots().ready_roots,
|
||||
@@ -1071,24 +1107,27 @@ async fn shared_executor_keeps_ready_capability_roots_scoped_to_each_attachment(
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
{
|
||||
let mut request = TurnInputRequest::user_input(vec![UserInput::Text {
|
||||
text: prompt.to_string(),
|
||||
text_elements: Vec::new(),
|
||||
}]);
|
||||
if index == 2 {
|
||||
test.codex
|
||||
.environment_ready(
|
||||
&selection,
|
||||
EnvironmentConfig {
|
||||
allow_login_shell: false,
|
||||
selected_capability_roots: vec![root("first-updated-root")],
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
request = request.with_thread_settings(ThreadSettingsOverrides {
|
||||
environments: Some(TurnEnvironmentSelections::new(
|
||||
test.config.cwd.clone(),
|
||||
vec![TurnEnvironmentSelection {
|
||||
config: EnvironmentConfigState::Ready(EnvironmentConfig {
|
||||
allow_login_shell: false,
|
||||
selected_capability_roots: vec![root("first-updated-root")],
|
||||
}),
|
||||
..selection.clone()
|
||||
}],
|
||||
)),
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
|
||||
thread
|
||||
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||
text: prompt.to_string(),
|
||||
text_elements: Vec::new(),
|
||||
}]))
|
||||
.await?;
|
||||
thread.start_or_steer_turn(request).await?;
|
||||
wait_for_event(thread, |event| matches!(event, EventMsg::TurnComplete(_))).await;
|
||||
}
|
||||
|
||||
@@ -1288,6 +1327,7 @@ async fn ready_before_selection_exposes_remote_tools_and_capability_context_afte
|
||||
environment_id: REMOTE_ENVIRONMENT_ID.to_string(),
|
||||
cwd: PathUri::from_abs_path(&test.config.cwd),
|
||||
workspace_roots: vec![PathUri::from_abs_path(&test.config.cwd)],
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
}]),
|
||||
)
|
||||
.await?;
|
||||
@@ -1390,6 +1430,7 @@ async fn deferred_executor_stays_pending_after_materialization() -> Result<()> {
|
||||
environment_id: REMOTE_ENVIRONMENT_ID.to_string(),
|
||||
cwd: PathUri::from_abs_path(&test.config.cwd),
|
||||
workspace_roots: vec![PathUri::from_abs_path(&test.config.cwd)],
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
}],
|
||||
)),
|
||||
..Default::default()
|
||||
@@ -1514,6 +1555,7 @@ async fn deferred_executor_spawn_agent_inherits_ready_step_environments(
|
||||
environment_id: REMOTE_ENVIRONMENT_ID.to_string(),
|
||||
cwd: PathUri::from_abs_path(&test.config.cwd),
|
||||
workspace_roots: vec![PathUri::from_abs_path(&test.config.cwd)],
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
};
|
||||
let expected_environments = vec![remote_selection, local(test.config.cwd.clone())];
|
||||
let mut created_threads = test.thread_manager.subscribe_thread_created();
|
||||
@@ -1646,6 +1688,7 @@ async fn deferred_executor_guardian_uses_newly_ready_step_environment() -> Resul
|
||||
environment_id: REMOTE_ENVIRONMENT_ID.to_string(),
|
||||
cwd: PathUri::from_abs_path(&remote_cwd),
|
||||
workspace_roots: vec![PathUri::from_abs_path(&remote_cwd)],
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
};
|
||||
let permission_profile = PermissionProfile::from_runtime_permissions(
|
||||
&FileSystemSandboxPolicy::restricted(vec![
|
||||
@@ -2142,6 +2185,7 @@ async fn exec_command_routes_to_selected_remote_environment() -> Result<()> {
|
||||
environment_id: REMOTE_ENVIRONMENT_ID.to_string(),
|
||||
cwd: PathUri::from_abs_path(&remote_cwd),
|
||||
workspace_roots: vec![PathUri::from_abs_path(&remote_cwd)],
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
};
|
||||
let multi_env_output = exec_command_routing_output(
|
||||
&test,
|
||||
@@ -2280,11 +2324,13 @@ async fn remote_exec_materializes_target_roots_before_sandbox_selection() -> Res
|
||||
environment_id: LOCAL_ENVIRONMENT_ID.to_string(),
|
||||
cwd: PathUri::from_abs_path(&local_cwd.path().abs()),
|
||||
workspace_roots: Vec::new(),
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
},
|
||||
TurnEnvironmentSelection {
|
||||
environment_id: REMOTE_ENVIRONMENT_ID.to_string(),
|
||||
cwd: remote_cwd_uri.clone(),
|
||||
workspace_roots: vec![remote_cwd_uri.clone()],
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
},
|
||||
],
|
||||
)),
|
||||
@@ -2453,6 +2499,7 @@ async fn remote_request_permissions_grant_unblocks_later_remote_exec() -> Result
|
||||
environment_id: REMOTE_ENVIRONMENT_ID.to_string(),
|
||||
cwd: PathUri::from_abs_path(&remote_cwd),
|
||||
workspace_roots: vec![PathUri::from_abs_path(&remote_cwd)],
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
},
|
||||
],
|
||||
AskForApproval::OnRequest,
|
||||
@@ -2595,6 +2642,7 @@ async fn apply_patch_freeform_routes_to_selected_remote_environment() -> Result<
|
||||
environment_id: REMOTE_ENVIRONMENT_ID.to_string(),
|
||||
cwd: PathUri::from_abs_path(&remote_cwd),
|
||||
workspace_roots: vec![PathUri::from_abs_path(&remote_cwd)],
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
},
|
||||
]),
|
||||
)
|
||||
@@ -2679,6 +2727,7 @@ async fn apply_patch_approvals_are_remembered_per_environment() -> Result<()> {
|
||||
environment_id: REMOTE_ENVIRONMENT_ID.to_string(),
|
||||
cwd: PathUri::from_abs_path(&remote_cwd),
|
||||
workspace_roots: vec![PathUri::from_abs_path(&remote_cwd)],
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
},
|
||||
];
|
||||
let local_patch = format!(
|
||||
@@ -2881,6 +2930,7 @@ async fn apply_patch_intercepted_exec_command_routes_to_selected_remote_environm
|
||||
environment_id: REMOTE_ENVIRONMENT_ID.to_string(),
|
||||
cwd: PathUri::from_abs_path(&remote_cwd),
|
||||
workspace_roots: vec![PathUri::from_abs_path(&remote_cwd)],
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
},
|
||||
]),
|
||||
)
|
||||
|
||||
@@ -17,6 +17,7 @@ use codex_protocol::permissions::FileSystemSandboxPolicy;
|
||||
use codex_protocol::permissions::FileSystemSpecialPath;
|
||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
use codex_protocol::protocol::EnvironmentConfigState;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
use codex_protocol::protocol::Op;
|
||||
use codex_protocol::protocol::ReviewDecision;
|
||||
@@ -704,6 +705,7 @@ timeout = 900
|
||||
PathUri::parse("file:///D:/other-workspace")
|
||||
.expect("valid Windows workspace root"),
|
||||
],
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
}],
|
||||
)
|
||||
}),
|
||||
|
||||
@@ -29,6 +29,7 @@ use codex_protocol::permissions::FileSystemSandboxEntry;
|
||||
use codex_protocol::permissions::FileSystemSandboxPolicy;
|
||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
use codex_protocol::protocol::EnvironmentConfigState;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
use codex_protocol::protocol::ThreadSettingsOverrides;
|
||||
use codex_protocol::protocol::TurnEnvironmentSelection;
|
||||
@@ -748,6 +749,7 @@ async fn view_image_routes_to_selected_remote_environment() -> anyhow::Result<()
|
||||
environment_id: REMOTE_ENVIRONMENT_ID.to_string(),
|
||||
cwd: remote_cwd_uri.clone(),
|
||||
workspace_roots: vec![remote_cwd_uri],
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
};
|
||||
let relative_call_id = "call-view-image-relative-multi-env";
|
||||
let absolute_call_id = "call-view-image-absolute-multi-env";
|
||||
|
||||
@@ -37,6 +37,7 @@ use codex_otel::THREAD_SKILLS_KEPT_TOTAL_METRIC;
|
||||
use codex_otel::THREAD_SKILLS_TRUNCATED_METRIC;
|
||||
use codex_protocol::capabilities::CapabilityRootLocation;
|
||||
use codex_protocol::capabilities::SelectedCapabilityRoot;
|
||||
use codex_protocol::protocol::EnvironmentConfigState;
|
||||
use codex_protocol::protocol::Event;
|
||||
use codex_protocol::protocol::SKILLS_INSTRUCTIONS_CLOSE_TAG;
|
||||
use codex_protocol::protocol::SKILLS_INSTRUCTIONS_OPEN_TAG;
|
||||
@@ -1031,6 +1032,7 @@ async fn selected_executor_catalog_follows_step_availability_and_reuses_its_cach
|
||||
environment_id: "turn-env".to_string(),
|
||||
cwd: PathUri::parse("file:///workspace").expect("cwd URI"),
|
||||
workspace_roots: Vec::new(),
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
};
|
||||
let available_sections = registry.context_contributors()[0]
|
||||
.contribute_world_state(WorldStateContributionInput {
|
||||
@@ -1752,6 +1754,7 @@ async fn root_qualified_locator_selects_only_the_matching_executor_skill() -> Te
|
||||
environment_id: "env-1".to_string(),
|
||||
cwd: PathUri::parse("file:///workspace").expect("cwd URI"),
|
||||
workspace_roots: Vec::new(),
|
||||
config: EnvironmentConfigState::FromThread,
|
||||
}],
|
||||
ready_selected_capability_roots: &selected_roots,
|
||||
executor_capability_discovery: None,
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
use crate::capabilities::SelectedCapabilityRoot;
|
||||
|
||||
/// Configuration supplied for a thread's selected environment.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum EnvironmentConfigState {
|
||||
/// Preserve the existing thread-derived environment configuration.
|
||||
FromThread,
|
||||
/// The owner will supply environment configuration later.
|
||||
Pending,
|
||||
/// The owner supplied configuration for this environment attachment.
|
||||
Ready(EnvironmentConfig),
|
||||
}
|
||||
|
||||
/// Resolved configuration supplied by the owner of a thread/environment attachment.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct EnvironmentConfig {
|
||||
|
||||
@@ -91,6 +91,7 @@ pub use crate::approvals::NetworkApprovalProtocol;
|
||||
pub use crate::approvals::NetworkPolicyAmendment;
|
||||
pub use crate::approvals::NetworkPolicyRuleAction;
|
||||
pub use crate::environment::EnvironmentConfig;
|
||||
pub use crate::environment::EnvironmentConfigState;
|
||||
pub use crate::legacy_events::HasLegacyEvent;
|
||||
pub use crate::permissions::FileSystemAccessMode;
|
||||
pub use crate::permissions::FileSystemPath;
|
||||
@@ -146,6 +147,7 @@ pub struct TurnEnvironmentSelection {
|
||||
pub environment_id: String,
|
||||
pub cwd: PathUri,
|
||||
pub workspace_roots: Vec<PathUri>,
|
||||
pub config: EnvironmentConfigState,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
|
||||
Reference in New Issue
Block a user