Honor thread-level plugin exclusions across runtime capabilities (#44655)

## What changed

- Apply `disabled_plugin_ids` to plugin skills, recommendations, hooks, and MCP servers without modifying shared plugin state.
- Activate selection changes when the next task starts, keeping pending settings from changing the active runtime.
- Hide app tools contributed only by disabled plugins from model tool catalogs and search. Preserve connectors shared with enabled plugins and existing direct Apps RPC behavior.
- Include plugin identity in MCP approval keys and use that identity when persisting plugin tool approvals.

## Testing

Add regression coverage for disabling and re-enabling plugin skills and tools, deferred activation of settings, shared connector ownership, selected plugin servers, executor hooks, and plugin-specific approval keys and persistence.

GitOrigin-RevId: 1153ff6aa7ab6953d50e838da05203904ecd2956
This commit is contained in:
Matthew Zeng authored and copyberry committed 2026-09-10 21:51:34 +00:00
1 parent 5c94936b56
commit 8570091e14
27 files changed
+565 -171

No files matched your search

+9 -3
View File
@@ -98,11 +98,12 @@ impl Session {
config: &Config,
) -> (McpConfig, McpRuntimeContext) {
let originator = self.originator().await;
let (session_source, host_fallback_cwd) = {
let (session_source, host_fallback_cwd, disabled_plugin_ids) = {
let state = self.state.lock().await;
(
state.session_configuration.session_source.clone(),
state.session_configuration.cwd().clone(),
state.active_disabled_plugin_ids.clone(),
)
};
let environments = self.services.turn_environments.snapshot().await;
@@ -128,6 +129,7 @@ impl Session {
McpThreadIdentity {
session_source: &session_source,
originator: &originator,
disabled_plugin_ids: &disabled_plugin_ids,
environments: McpEnvironmentScope::Live(&self.services.turn_environments),
},
&ready_selected_capability_roots,
@@ -233,6 +235,7 @@ impl Session {
McpThreadIdentity {
session_source: &desired.session_source,
originator: &desired.originator,
disabled_plugin_ids: &desired.disabled_plugin_ids,
environments: McpEnvironmentScope::Live(&self.services.turn_environments),
},
&ready_selected_capability_roots,
@@ -299,6 +302,7 @@ impl Session {
McpThreadIdentity {
session_source: &desired.session_source,
originator: &desired.originator,
disabled_plugin_ids: &desired.disabled_plugin_ids,
environments: McpEnvironmentScope::Live(&self.services.turn_environments),
},
&ready_selected_capability_roots,
@@ -662,12 +666,13 @@ impl Session {
return;
};
let auth = self.services.auth_manager.auth().await;
{
let disabled_plugin_ids = {
let mut state = self.state.lock().await;
let mut config = (*state.session_configuration.original_config_do_not_use).clone();
config.mcp_servers = refresh_config.mcp_servers.clone();
state.session_configuration.original_config_do_not_use = Arc::new(config);
}
state.active_disabled_plugin_ids.clone()
};
let ready_selected_capability_roots = self
.services
.mcp_runtime
@@ -690,6 +695,7 @@ impl Session {
McpThreadIdentity {
session_source: &turn_context.session_source,
originator: &turn_context.originator,
disabled_plugin_ids: &disabled_plugin_ids,
environments: McpEnvironmentScope::Live(&self.services.turn_environments),
},
&ready_selected_capability_roots,
+8 -2
View File
@@ -28,6 +28,7 @@ pub(super) struct McpDesiredState {
pub(super) session_source: SessionSource,
pub(super) environments: TurnEnvironmentSnapshot,
pub(super) local_process_cwd: PathBuf,
pub(super) disabled_plugin_ids: Vec<String>,
}
impl Session {
@@ -75,9 +76,12 @@ impl Session {
&self,
auth: Option<CodexAuth>,
) -> McpDesiredState {
let session_configuration = {
let (session_configuration, disabled_plugin_ids) = {
let state = self.state.lock().await;
state.session_configuration.clone()
(
state.session_configuration.clone(),
state.active_disabled_plugin_ids.clone(),
)
};
let environments = self.services.turn_environments.snapshot().await;
let cwd = environments
@@ -98,6 +102,7 @@ impl Session {
session_source: session_configuration.session_source.clone(),
environments,
local_process_cwd,
disabled_plugin_ids,
}
}
@@ -124,6 +129,7 @@ impl Session {
session_source: session_configuration.session_source.clone(),
environments: resolved_environments.clone(),
local_process_cwd,
disabled_plugin_ids: session_configuration.disabled_plugin_ids.clone(),
};
self.publish_mcp_runtime(
&desired,
+12 -3
View File
@@ -235,6 +235,7 @@ mod mcp_prewarm;
mod mcp_refresh;
mod mcp_runtime;
pub(crate) mod multi_agents;
mod plugin_selection;
mod realtime_history;
mod retained_context;
mod review;
@@ -2006,11 +2007,13 @@ impl Session {
}
pub(crate) async fn refresh_hooks(&self, config: Arc<Config>) {
let disabled_plugin_ids = self.state.lock().await.active_disabled_plugin_ids.clone();
let environments = self.services.turn_environments.snapshot().await;
let hooks_config = build_hooks_config(
config.as_ref(),
self.services.plugins_manager.as_ref(),
environments.single_local_environment(),
&disabled_plugin_ids,
)
.await;
@@ -2020,7 +2023,8 @@ impl Session {
if Arc::ptr_eq(
&state.session_configuration.original_config_do_not_use,
&config,
) {
) && state.active_disabled_plugin_ids == disabled_plugin_ids
{
let hooks = self.hooks().reconfigured(hooks_config);
self.services.hooks.store(Arc::new(hooks));
}
@@ -4112,7 +4116,8 @@ impl Session {
.services
.plugins_manager
.plugins_for_config(&turn_context.config.plugins_config_input())
.await;
.await
.without_plugins(&turn_context.disabled_plugin_ids);
let recommended_plugin_candidates = if turn_context
.config
.features
@@ -4874,6 +4879,7 @@ async fn build_hooks_config(
config: &Config,
plugins_manager: &PluginsManager,
environment: Option<&TurnEnvironment>,
disabled_plugin_ids: &[String],
) -> HooksConfig {
let (hook_shell_program, hook_shell_argv) = environment
.and_then(|environment| environment.shell.as_ref())
@@ -4885,7 +4891,10 @@ async fn build_hooks_config(
})
.unwrap_or_default();
let plugins_input = config.plugins_config_input();
let plugin_outcome = plugins_manager.plugins_for_config(&plugins_input).await;
let plugin_outcome = plugins_manager
.plugins_for_config(&plugins_input)
.await
.without_plugins(disabled_plugin_ids);
let plugin_hook_sources = plugin_outcome.effective_plugin_hook_sources();
let plugin_hook_load_warnings = plugin_outcome.effective_plugin_hook_warnings();
HooksConfig {
@@ -0,0 +1,32 @@
//! Activates an admitted task's plugin selection without changing pending settings.
use std::sync::Arc;
use super::Session;
use super::turn_context::TurnContext;
impl Session {
pub(crate) async fn activate_plugin_selection(&self, turn_context: &TurnContext) {
let config = {
let mut state = self.state.lock().await;
if state.active_disabled_plugin_ids != turn_context.disabled_plugin_ids {
state.active_disabled_plugin_ids = turn_context.disabled_plugin_ids.clone();
self.mark_mcp_runtime_dirty();
}
Arc::clone(&state.session_configuration.original_config_do_not_use)
};
let plugin_outcome = self
.services
.plugins_manager
.plugins_for_config(&config.plugins_config_input())
.await
.without_plugins(&turn_context.disabled_plugin_ids);
// Cache changes from another process do not notify this session's hook runtime.
if !self.hooks().matches_plugin_hooks(
plugin_outcome.iter_effective_plugin_hook_sources(),
plugin_outcome.iter_effective_plugin_hook_warnings(),
) {
self.refresh_hooks(config).await;
}
}
}
+5
View File
@@ -996,6 +996,7 @@ impl Session {
let thread_extension_data_for_mcp = &thread_extension_data;
let mcp_originator = session_configuration.originator.clone();
let mcp_session_source = session_configuration.session_source.clone();
let mcp_disabled_plugin_ids = session_configuration.disabled_plugin_ids.clone();
let mcp_runtime_cwd = environment_selections
.first()
.and_then(|environment| environment.cwd.to_abs_path().ok())
@@ -1025,6 +1026,7 @@ impl Session {
McpThreadIdentity {
session_source: &mcp_session_source,
originator: &mcp_originator,
disabled_plugin_ids: &mcp_disabled_plugin_ids,
environments: McpEnvironmentScope::Initial(environment_selections),
},
/*ready_selected_capability_roots*/ &[],
@@ -1340,6 +1342,7 @@ impl Session {
),
);
state.base_instructions_provenance = base_instructions_provenance.clone();
state.active_disabled_plugin_ids = session_configuration.disabled_plugin_ids.clone();
let managed_network_requirements_configured = config
.config_layer_stack
.requirements_toml()
@@ -1419,6 +1422,7 @@ impl Session {
&config,
plugins_manager.as_ref(),
resolved_environments.single_local_environment(),
&session_configuration.disabled_plugin_ids,
)
.await;
let (hooks, async_hook_results) = Hooks::new(
@@ -1672,6 +1676,7 @@ impl Session {
McpThreadIdentity {
session_source: &session_configuration.session_source,
originator: &session_configuration.originator,
disabled_plugin_ids: &session_configuration.disabled_plugin_ids,
environments: McpEnvironmentScope::Live(
&sess.services.turn_environments,
),
+4 -2
View File
@@ -878,7 +878,8 @@ async fn required_mcp_servers_for_input(
.services
.plugins_manager
.plugins_for_config(&turn_context.config.plugins_config_input())
.await;
.await
.without_plugins(&turn_context.disabled_plugin_ids);
let current_config = sess.services.mcp_runtime.current_config();
let mentioned_plugins =
collect_explicit_plugin_mentions(user_input, loaded_plugins.capability_summaries());
@@ -1661,7 +1662,8 @@ pub(crate) async fn prepare_tool_recommendations(
.plugins_manager
.plugins_for_config(&turn_context.config.plugins_config_input())
.instrument(trace_span!("built_tools.load_plugins"))
.await;
.await
.without_plugins(&turn_context.disabled_plugin_ids);
let tool_suggest_is_enabled = tool_suggest_enabled(turn_context);
let auth = if tool_suggest_is_enabled {
sess.services.auth_manager.auth().await
+2 -12
View File
@@ -1039,18 +1039,8 @@ impl Session {
.services
.plugins_manager
.plugins_for_config(&plugins_input)
.await;
// Cache changes from another process do not notify this session's hook runtime.
if !self.hooks().matches_plugin_hooks(
plugin_outcome.iter_effective_plugin_hook_sources(),
plugin_outcome.iter_effective_plugin_hook_warnings(),
) {
// Keep the refresh state out of the enclosing turn-construction future.
Box::pin(self.refresh_hooks(Arc::clone(
&session_configuration.original_config_do_not_use,
)))
.await;
}
.await
.without_plugins(&session_configuration.disabled_plugin_ids);
let trusted_plugin_roots = TrustedPluginRoots::from_plugin_load_outcome(
&plugin_outcome,
per_turn_config.codex_home.as_path(),