Apply permission profile updates to future turn environments (#37031)

## What changed

- Include the current permission profile in each turn environment's configuration.
- Refresh retained environment configurations when session settings change, while preserving the configuration already captured by active turns.
- Keep environment connection and shell snapshot resolution reusable across configuration updates.

## Testing

- Cover permission profile updates applied through both session settings updates and per-turn updates.
- Verify that pending environment resolutions use the configuration captured by their turn and that configuration-only updates do not retry failed environments.

GitOrigin-RevId: 923694f0729b87a118d7635ade3a8ca9bcd4e7b1
This commit is contained in:
sayan-oai
2026-08-05 05:30:51 +00:00
committed by copyberry
parent 56b82e676c
commit 30d99232f4
15 changed files with 339 additions and 116 deletions
+6 -1
View File
@@ -1531,10 +1531,15 @@ impl Session {
let permission_profile_changed =
previous_permission_profile != updated_permission_profile;
let mcp_inputs_changed = state.session_configuration.mcp_inputs_differ(&updated);
let environment_config = updated.environment_config();
if updates.environments.is_some() {
self.services
.turn_environments
.update_selections(updated.environment_selections());
.update_selections(updated.environment_selections(), &environment_config);
} else if state.session_configuration.environment_config() != environment_config {
self.services
.turn_environments
.update_environment_configs(&environment_config);
}
state.session_configuration = updated;
if mcp_inputs_changed {
+15 -4
View File
@@ -156,6 +156,16 @@ impl SessionConfiguration {
&self.permission_profile_state
}
pub(super) fn environment_config(&self) -> EnvironmentConfig {
EnvironmentConfig {
allow_login_shell: self
.original_config_do_not_use
.permissions
.allow_login_shell,
permission_profile: self.permission_profile_state.snapshot(),
}
}
pub(super) fn permission_profile(&self) -> PermissionProfile {
self.permission_profile_state.permission_profile().clone()
}
@@ -943,14 +953,15 @@ impl Session {
environment_manager,
default_shell.clone(),
// Temporary: preserve thread-level behavior until environments supply config.
EnvironmentConfig {
allow_login_shell: config.permissions.allow_login_shell,
},
session_configuration.environment_config(),
shell_snapshot,
inherited_environments.unwrap_or_default(),
config.features.enabled(Feature::DeferredExecutor),
));
turn_environments.update_selections(session_configuration.environment_selections());
turn_environments.update_selections(
session_configuration.environment_selections(),
&session_configuration.environment_config(),
);
let resolved_environments = turn_environments.snapshot().await;
let agents_md_manager = Arc::new(AgentsMdManager::new(user_instructions));
let plugin_skill_warmup = warm_plugins_and_skills_for_session_init(
+80 -21
View File
@@ -1,5 +1,4 @@
use super::mcp_refresh::McpRefresh;
use super::turn_context::EnvironmentConfig;
use super::turn_context::TurnEnvironment;
use super::*;
use crate::agents_md_manager::AgentsMdManager;
@@ -4871,17 +4870,15 @@ async fn resolved_environments_for_configuration(
let turn_environments = ThreadEnvironments::new(
Arc::clone(&environment_manager),
default_user_shell(),
EnvironmentConfig {
allow_login_shell: session_configuration
.original_config_do_not_use
.permissions
.allow_login_shell,
},
session_configuration.environment_config(),
ShellSnapshot::disabled(),
TurnEnvironmentSnapshot::default(),
/*non_blocking_snapshots*/ false,
);
turn_environments.update_selections(session_configuration.environment_selections());
turn_environments.update_selections(
session_configuration.environment_selections(),
&session_configuration.environment_config(),
);
(environment_manager, turn_environments.snapshot().await)
}
@@ -5363,6 +5360,61 @@ async fn session_update_settings_does_not_rewrite_sticky_environment_cwds() {
assert_eq!(next_turn.config.cwd, turn_cwd);
}
#[tokio::test]
async fn permission_profile_updates_apply_to_next_turn_environment() {
for apply_on_turn_start in [false, true] {
let (session, active_turn) = make_session_and_context().await;
let active_environment_config = active_turn
.environments
.primary()
.expect("active turn environment")
.config
.clone();
let profile_root = active_turn.config.cwd.join("profile-root");
let active_profile = ActivePermissionProfile::read_only();
let updates = SessionSettingsUpdate {
permission_profile: Some(PermissionProfile::read_only()),
active_permission_profile: Some(active_profile.clone()),
profile_workspace_roots: Some(vec![profile_root.clone()]),
..Default::default()
};
let next_turn = if apply_on_turn_start {
session
.new_turn_with_sub_id("permission-profile-update".to_string(), updates)
.await
.expect("turn permission profile update should succeed")
} else {
session
.update_settings(updates)
.await
.expect("permission profile update should succeed");
session.new_default_turn().await
};
let next_environment = next_turn
.environments
.primary()
.expect("next turn environment");
let mut expected_environment_config = active_environment_config.clone();
expected_environment_config.permission_profile =
PermissionProfileSnapshot::active_with_profile_workspace_roots(
PermissionProfile::read_only(),
active_profile,
vec![profile_root],
);
assert_eq!(next_environment.config, expected_environment_config);
assert_eq!(
active_turn
.environments
.primary()
.expect("active turn environment")
.config,
active_environment_config
);
}
}
#[tokio::test]
async fn relative_cwd_update_without_environments_resolves_under_session_cwd() {
let (session, _turn_context) = make_session_and_context().await;
@@ -5681,9 +5733,7 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
let turn_environments = Arc::new(ThreadEnvironments::new(
environment_manager,
default_user_shell(),
EnvironmentConfig {
allow_login_shell: config.permissions.allow_login_shell,
},
session_configuration.environment_config(),
ShellSnapshot::disabled(),
resolved_environments,
/*non_blocking_snapshots*/ false,
@@ -7077,14 +7127,18 @@ async fn turn_environments_set_primary_environment() {
#[tokio::test]
async fn default_turn_does_not_overlay_legacy_fallback_cwd_onto_stored_thread_environments() {
let (session, _turn_context, _rx) = make_session_and_context_with_rx().await;
let (session, initial_turn, _rx) = make_session_and_context_with_rx().await;
let session_cwd = session.get_config().await.cwd.clone();
let selected_cwd =
AbsolutePathBuf::try_from(session_cwd.as_path().join("selected")).expect("absolute path");
session
.services
.turn_environments
.update_selections(&[local(selected_cwd.clone())]);
session.services.turn_environments.update_selections(
&[local(selected_cwd.clone())],
&initial_turn
.environments
.primary()
.expect("initial turn environment")
.config,
);
{
let mut state = session.state.lock().await;
state.session_configuration.environments.environments = vec![local(selected_cwd.clone())];
@@ -7113,10 +7167,17 @@ async fn default_turn_does_not_overlay_legacy_fallback_cwd_onto_stored_thread_en
#[tokio::test]
async fn default_turn_honors_empty_stored_thread_environments() {
let (session, _turn_context, _rx) = make_session_and_context_with_rx().await;
let (session, initial_turn, _rx) = make_session_and_context_with_rx().await;
let session_cwd = session.get_config().await.cwd.clone();
session.services.turn_environments.update_selections(&[]);
session.services.turn_environments.update_selections(
&[],
&initial_turn
.environments
.primary()
.expect("initial turn environment")
.config,
);
{
let mut state = session.state.lock().await;
state.session_configuration.environments.environments = Vec::new();
@@ -7888,9 +7949,7 @@ where
let turn_environments = Arc::new(ThreadEnvironments::new(
environment_manager,
default_user_shell(),
EnvironmentConfig {
allow_login_shell: config.permissions.allow_login_shell,
},
session_configuration.environment_config(),
ShellSnapshot::disabled(),
resolved_turn_environments.clone(),
/*non_blocking_snapshots*/ false,
+8 -1
View File
@@ -29,6 +29,7 @@ pub(crate) type ShellSnapshotTask = Shared<BoxFuture<'static, Option<Arc<ShellSn
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct EnvironmentConfig {
pub(crate) allow_login_shell: bool,
pub(crate) permission_profile: PermissionProfileSnapshot,
}
#[derive(Clone)]
@@ -611,10 +612,16 @@ impl Session {
});
let new_config = notify_config_contributors
.then(|| Self::build_effective_session_config(&next));
let environment_config = next.environment_config();
if updates.environments.is_some() {
self.services
.turn_environments
.update_selections(next.environment_selections());
.update_selections(next.environment_selections(), &environment_config);
} else if state.session_configuration.environment_config() != environment_config
{
self.services
.turn_environments
.update_environment_configs(&environment_config);
}
if mcp_inputs_changed {
self.mark_mcp_runtime_dirty();