Emit analytics for remote plugin installs (#20267)

## Summary

- emit `codex_plugin_installed` after a remote plugin install succeeds
- keep local installs unchanged, but let remote installs override the
analytics `plugin_id` with the backend remote plugin id
(`plugins~Plugin_...`)
- preserve the local/display identity in `plugin_name` and
`marketplace_name`, plus capability metadata from the installed bundle
- add regression coverage for local install analytics, remote install
analytics, and analytics id override serialization

## Testing

- `just fmt`
- `cargo test -p codex-analytics`
- `cargo test -p codex-app-server`
This commit is contained in:
xli-oai
2026-04-30 17:27:16 -07:00
committed by GitHub
parent b6f81257f8
commit acdf908268
7 changed files with 136 additions and 27 deletions

View File

@@ -505,7 +505,7 @@ impl CodexMessageProcessor {
async fn remote_plugin_install_response(
&self,
remote_marketplace_name: String,
plugin_name: String,
remote_plugin_id: String,
) -> Result<PluginInstallResponse, JSONRPCErrorError> {
let config = self.load_latest_config(/*fallback_cwd*/ None).await?;
if !config.features.enabled(Feature::Plugins)
@@ -513,8 +513,10 @@ impl CodexMessageProcessor {
{
return Err(invalid_request("remote plugin install is not enabled"));
}
if plugin_name.is_empty() || !is_valid_remote_plugin_id(&plugin_name) {
return Err(invalid_request("invalid remote plugin id"));
if remote_plugin_id.is_empty() || !is_valid_remote_plugin_id(&remote_plugin_id) {
return Err(invalid_request(
"invalid remote plugin id: only ASCII letters, digits, `_`, `-`, and `~` are allowed",
));
}
let auth = self.auth_manager.auth().await;
@@ -526,7 +528,7 @@ impl CodexMessageProcessor {
&remote_plugin_service_config,
auth.as_ref(),
&remote_marketplace_name,
&plugin_name,
&remote_plugin_id,
)
.await
.map_err(|err| {
@@ -537,7 +539,7 @@ impl CodexMessageProcessor {
})?;
if remote_detail.summary.install_policy == PluginInstallPolicy::NotAvailable {
return Err(invalid_request(format!(
"remote plugin {plugin_name} is not available for install"
"remote plugin {remote_plugin_id} is not available for install"
)));
}
let actual_remote_marketplace_name = remote_detail.marketplace_name.clone();
@@ -550,7 +552,7 @@ impl CodexMessageProcessor {
&remote_detail.summary.name,
);
let validated_bundle = codex_core_plugins::remote_bundle::validate_remote_plugin_bundle(
&plugin_name,
&remote_plugin_id,
&actual_remote_marketplace_name,
&remote_detail.summary.name,
remote_detail.release_version.as_deref(),
@@ -572,7 +574,7 @@ impl CodexMessageProcessor {
&remote_plugin_service_config,
auth.as_ref(),
&actual_remote_marketplace_name,
&plugin_name,
&remote_plugin_id,
)
.await
.map_err(|err| remote_plugin_catalog_error_to_jsonrpc(err, "install remote plugin"))?;
@@ -585,6 +587,12 @@ impl CodexMessageProcessor {
Some(self.effective_plugins_changed_callback(config.clone())),
);
let mut plugin_metadata =
plugin_telemetry_metadata_from_root(&result.plugin_id, &result.installed_path).await;
plugin_metadata.remote_plugin_id = Some(remote_plugin_id);
self.analytics_events_client
.track_plugin_installed(plugin_metadata);
let plugin_mcp_servers = load_plugin_mcp_servers(result.installed_path.as_path()).await;
if !plugin_mcp_servers.is_empty() {
self.start_plugin_mcp_oauth_logins(&config, plugin_mcp_servers)