Support multiple managed environments (#18401)

## Summary
- refactor EnvironmentManager to own keyed environments with
default/local lookup helpers
- keep remote exec-server client creation lazy until exec/fs use
- preserve disabled agent environment access separately from internal
local environment access

## Validation
- not run (per Codex worktree instruction to avoid tests/builds unless
requested)

---------

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
starr-openai
2026-04-21 15:29:35 -07:00
committed by GitHub
Unverified
parent 27d9673273
commit ddbe2536be
44 changed files with 606 additions and 432 deletions
@@ -260,6 +260,7 @@ use codex_core_plugins::loader::load_plugin_mcp_servers;
use codex_core_plugins::manifest::PluginManifestInterface;
use codex_core_plugins::marketplace::MarketplaceError;
use codex_core_plugins::marketplace::MarketplacePluginSource;
use codex_exec_server::EnvironmentManager;
use codex_exec_server::LOCAL_FS;
use codex_features::FEATURES;
use codex_features::Feature;
@@ -5677,27 +5678,17 @@ impl CodexMessageProcessor {
.to_mcp_config(self.thread_manager.plugins_manager().as_ref())
.await;
let auth = self.auth_manager.auth().await;
let runtime_environment = match self.thread_manager.environment_manager().current().await {
Ok(Some(environment)) => {
let environment_manager = self.thread_manager.environment_manager();
let runtime_environment = match environment_manager.default_environment() {
Some(environment) => {
// Status listing has no turn cwd. This fallback is used only
// by executor-backed stdio MCPs whose config omits `cwd`.
McpRuntimeEnvironment::new(environment, config.cwd.to_path_buf())
}
Ok(None) => McpRuntimeEnvironment::new(
Arc::new(codex_exec_server::Environment::default()),
None => McpRuntimeEnvironment::new(
environment_manager.local_environment(),
config.cwd.to_path_buf(),
),
Err(err) => {
// TODO(aibrahim): Investigate degrading MCP status listing when
// executor environment creation fails.
let error = JSONRPCErrorError {
code: INTERNAL_ERROR_CODE,
message: format!("failed to create environment: {err}"),
data: None,
};
self.outgoing.send_error(request, error).await;
return;
}
};
tokio::spawn(async move {
@@ -5856,25 +5847,14 @@ impl CodexMessageProcessor {
.to_mcp_config(self.thread_manager.plugins_manager().as_ref())
.await;
let auth = self.auth_manager.auth().await;
let runtime_environment = match self.thread_manager.environment_manager().current().await {
Ok(Some(environment)) => {
// Resource reads without a thread have no turn cwd. This fallback
// is used only by executor-backed stdio MCPs whose config omits `cwd`.
McpRuntimeEnvironment::new(environment, config.cwd.to_path_buf())
}
Ok(None) => McpRuntimeEnvironment::new(
Arc::new(codex_exec_server::Environment::default()),
config.cwd.to_path_buf(),
),
Err(err) => {
let error = JSONRPCErrorError {
code: INTERNAL_ERROR_CODE,
message: format!("failed to create environment: {err}"),
data: None,
};
self.outgoing.send_error(request_id, error).await;
return;
}
let runtime_environment = {
let environment_manager = self.thread_manager.environment_manager();
let environment = environment_manager
.default_environment()
.unwrap_or_else(|| environment_manager.local_environment());
// Resource reads without a thread have no turn cwd. This fallback
// is used only by executor-backed stdio MCPs whose config omits `cwd`.
McpRuntimeEnvironment::new(environment, config.cwd.to_path_buf())
};
tokio::spawn(async move {
@@ -6222,8 +6202,9 @@ impl CodexMessageProcessor {
let request = request_id.clone();
let outgoing = Arc::clone(&self.outgoing);
let environment_manager = self.thread_manager.environment_manager();
tokio::spawn(async move {
Self::apps_list_task(outgoing, request, params, config).await;
Self::apps_list_task(outgoing, request, params, config, environment_manager).await;
});
}
@@ -6232,6 +6213,7 @@ impl CodexMessageProcessor {
request_id: ConnectionRequestId,
params: AppsListParams,
config: Config,
environment_manager: Arc<EnvironmentManager>,
) {
let AppsListParams {
cursor,
@@ -6266,12 +6248,15 @@ impl CodexMessageProcessor {
let accessible_config = config.clone();
let accessible_tx = tx.clone();
tokio::spawn(async move {
let result = connectors::list_accessible_connectors_from_mcp_tools_with_options(
&accessible_config,
force_refetch,
)
.await
.map_err(|err| format!("failed to load accessible apps: {err}"));
let result =
connectors::list_accessible_connectors_from_mcp_tools_with_environment_manager(
&accessible_config,
force_refetch,
&environment_manager,
)
.await
.map(|status| status.connectors)
.map_err(|err| format!("failed to load accessible apps: {err}"));
let _ = accessible_tx.send(AppListLoadResult::Accessible(result));
});
@@ -6461,23 +6446,11 @@ impl CodexMessageProcessor {
};
let skills_manager = self.thread_manager.skills_manager();
let plugins_manager = self.thread_manager.plugins_manager();
let fs = match self.thread_manager.environment_manager().current().await {
Ok(Some(environment)) => Some(environment.get_filesystem()),
Ok(None) => None,
Err(err) => {
self.outgoing
.send_error(
request_id,
JSONRPCErrorError {
code: INTERNAL_ERROR_CODE,
message: format!("failed to create environment: {err}"),
data: None,
},
)
.await;
return;
}
};
let fs = self
.thread_manager
.environment_manager()
.default_environment()
.map(|environment| environment.get_filesystem());
let mut data = Vec::new();
for cwd in cwds {
let extra_roots = extra_roots_by_cwd
@@ -6793,8 +6766,13 @@ impl CodexMessageProcessor {
return;
}
};
let app_summaries =
plugin_app_helpers::load_plugin_app_summaries(&config, &outcome.plugin.apps).await;
let environment_manager = self.thread_manager.environment_manager();
let app_summaries = plugin_app_helpers::load_plugin_app_summaries(
&config,
&outcome.plugin.apps,
&environment_manager,
)
.await;
let visible_skills = outcome
.plugin
.skills
@@ -6971,10 +6949,11 @@ impl CodexMessageProcessor {
) {
Vec::new()
} else {
let environment_manager = self.thread_manager.environment_manager();
let (all_connectors_result, accessible_connectors_result) = tokio::join!(
connectors::list_all_connectors_with_options(&config, /*force_refetch*/ true),
connectors::list_accessible_connectors_from_mcp_tools_with_options_and_status(
&config, /*force_refetch*/ true
connectors::list_accessible_connectors_from_mcp_tools_with_environment_manager(
&config, /*force_refetch*/ true, &environment_manager
),
);