diff --git a/codex-rs/core/src/plugins/startup_sync.rs b/codex-rs/core/src/plugins/startup_sync.rs index 14608d1313..d60a8bf02c 100644 --- a/codex-rs/core/src/plugins/startup_sync.rs +++ b/codex-rs/core/src/plugins/startup_sync.rs @@ -19,7 +19,10 @@ use crate::config::Config; use codex_login::AuthManager; use codex_login::default_client::build_reqwest_client; +use super::OPENAI_CURATED_MARKETPLACE_NAME; use super::PluginsManager; +use super::marketplace_install_root; +use super::record_installed_marketplace_root; const GITHUB_API_BASE_URL: &str = "https://api.github.com"; const GITHUB_API_ACCEPT_HEADER: &str = "application/vnd.github+json"; @@ -28,7 +31,6 @@ const CURATED_PLUGINS_BACKUP_ARCHIVE_API_URL: &str = "https://chatgpt.com/backend-api/plugins/export/curated"; const OPENAI_PLUGINS_OWNER: &str = "openai"; const OPENAI_PLUGINS_REPO: &str = "plugins"; -const CURATED_PLUGINS_RELATIVE_DIR: &str = ".tmp/plugins"; const CURATED_PLUGINS_SHA_FILE: &str = ".tmp/plugins.sha"; const CURATED_PLUGINS_BACKUP_ARCHIVE_FALLBACK_VERSION: &str = "export-backup"; const CURATED_PLUGINS_GIT_TIMEOUT: Duration = Duration::from_secs(30); @@ -60,7 +62,7 @@ struct CuratedPluginsBackupArchiveResponse { } pub(crate) fn curated_plugins_repo_path(codex_home: &Path) -> PathBuf { - codex_home.join(CURATED_PLUGINS_RELATIVE_DIR) + marketplace_install_root(codex_home).join(OPENAI_CURATED_MARKETPLACE_NAME) } pub(crate) fn read_curated_plugins_sha(codex_home: &Path) -> Option { @@ -88,6 +90,7 @@ fn sync_openai_plugins_repo_with_transport_overrides( ) -> Result { match sync_openai_plugins_repo_via_git(codex_home, git_binary) { Ok(remote_sha) => { + record_curated_marketplace_root(codex_home)?; emit_curated_plugins_startup_sync_metric("git", "success"); emit_curated_plugins_startup_sync_final_metric("git", "success"); Ok(remote_sha) @@ -101,6 +104,7 @@ fn sync_openai_plugins_repo_with_transport_overrides( ); match sync_openai_plugins_repo_via_http(codex_home, api_base_url) { Ok(remote_sha) => { + record_curated_marketplace_root(codex_home)?; emit_curated_plugins_startup_sync_metric("http", "success"); emit_curated_plugins_startup_sync_final_metric("http", "success"); Ok(remote_sha) @@ -131,11 +135,15 @@ fn sync_openai_plugins_repo_with_transport_overrides( let status = if result.is_ok() { "success" } else { "failure" }; emit_curated_plugins_startup_sync_metric("export_archive", status); emit_curated_plugins_startup_sync_final_metric("export_archive", status); - result.map_err(|export_err| { - format!( + match result { + Ok(remote_sha) => { + record_curated_marketplace_root(codex_home)?; + Ok(remote_sha) + } + Err(export_err) => Err(format!( "git sync failed for curated plugin sync: {err}; GitHub HTTP sync failed for curated plugin sync: {http_err}; export archive sync failed for curated plugin sync: {export_err}" - ) - }) + )), + } } } } @@ -290,6 +298,17 @@ fn startup_remote_plugin_sync_marker_path(codex_home: &Path) -> PathBuf { codex_home.join(STARTUP_REMOTE_PLUGIN_SYNC_MARKER_FILE) } +fn record_curated_marketplace_root(codex_home: &Path) -> Result<(), String> { + let repo_path = curated_plugins_repo_path(codex_home); + record_installed_marketplace_root(codex_home, OPENAI_CURATED_MARKETPLACE_NAME, &repo_path) + .map_err(|err| { + format!( + "failed to record curated marketplace root {}: {err}", + repo_path.display() + ) + }) +} + fn has_local_curated_plugins_snapshot(codex_home: &Path) -> bool { curated_plugins_repo_path(codex_home) .join(".agents/plugins/marketplace.json") diff --git a/codex-rs/core/src/plugins/startup_sync_tests.rs b/codex-rs/core/src/plugins/startup_sync_tests.rs index cd0b91f4db..e5b299831a 100644 --- a/codex-rs/core/src/plugins/startup_sync_tests.rs +++ b/codex-rs/core/src/plugins/startup_sync_tests.rs @@ -144,7 +144,7 @@ fn curated_plugins_repo_path_uses_codex_home_tmp_dir() { let tmp = tempdir().expect("tempdir"); assert_eq!( curated_plugins_repo_path(tmp.path()), - tmp.path().join(".tmp/plugins") + tmp.path().join(".tmp/marketplaces/openai-curated") ); } @@ -254,6 +254,10 @@ exit 1 assert!(repo_path.join(".git").is_dir()); assert_curated_gmail_repo(&repo_path); assert_eq!(read_curated_plugins_sha(tmp.path()).as_deref(), Some(sha)); + let registry = + std::fs::read_to_string(tmp.path().join(".tmp/known_marketplaces.json")).unwrap(); + assert!(registry.contains(r#""name": "openai-curated""#)); + assert!(registry.contains(r#""installLocation""#)); } #[cfg(unix)]