[codex] Preserve plugin apps in connector listings (#27602)

## Context

This is PR4 in the plugin auth-routing stack. The earlier PRs make
plugin surface projection auth-aware and narrow App/MCP conflicts by App
declaration name. This PR keeps connector listing paths aligned with
that projected plugin App set.

This means ChatGPT/SIWC users will still see plugin-provided Apps in
connector listing surfaces like the Apps/connector picker, while API-key
users will not see Apps they cannot use.

## Stack

- PR1: #27652 seed plugin manager auth at construction.
- PR2: #27459 route plugin surfaces by auth mode.
- PR3: #27607 dedupe plugin MCP servers by App declaration name.
- PR4: #27602 preserve plugin Apps in connector listings.
- PR5: #27461 skip install-time plugin MCP OAuth for matching App
routes.

## Summary

- Have app-server compute effective plugin Apps from the existing
PluginsManager and pass them into connector listing.
- Keep plugin Apps visible in Apps/connector listing for ChatGPT/SIWC
users.
- Keep API-key-style auth from surfacing plugin Apps in connector
listings.

## Validation

```bash
cargo test -p codex-chatgpt connectors::tests
cargo test -p codex-app-server list_apps_includes_plugin_apps_for_chatgpt_auth
git diff --check
```
This commit is contained in:
felixxia-oai
2026-06-15 13:50:08 +01:00
committed by GitHub
Unverified
parent 127224cacc
commit cededa26c5
7 changed files with 153 additions and 53 deletions
@@ -89,6 +89,7 @@ impl AppsRequestProcessor {
let outgoing = Arc::clone(&self.outgoing);
let environment_manager = self.thread_manager.environment_manager();
let mcp_manager = self.thread_manager.mcp_manager();
let plugins_manager = self.thread_manager.plugins_manager();
let shutdown_token = self.shutdown_token.child_token();
tokio::spawn(async move {
tokio::select! {
@@ -100,6 +101,7 @@ impl AppsRequestProcessor {
config,
environment_manager,
mcp_manager,
plugins_manager,
) => {}
}
});
@@ -117,14 +119,22 @@ impl AppsRequestProcessor {
config: Config,
environment_manager: Arc<EnvironmentManager>,
mcp_manager: Arc<McpManager>,
plugins_manager: Arc<PluginsManager>,
) {
let retry_params = params.clone();
let retry_config = config.clone();
let retry_environment_manager = Arc::clone(&environment_manager);
let retry_mcp_manager = Arc::clone(&mcp_manager);
let result =
Self::apps_list_response(&outgoing, params, config, environment_manager, mcp_manager)
.await;
let retry_plugins_manager = Arc::clone(&plugins_manager);
let result = Self::apps_list_response(
&outgoing,
params,
config,
environment_manager,
mcp_manager,
plugins_manager,
)
.await;
let should_retry = result
.as_ref()
.is_ok_and(|(_, codex_apps_ready)| !codex_apps_ready);
@@ -141,6 +151,7 @@ impl AppsRequestProcessor {
retry_config,
retry_environment_manager,
retry_mcp_manager,
retry_plugins_manager,
)
.await
{
@@ -155,6 +166,7 @@ impl AppsRequestProcessor {
config: Config,
environment_manager: Arc<EnvironmentManager>,
mcp_manager: Arc<McpManager>,
plugins_manager: Arc<PluginsManager>,
) -> Result<(AppsListResponse, bool), JSONRPCErrorError> {
let AppsListParams {
cursor,
@@ -170,9 +182,13 @@ impl AppsRequestProcessor {
None => 0,
};
let plugin_apps = plugins_manager
.plugins_for_config(&config.plugins_config_input())
.await
.effective_apps();
let (mut accessible_connectors, mut all_connectors) = tokio::join!(
connectors::list_cached_accessible_connectors_from_mcp_tools(&config),
connectors::list_cached_all_connectors(&config)
connectors::list_cached_all_connectors(&config, &plugin_apps)
);
let cached_all_connectors = all_connectors.clone();
@@ -193,10 +209,15 @@ impl AppsRequestProcessor {
});
let all_config = config.clone();
let all_plugin_apps = plugin_apps.clone();
tokio::spawn(async move {
let result = connectors::list_all_connectors_with_options(&all_config, force_refetch)
.await
.map_err(|err| format!("failed to list apps: {err}"));
let result = connectors::list_all_connectors_with_options(
&all_config,
force_refetch,
&all_plugin_apps,
)
.await
.map_err(|err| format!("failed to list apps: {err}"));
let _ = tx.send(AppListLoadResult::Directory(result));
});
@@ -1578,7 +1578,7 @@ impl PluginRequestProcessor {
.as_ref()
.map(plugin_app_category_by_id_from_value)
.unwrap_or_default();
let all_connectors = connectors::list_cached_all_connectors(&config)
let all_connectors = connectors::list_cached_all_connectors(&config, &[])
.await
.unwrap_or_default();
connectors::connectors_for_plugin_apps(all_connectors, &plugin_apps)
@@ -1630,7 +1630,7 @@ impl PluginRequestProcessor {
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*/ false),
connectors::list_all_connectors_with_options(config, /*force_refetch*/ false, &[]),
connectors::list_accessible_connectors_from_mcp_tools_with_mcp_manager(
config,
/*force_refetch*/ true,
@@ -1646,7 +1646,7 @@ impl PluginRequestProcessor {
plugin = plugin_id,
"failed to load app metadata after plugin install: {err:#}"
);
connectors::list_cached_all_connectors(config)
connectors::list_cached_all_connectors(config, &[])
.await
.unwrap_or_default()
}
@@ -1910,16 +1910,21 @@ async fn load_plugin_app_summaries(
return Vec::new();
}
let connectors =
match connectors::list_all_connectors_with_options(config, /*force_refetch*/ false).await {
Ok(connectors) => connectors,
Err(err) => {
warn!("failed to load app metadata for plugin/read: {err:#}");
connectors::list_cached_all_connectors(config)
.await
.unwrap_or_default()
}
};
let connectors = match connectors::list_all_connectors_with_options(
config,
/*force_refetch*/ false,
&[],
)
.await
{
Ok(connectors) => connectors,
Err(err) => {
warn!("failed to load app metadata for plugin/read: {err:#}");
connectors::list_cached_all_connectors(config, &[])
.await
.unwrap_or_default()
}
};
let plugin_connectors = connectors::connectors_for_plugin_apps(connectors, plugin_apps);