From 66edc347ae6c84902b88bc46774712415d2dfd99 Mon Sep 17 00:00:00 2001 From: canvrno-oai Date: Tue, 24 Mar 2026 10:34:19 -0700 Subject: [PATCH] Pretty plugin labels, preserve plugin app provenance during MCP tool refresh (#15606) - Prefer plugin manifest `interface.displayName` for plugin labels. - Preserve plugin provenance when handling `list_mcp_tools` so connector `plugin_display_names` are not clobbered. - Add a TUI test to ensure plugin-owned app mentions are deduped correctly. --- codex-rs/core/src/plugins/manager.rs | 9 +++- codex-rs/tui/src/bottom_pane/chat_composer.rs | 50 +++++++++++++++++++ codex-rs/tui/src/chatwidget.rs | 8 ++- 3 files changed, 65 insertions(+), 2 deletions(-) diff --git a/codex-rs/core/src/plugins/manager.rs b/codex-rs/core/src/plugins/manager.rs index 571535b5f0..4e0eec5327 100644 --- a/codex-rs/core/src/plugins/manager.rs +++ b/codex-rs/core/src/plugins/manager.rs @@ -1550,7 +1550,14 @@ fn load_plugin( }; let manifest_paths = &manifest.paths; - loaded_plugin.manifest_name = Some(manifest.name.clone()); + loaded_plugin.manifest_name = manifest + .interface + .as_ref() + .and_then(|interface| interface.display_name.as_deref()) + .map(str::trim) + .filter(|display_name| !display_name.is_empty()) + .map(str::to_string) + .or_else(|| Some(manifest.name.clone())); loaded_plugin.manifest_description = manifest.description.clone(); loaded_plugin.skill_roots = plugin_skill_roots(plugin_root.as_path(), manifest_paths); let resolved_skills = load_plugin_skills( diff --git a/codex-rs/tui/src/bottom_pane/chat_composer.rs b/codex-rs/tui/src/bottom_pane/chat_composer.rs index bc3ff38599..cbaea91fa0 100644 --- a/codex-rs/tui/src/bottom_pane/chat_composer.rs +++ b/codex-rs/tui/src/bottom_pane/chat_composer.rs @@ -5500,6 +5500,56 @@ mod tests { ); } + #[test] + fn mention_items_hide_plugin_owned_app_duplicates_when_provenance_matches() { + let (tx, _rx) = unbounded_channel::(); + let sender = AppEventSender::new(tx); + let mut composer = ChatComposer::new( + true, + sender, + false, + "Ask Codex to do anything".to_string(), + false, + ); + composer.set_connectors_enabled(true); + composer.set_text_content("$goog".to_string(), Vec::new(), Vec::new()); + composer.set_plugin_mentions(Some(vec![PluginCapabilitySummary { + config_name: "google-calendar@debug".to_string(), + display_name: "Google Calendar".to_string(), + description: None, + has_skills: false, + mcp_server_names: vec!["google-calendar".to_string()], + app_connector_ids: vec![codex_core::plugins::AppConnectorId( + "google_calendar".to_string(), + )], + }])); + composer.set_connector_mentions(Some(ConnectorsSnapshot { + connectors: vec![AppInfo { + id: "google_calendar".to_string(), + name: "Google Calendar".to_string(), + description: Some("Look up events and availability".to_string()), + logo_url: None, + logo_url_dark: None, + distribution_channel: None, + branding: None, + app_metadata: None, + labels: None, + install_url: Some("https://example.test/google-calendar".to_string()), + is_accessible: true, + is_enabled: true, + plugin_display_names: vec!["Google Calendar".to_string()], + }], + })); + + let mentions = composer.mention_items(); + assert_eq!(mentions.len(), 1); + assert_eq!(mentions[0].category_tag, Some("[Plugin]".to_string())); + assert_eq!( + mentions[0].path, + Some("plugin://google-calendar@debug".to_string()) + ); + } + #[test] fn plugin_mention_popup_snapshot() { snapshot_composer_state("plugin_mention_popup", false, |composer| { diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index 84dfc62fcc..9963b95700 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -8946,6 +8946,10 @@ impl ChatWidget { fn on_list_mcp_tools(&mut self, ev: McpListToolsResponseEvent) { if self.connectors_enabled() { + let plugin_provenance = McpManager::new(Arc::new(PluginsManager::new( + self.config.codex_home.clone(), + ))) + .tool_plugin_provenance(&self.config); let mut connectors_by_id: HashMap = HashMap::new(); for tool in ev.tools.values() { let Some(meta) = tool.meta.as_ref().and_then(serde_json::Value::as_object) else { @@ -8988,7 +8992,9 @@ impl ChatWidget { install_url: None, is_accessible: true, is_enabled: true, - plugin_display_names: Vec::new(), + plugin_display_names: plugin_provenance + .plugin_display_names_for_connector_id(connector_id) + .to_vec(), } }); }