mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
Separate local and remote plugin analytics IDs (#29495)
## Why Plugin analytics overloaded `plugin_id`: most events used the Codex `<plugin>@<marketplace>` identity, while remote install events used the backend plugin ID. That makes the same field change meaning across event types and complicates downstream identity resolution. This change makes the contract unambiguous: - `plugin_id`: the local Codex `<plugin>@<marketplace>` identity, when resolved - `remote_plugin_id`: the backend plugin identity, when available For a remote install failure that happens before plugin details resolve, `plugin_id` is `null` and `remote_plugin_id` remains populated. ## What changed All six plugin analytics events use the same identity contract: - `codex_plugin_installed` - `codex_plugin_install_failed` - `codex_plugin_uninstalled` - `codex_plugin_enabled` - `codex_plugin_disabled` - `codex_plugin_used` Remote identity is resolved from the current installed-plugin snapshot first, with persisted install metadata as fallback. The telemetry metadata type keeps local identity optional for failures that occur before remote details are available. The app-server test client's manual analytics smokes now find remote mutation events through `remote_plugin_id` and validate that `plugin_id` remains local. ## Remote uninstall Resolve and capture telemetry metadata before removing the local plugin cache, then emit `codex_plugin_uninstalled` after the backend confirms success. The event is also emitted when backend uninstall succeeds but local cache cleanup reports `CacheRemove`. If a concurrent remote-cache refresh removes the local bundle before telemetry capture, the already-fetched remote plugin detail supplies fallback capability metadata. ## Validation - `just test -p codex-analytics` — 82 passed - `just test -p codex-core-plugins` — 271 passed - `just test -p codex-app-server-test-client` — 5 passed - `just test -p codex-plugin` — 3 passed - `just test -p codex-app-server plugin_install` — 37 passed - `just test -p codex-app-server plugin_uninstall` — 10 passed The production app-server install/uninstall flow was also exercised against `plugins~Plugin_f1b845ac33888191ac156169c58733c2` (`build-ios-apps@openai-curated-remote`), and the plugin's original uninstalled state was restored.
This commit is contained in:
committed by
GitHub
Unverified
parent
c5a9a95ab6
commit
ff50b47dce
@@ -23,6 +23,7 @@ use codex_mcp::McpOAuthLoginSupport;
|
||||
use codex_mcp::oauth_login_support;
|
||||
use codex_mcp::should_retry_without_scopes;
|
||||
use codex_plugin::PluginId;
|
||||
use codex_plugin::PluginTelemetryMetadata;
|
||||
use codex_rmcp_client::perform_oauth_login_silent;
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -1528,6 +1529,7 @@ impl PluginRequestProcessor {
|
||||
self.track_plugin_install_failed_for_remote_plugin(
|
||||
&remote_plugin_id,
|
||||
&remote_marketplace_name,
|
||||
/*plugin_id*/ None,
|
||||
error_type,
|
||||
err.to_string(),
|
||||
);
|
||||
@@ -1538,6 +1540,12 @@ impl PluginRequestProcessor {
|
||||
})?;
|
||||
let actual_remote_marketplace_name = remote_detail.marketplace_name.clone();
|
||||
let remote_plugin_name = remote_detail.summary.name.clone();
|
||||
let resolved_plugin_id = PluginId::parse(&remote_detail.summary.id).map_err(|err| {
|
||||
internal_error(format!(
|
||||
"invalid resolved plugin id `{}`: {err}",
|
||||
remote_detail.summary.id
|
||||
))
|
||||
})?;
|
||||
if remote_detail.summary.availability == PluginAvailability::DisabledByAdmin {
|
||||
return Err(invalid_request(format!(
|
||||
"remote plugin {remote_plugin_id} is disabled by admin"
|
||||
@@ -1569,6 +1577,7 @@ impl PluginRequestProcessor {
|
||||
self.track_plugin_install_failed_for_remote_plugin(
|
||||
&remote_plugin_id,
|
||||
&actual_remote_marketplace_name,
|
||||
Some(&resolved_plugin_id),
|
||||
error_type,
|
||||
err.to_string(),
|
||||
);
|
||||
@@ -1585,6 +1594,7 @@ impl PluginRequestProcessor {
|
||||
self.track_plugin_install_failed_for_remote_plugin(
|
||||
&remote_plugin_id,
|
||||
&actual_remote_marketplace_name,
|
||||
Some(&resolved_plugin_id),
|
||||
error_type,
|
||||
err.to_string(),
|
||||
);
|
||||
@@ -1606,6 +1616,7 @@ impl PluginRequestProcessor {
|
||||
self.track_plugin_install_failed_for_remote_plugin(
|
||||
&remote_plugin_id,
|
||||
&actual_remote_marketplace_name,
|
||||
Some(&result.plugin_id),
|
||||
error_type,
|
||||
err.to_string(),
|
||||
);
|
||||
@@ -1702,6 +1713,7 @@ impl PluginRequestProcessor {
|
||||
&self,
|
||||
remote_plugin_id: &str,
|
||||
marketplace_name: &str,
|
||||
plugin_id: Option<&PluginId>,
|
||||
error_type: &'static str,
|
||||
error_message: String,
|
||||
) {
|
||||
@@ -1712,16 +1724,17 @@ impl PluginRequestProcessor {
|
||||
error = %error_message,
|
||||
"remote plugin install failed"
|
||||
);
|
||||
// The remote id is reported separately; this local name only satisfies
|
||||
// PluginId validation before remote details are available.
|
||||
let Ok(plugin_id) = PluginId::new("unknown".to_string(), marketplace_name.to_string())
|
||||
else {
|
||||
return;
|
||||
let plugin = if let Some(plugin_id) = plugin_id {
|
||||
self.thread_manager
|
||||
.plugins_manager()
|
||||
.telemetry_metadata_for_plugin_id_with_remote_id(plugin_id, remote_plugin_id)
|
||||
} else {
|
||||
PluginTelemetryMetadata {
|
||||
plugin_id: None,
|
||||
remote_plugin_id: Some(remote_plugin_id.to_string()),
|
||||
capability_summary: None,
|
||||
}
|
||||
};
|
||||
let plugin = self
|
||||
.thread_manager
|
||||
.plugins_manager()
|
||||
.telemetry_metadata_for_plugin_id_with_remote_id(&plugin_id, remote_plugin_id);
|
||||
self.analytics_events_client
|
||||
.track_plugin_install_failed(plugin, error_type.to_string());
|
||||
}
|
||||
@@ -1980,11 +1993,31 @@ impl PluginRequestProcessor {
|
||||
let remote_plugin_service_config = RemotePluginServiceConfig {
|
||||
chatgpt_base_url: config.chatgpt_base_url.clone(),
|
||||
};
|
||||
let uninstall_target = codex_core_plugins::remote::resolve_remote_plugin_uninstall_target(
|
||||
&remote_plugin_service_config,
|
||||
auth.as_ref(),
|
||||
&plugin_id,
|
||||
)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
remote_plugin_catalog_error_to_jsonrpc(err, "resolve remote plugin before uninstall")
|
||||
})?;
|
||||
let plugins_manager = self.thread_manager.plugins_manager();
|
||||
let mut plugin_telemetry = plugins_manager
|
||||
.telemetry_metadata_for_installed_plugin_with_remote_id(
|
||||
&uninstall_target.plugin_id,
|
||||
&uninstall_target.remote_plugin_id,
|
||||
)
|
||||
.await;
|
||||
if plugin_telemetry.capability_summary.is_none() {
|
||||
plugin_telemetry.capability_summary =
|
||||
Some(uninstall_target.fallback_capability_summary.clone());
|
||||
}
|
||||
let uninstall_result = codex_core_plugins::remote::uninstall_remote_plugin(
|
||||
&remote_plugin_service_config,
|
||||
auth.as_ref(),
|
||||
config.codex_home.to_path_buf(),
|
||||
&plugin_id,
|
||||
uninstall_target,
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -1992,7 +2025,8 @@ impl PluginRequestProcessor {
|
||||
&uninstall_result,
|
||||
Ok(()) | Err(RemotePluginCatalogError::CacheRemove(_))
|
||||
) {
|
||||
let plugins_manager = self.thread_manager.plugins_manager();
|
||||
self.analytics_events_client
|
||||
.track_plugin_uninstalled(plugin_telemetry);
|
||||
if plugins_manager.clear_remote_installed_plugins_cache() {
|
||||
self.on_effective_plugins_changed();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user