add @plugin mentions (#13510)

## Note-- added plugin mentions via @, but that conflicts with file
mentions

depends and builds upon #13433.

- introduces explicit `@plugin` mentions. this injects the plugin's mcp
servers, app names, and skill name format into turn context as a dev
message.
- we do not yet have UI for these mentions, so we currently parse raw
text (as opposed to skills and apps which have UI chips, autocomplete,
etc.) this depends on a `plugins/list` app-server endpoint we can feed
the UI with, which is upcoming
- also annotate mcp and app tool descriptions with the plugin(s) they
come from. this gives the model a first class way of understanding what
tools come from which plugins, which will help implicit invocation.

### Tests
Added and updated tests, unit and integration. Also confirmed locally a
raw `@plugin` injects the dev message, and the model knows about its
apps, mcps, and skills.
This commit is contained in:
sayan-oai
2026-03-05 16:03:39 -08:00
committed by GitHub
parent 1ed542bf31
commit 4e77ea0ec7
24 changed files with 1067 additions and 181 deletions

View File

@@ -6276,6 +6276,7 @@ async fn apps_popup_refreshes_when_connectors_snapshot_updates() {
install_url: Some("https://example.test/notion".to_string()),
is_accessible: true,
is_enabled: true,
plugin_display_names: Vec::new(),
}],
}),
false,
@@ -6312,6 +6313,7 @@ async fn apps_popup_refreshes_when_connectors_snapshot_updates() {
install_url: Some("https://example.test/notion".to_string()),
is_accessible: true,
is_enabled: true,
plugin_display_names: Vec::new(),
},
codex_chatgpt::connectors::AppInfo {
id: linear_id.to_string(),
@@ -6326,6 +6328,7 @@ async fn apps_popup_refreshes_when_connectors_snapshot_updates() {
install_url: Some("https://example.test/linear".to_string()),
is_accessible: true,
is_enabled: true,
plugin_display_names: Vec::new(),
},
],
}),
@@ -6368,6 +6371,7 @@ async fn apps_refresh_failure_keeps_existing_full_snapshot() {
install_url: Some("https://example.test/notion".to_string()),
is_accessible: true,
is_enabled: true,
plugin_display_names: Vec::new(),
},
codex_chatgpt::connectors::AppInfo {
id: linear_id.to_string(),
@@ -6382,6 +6386,7 @@ async fn apps_refresh_failure_keeps_existing_full_snapshot() {
install_url: Some("https://example.test/linear".to_string()),
is_accessible: false,
is_enabled: true,
plugin_display_names: Vec::new(),
},
];
chat.on_connectors_loaded(
@@ -6406,6 +6411,7 @@ async fn apps_refresh_failure_keeps_existing_full_snapshot() {
install_url: Some("https://example.test/notion".to_string()),
is_accessible: true,
is_enabled: true,
plugin_display_names: Vec::new(),
}],
}),
false,
@@ -6449,6 +6455,7 @@ async fn apps_refresh_failure_with_cached_snapshot_triggers_pending_force_refetc
install_url: Some("https://example.test/notion".to_string()),
is_accessible: true,
is_enabled: true,
plugin_display_names: Vec::new(),
}];
chat.connectors_cache = ConnectorsCacheState::Ready(ConnectorsSnapshot {
connectors: full_connectors.clone(),
@@ -6487,6 +6494,7 @@ async fn apps_partial_refresh_uses_same_filtering_as_full_refresh() {
install_url: Some("https://example.test/notion".to_string()),
is_accessible: true,
is_enabled: true,
plugin_display_names: Vec::new(),
},
codex_chatgpt::connectors::AppInfo {
id: "unit_test_connector_2".to_string(),
@@ -6501,6 +6509,7 @@ async fn apps_partial_refresh_uses_same_filtering_as_full_refresh() {
install_url: Some("https://example.test/linear".to_string()),
is_accessible: false,
is_enabled: true,
plugin_display_names: Vec::new(),
},
];
chat.on_connectors_loaded(
@@ -6527,6 +6536,7 @@ async fn apps_partial_refresh_uses_same_filtering_as_full_refresh() {
install_url: Some("https://example.test/notion".to_string()),
is_accessible: true,
is_enabled: true,
plugin_display_names: Vec::new(),
},
codex_chatgpt::connectors::AppInfo {
id: "connector_openai_hidden".to_string(),
@@ -6541,6 +6551,7 @@ async fn apps_partial_refresh_uses_same_filtering_as_full_refresh() {
install_url: Some("https://example.test/hidden-openai".to_string()),
is_accessible: true,
is_enabled: true,
plugin_display_names: Vec::new(),
},
],
}),
@@ -6587,6 +6598,7 @@ async fn apps_popup_shows_disabled_status_for_installed_but_disabled_apps() {
install_url: Some("https://example.test/notion".to_string()),
is_accessible: true,
is_enabled: false,
plugin_display_names: Vec::new(),
}],
}),
true,
@@ -6640,6 +6652,7 @@ async fn apps_initial_load_applies_enabled_state_from_config() {
install_url: Some("https://example.test/notion".to_string()),
is_accessible: true,
is_enabled: true,
plugin_display_names: Vec::new(),
}],
}),
true,
@@ -6680,6 +6693,7 @@ async fn apps_refresh_preserves_toggled_enabled_state() {
install_url: Some("https://example.test/notion".to_string()),
is_accessible: true,
is_enabled: true,
plugin_display_names: Vec::new(),
}],
}),
true,
@@ -6701,6 +6715,7 @@ async fn apps_refresh_preserves_toggled_enabled_state() {
install_url: Some("https://example.test/notion".to_string()),
is_accessible: true,
is_enabled: true,
plugin_display_names: Vec::new(),
}],
}),
true,
@@ -6748,6 +6763,7 @@ async fn apps_popup_for_not_installed_app_uses_install_only_selected_description
install_url: Some("https://example.test/linear".to_string()),
is_accessible: false,
is_enabled: true,
plugin_display_names: Vec::new(),
}],
}),
true,