Centralize thread environment selection state (#38423)

## What changed

- Make `ThreadEnvironments` the source of truth for live environment selections and use it when building configuration snapshots, permission profiles, per-turn configuration, and MCP refresh inputs.
- Keep settings previews side-effect free while applying accepted environment updates consistently to subsequent turns.
- Move `EnvironmentConfig` into `codex-protocol` so environment ownership no longer depends on a core-only type.

## Testing

- Cover environment previews and snapshots, accepted and rejected steer updates, and active-turn environment stability.

GitOrigin-RevId: 7bcf8fb5df1215f720e4e9c419541a51a871aa5e
This commit is contained in:
sayan-oai authored and copyberry committed 2026-08-13 19:25:05 +00:00
1 parent 588e18aae5
commit 781445f7c6
16 files changed
+428 -327

No files matched your search

+23 -28
View File
@@ -127,7 +127,6 @@ use codex_protocol::protocol::TurnAbortReason;
use codex_protocol::protocol::TurnContextItem;
use codex_protocol::protocol::TurnContextNetworkItem;
use codex_protocol::protocol::TurnEnvironmentSelection;
use codex_protocol::protocol::TurnEnvironmentSelections;
use codex_protocol::protocol::W3cTraceContext;
use codex_protocol::protocol::WorldStateItem;
use codex_protocol::request_permissions::PermissionGrantScope;
@@ -697,10 +696,7 @@ impl Session {
approvals_reviewer: config.approvals_reviewer,
permission_profile_state: session_permission_profile_state_from_config(&config)?,
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
environments: TurnEnvironmentSelections::new(
config.cwd.clone(),
environment_selections,
),
legacy_fallback_cwd: config.cwd.clone(),
codex_home: config.codex_home.clone(),
thread_name: None,
original_config_do_not_use: Arc::clone(&config),
@@ -718,7 +714,7 @@ impl Session {
user_shell_override,
};
session_configuration
.validate_auto_review_requirement()
.validate_auto_review_requirement(&environment_selections)
.map_err(|err| CodexErr::InvalidRequest(err.to_string()))?;
// Generate a unique ID for the lifetime of this session.
@@ -727,6 +723,7 @@ impl Session {
let session = Box::pin(Session::new(
session_configuration,
&environment_selections,
config.clone(),
user_instructions,
installation_id,
@@ -1570,7 +1567,8 @@ impl Session {
let notify_config_contributors = !self.services.extensions.config_contributors().is_empty();
let (previous_config, new_config, permission_profile_changed, mcp_inputs_changed) = {
let mut state = self.state.lock().await;
let updated = match state.session_configuration.apply(&updates) {
let updated = match self.apply_session_settings(&state.session_configuration, &updates)
{
Ok(updated) => updated,
Err(err) => {
warn!("rejected session settings update: {err}");
@@ -1579,25 +1577,26 @@ impl Session {
};
let previous_config = notify_config_contributors
.then(|| Self::build_effective_session_config(&state.session_configuration));
let new_config =
notify_config_contributors.then(|| Self::build_effective_session_config(&updated));
.then(|| self.build_effective_session_config(&state.session_configuration));
let previous_permission_profile = state.session_configuration.permission_profile();
let updated_permission_profile = updated.permission_profile();
let permission_profile_changed =
previous_permission_profile != updated_permission_profile;
let mcp_inputs_changed = state.session_configuration.mcp_inputs_differ(&updated);
let mcp_inputs_changed =
self.mcp_inputs_differ(&state.session_configuration, &updated, &updates);
let environment_config = updated.turn_environment_config();
if updates.environments.is_some() {
if let Some(environments) = &updates.environments {
self.services
.turn_environments
.update_selections(updated.environment_selections(), &environment_config);
.update_selections(&environments.environments, &environment_config);
} else if state.session_configuration.turn_environment_config() != environment_config {
self.services
.turn_environments
.update_environment_configs(&environment_config);
}
state.session_configuration = updated;
let new_config = notify_config_contributors
.then(|| self.build_effective_session_config(&state.session_configuration));
if mcp_inputs_changed {
self.mark_mcp_runtime_dirty();
}
@@ -1624,15 +1623,19 @@ impl Session {
updates: &SessionSettingsUpdate,
) -> ConstraintResult<ThreadConfigSnapshot> {
let state = self.state.lock().await;
state
.session_configuration
.apply(updates)
.map(|configuration| configuration.thread_config_snapshot())
let configuration = self.apply_session_settings(&state.session_configuration, updates)?;
let environments = updates.environments.as_ref().map_or_else(
|| self.services.turn_environments.selections(),
|environments| environments.environments.clone(),
);
Ok(configuration.thread_config_snapshot(environments))
}
pub(crate) async fn thread_config_snapshot(&self) -> ThreadConfigSnapshot {
let state = self.state.lock().await;
state.session_configuration.thread_config_snapshot()
state
.session_configuration
.thread_config_snapshot(self.services.turn_environments.selections())
}
pub(crate) async fn set_app_server_client_info(
@@ -1662,14 +1665,6 @@ impl Session {
.map_or_else(Vec::new, |instructions| instructions.sources().collect())
}
pub(crate) async fn thread_environment_selections(&self) -> Vec<TurnEnvironmentSelection> {
let state = self.state.lock().await;
state
.session_configuration
.environment_selections()
.to_vec()
}
pub(crate) async fn set_session_startup_prewarm(
&self,
startup_prewarm: SessionStartupPrewarmHandle,
@@ -1708,7 +1703,7 @@ impl Session {
let (previous_config, new_config, config) = {
let mut state = self.state.lock().await;
let previous_config = notify_config_contributors
.then(|| Self::build_effective_session_config(&state.session_configuration));
.then(|| self.build_effective_session_config(&state.session_configuration));
let mut config = (*state.session_configuration.original_config_do_not_use).clone();
config.config_layer_stack = config
.config_layer_stack
@@ -1727,7 +1722,7 @@ impl Session {
state.session_configuration.original_config_do_not_use = Arc::clone(&config);
self.mark_mcp_runtime_dirty();
let new_config = notify_config_contributors
.then(|| Self::build_effective_session_config(&state.session_configuration));
.then(|| self.build_effective_session_config(&state.session_configuration));
(previous_config, new_config, config)
};
self.emit_config_changed_contributors(previous_config.as_ref(), new_config.as_ref());