mirror of
https://github.com/openai/codex.git
synced 2026-11-06 20:20:34 +00:00
Stop prefetching plugin discovery at app-server startup (#59768)
## What changed - Remove startup prefetching of remote catalogs, featured plugins, and recommendations while retaining installed-plugin refreshes and bundle downloads. - Fetch featured plugin IDs on demand, concurrently with marketplace fetching when listing the remote global catalog, and reuse the cached results. - Retire `plugin_discovery_prefetch` as an ignored compatibility flag and remove forced background catalog refreshes. ## Testing Update app-server tests to verify that startup downloads installed bundles without discovery requests and that two `plugin/list` calls share one featured-plugin fetch. GitOrigin-RevId: aa10474f3429102d624c83472e81ec85e3b470ae
This commit is contained in:
committed by
copyberry
parent
9df200a068
commit
08e4f43c9a
@@ -592,7 +592,6 @@ impl MessageProcessor {
|
||||
turn_cost_worker.as_ref().map(TurnCostWorker::handle),
|
||||
);
|
||||
if let Some(startup_config) = plugin_startup_tasks {
|
||||
// Keep plugin startup warmups aligned at app-server startup.
|
||||
let reload_config = match startup_config {
|
||||
PluginStartupConfig::Current => {
|
||||
plugin_config_reload::for_cwd(config_manager.clone(), config.cwd.clone())
|
||||
@@ -607,7 +606,6 @@ impl MessageProcessor {
|
||||
.plugins_manager()
|
||||
.maybe_start_plugin_startup_tasks_for_config(
|
||||
&config.plugins_config_input(),
|
||||
config.features.enabled(Feature::PluginDiscoveryPrefetch),
|
||||
reload_config,
|
||||
Some(on_effective_plugins_changed),
|
||||
startup_warnings_tx,
|
||||
|
||||
@@ -322,7 +322,6 @@ impl AccountRequestProcessor {
|
||||
.maybe_start_remote_plugin_caches_refresh(
|
||||
&plugins_config,
|
||||
auth,
|
||||
/*prefetch_recommendations*/ true,
|
||||
Some(on_effective_plugins_changed),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -736,16 +736,27 @@ impl PluginRequestProcessor {
|
||||
if include_shared_with_me && config.features.enabled(Feature::PluginSharing) {
|
||||
remote_sources.push(RemoteMarketplaceSource::SharedWithMe);
|
||||
}
|
||||
let featured =
|
||||
plugins_manager.featured_plugin_ids_for_config(&context.global_config, auth.as_ref());
|
||||
tokio::pin!(featured);
|
||||
let mut featured_result = None;
|
||||
if !remote_sources.is_empty() {
|
||||
match codex_core_plugins::remote::fetch_remote_marketplaces(
|
||||
let marketplaces = codex_core_plugins::remote::fetch_remote_marketplaces(
|
||||
&remote_plugin_service_config,
|
||||
auth.as_ref(),
|
||||
&remote_sources,
|
||||
/*catalog_cache_root*/ Some(config.codex_home.as_path()),
|
||||
remote_catalog_cache_mode,
|
||||
)
|
||||
.await
|
||||
{
|
||||
);
|
||||
tokio::pin!(marketplaces);
|
||||
let outcome = tokio::select! {
|
||||
outcome = &mut marketplaces => outcome,
|
||||
ids = &mut featured, if use_remote_global_catalog => {
|
||||
featured_result = Some(ids);
|
||||
marketplaces.await
|
||||
}
|
||||
};
|
||||
match outcome {
|
||||
Ok(outcome) => {
|
||||
remote_catalog_cache_refresh_scopes = outcome.catalog_cache_refresh_scopes;
|
||||
for remote_marketplace in outcome
|
||||
@@ -804,10 +815,11 @@ impl PluginRequestProcessor {
|
||||
marketplace.name == OPENAI_CURATED_MARKETPLACE_NAME
|
||||
|| marketplace.name == REMOTE_GLOBAL_MARKETPLACE_NAME
|
||||
}) {
|
||||
match plugins_manager
|
||||
.featured_plugin_ids_for_config(&context.global_config, auth.as_ref())
|
||||
.await
|
||||
{
|
||||
let result = match featured_result {
|
||||
Some(result) => result,
|
||||
None => featured.await,
|
||||
};
|
||||
match result {
|
||||
Ok(featured_plugin_ids) => featured_plugin_ids,
|
||||
Err(err) => {
|
||||
warn!(
|
||||
|
||||
@@ -1961,8 +1961,7 @@ enabled = true
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn app_server_startup_downloads_installed_plugin_bundles_without_discovery_prefetch()
|
||||
-> Result<()> {
|
||||
async fn app_server_startup_downloads_installed_plugin_bundles_without_discovery() -> Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
let server = MockServer::start().await;
|
||||
write_remote_plugin_catalog_config(
|
||||
@@ -2017,9 +2016,8 @@ async fn app_server_startup_downloads_installed_plugin_bundles_without_discovery
|
||||
let installed_path = codex_home
|
||||
.path()
|
||||
.join("plugins/cache/openai-curated-remote/linear/1.2.3");
|
||||
let _mcp = TestAppServer::builder()
|
||||
let mut mcp = TestAppServer::builder()
|
||||
.with_codex_home(codex_home.path())
|
||||
.with_args(&["-c", "features.plugin_discovery_prefetch=false"])
|
||||
.with_plugin_startup_tasks()
|
||||
.with_env_overrides(&[(TEST_ALLOW_HTTP_REMOTE_PLUGIN_BUNDLE_DOWNLOADS, Some("1"))])
|
||||
.build_initialized_with_timeout(DEFAULT_TIMEOUT)
|
||||
@@ -2039,6 +2037,8 @@ async fn app_server_startup_downloads_installed_plugin_bundles_without_discovery
|
||||
assert!(installed_path.join("skills/plan-work/SKILL.md").is_file());
|
||||
let config = std::fs::read_to_string(codex_home.path().join("config.toml"))?;
|
||||
assert!(!config.contains("linear@openai-curated-remote"));
|
||||
timeout(DEFAULT_TIMEOUT, mcp.shutdown_gracefully()).await??;
|
||||
server.verify().await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -2151,7 +2151,7 @@ enum ProjectPluginConfiguration {
|
||||
Invalid,
|
||||
}
|
||||
|
||||
#[test_case(ProjectPluginConfiguration::None, None, "plugin_discovery_prefetch = false", true, true; "without discovery prefetch")]
|
||||
#[test_case(ProjectPluginConfiguration::None, None, "", true, true; "default configuration")]
|
||||
#[test_case(ProjectPluginConfiguration::Disabled, None, "", true, true; "project disables local plugins")]
|
||||
#[test_case(ProjectPluginConfiguration::Invalid, None, "", true, true; "invalid project preserves remote plugins")]
|
||||
#[test_case(ProjectPluginConfiguration::None, Some("tpp"), "", true, true; "configured product sku")]
|
||||
@@ -2652,265 +2652,6 @@ async fn plugin_list_honors_global_remote_catalog_cache_ttl() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn app_server_startup_refreshes_cached_remote_catalog_without_blocking_plugin_list()
|
||||
-> Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
let server = MockServer::start().await;
|
||||
write_remote_plugin_catalog_config(
|
||||
codex_home.path(),
|
||||
&format!("{}/backend-api/", server.uri()),
|
||||
)?;
|
||||
write_chatgpt_auth(
|
||||
codex_home.path(),
|
||||
ChatGptAuthFixture::new("chatgpt-token")
|
||||
.account_id("account-123")
|
||||
.chatgpt_user_id("user-123")
|
||||
.chatgpt_account_id("account-123"),
|
||||
AuthCredentialsStoreMode::File,
|
||||
)?;
|
||||
|
||||
let cached_remote_plugin_id = "plugins~Plugin_00000000000000000000000000000000";
|
||||
let refreshed_remote_plugin_id = "plugins~Plugin_11111111111111111111111111111111";
|
||||
mount_remote_plugin_list(
|
||||
&server,
|
||||
"GLOBAL",
|
||||
&remote_plugin_list_body(cached_remote_plugin_id, "linear", "Linear", "Plan work"),
|
||||
)
|
||||
.await;
|
||||
mount_remote_installed_plugins(&server, "GLOBAL", empty_remote_installed_plugins_body()).await;
|
||||
mount_remote_installed_plugins(&server, "WORKSPACE", empty_remote_installed_plugins_body())
|
||||
.await;
|
||||
mount_empty_user_installed_plugins(&server).await;
|
||||
|
||||
let mut app_server = TestAppServer::builder()
|
||||
.with_codex_home(codex_home.path())
|
||||
.build()
|
||||
.await?;
|
||||
timeout(DEFAULT_TIMEOUT, app_server.initialize()).await??;
|
||||
let request_id = app_server
|
||||
.send_plugin_list_request(PluginListParams {
|
||||
cwds: None,
|
||||
marketplace_kinds: None,
|
||||
force_refetch: false,
|
||||
})
|
||||
.await?;
|
||||
let _: PluginListResponse = to_response(
|
||||
timeout(
|
||||
DEFAULT_TIMEOUT,
|
||||
app_server.read_stream_until_response_message(RequestId::Integer(request_id)),
|
||||
)
|
||||
.await??,
|
||||
)?;
|
||||
wait_for_cached_remote_catalog_plugin_ids(codex_home.path(), &[cached_remote_plugin_id])
|
||||
.await?;
|
||||
timeout(DEFAULT_TIMEOUT, app_server.shutdown_gracefully()).await??;
|
||||
|
||||
server.reset().await;
|
||||
let refreshed_body = remote_plugin_list_body(
|
||||
refreshed_remote_plugin_id,
|
||||
"notion",
|
||||
"Notion",
|
||||
"Capture notes",
|
||||
);
|
||||
Mock::given(method("GET"))
|
||||
.and(path("/backend-api/ps/plugins/list"))
|
||||
.and(query_param("scope", "GLOBAL"))
|
||||
.and(query_param("limit", "200"))
|
||||
.and(header("authorization", "Bearer chatgpt-token"))
|
||||
.and(header("chatgpt-account-id", "account-123"))
|
||||
.respond_with(
|
||||
ResponseTemplate::new(200)
|
||||
.set_body_string(refreshed_body)
|
||||
.set_delay(Duration::from_secs(/*secs*/ 2)),
|
||||
)
|
||||
.mount(&server)
|
||||
.await;
|
||||
mount_remote_installed_plugins(&server, "GLOBAL", empty_remote_installed_plugins_body()).await;
|
||||
mount_remote_installed_plugins(&server, "WORKSPACE", empty_remote_installed_plugins_body())
|
||||
.await;
|
||||
mount_empty_user_installed_plugins(&server).await;
|
||||
|
||||
let mut app_server = TestAppServer::builder()
|
||||
.with_codex_home(codex_home.path())
|
||||
.with_plugin_startup_tasks()
|
||||
.build()
|
||||
.await?;
|
||||
timeout(DEFAULT_TIMEOUT, app_server.initialize()).await??;
|
||||
wait_for_remote_plugin_request_count(&server, "/ps/plugins/list", /*expected_count*/ 1).await?;
|
||||
|
||||
let request_id = app_server
|
||||
.send_plugin_list_request(PluginListParams {
|
||||
cwds: None,
|
||||
marketplace_kinds: None,
|
||||
force_refetch: false,
|
||||
})
|
||||
.await?;
|
||||
let response: PluginListResponse = to_response(
|
||||
timeout(
|
||||
DEFAULT_TIMEOUT,
|
||||
app_server.read_stream_until_response_message(RequestId::Integer(request_id)),
|
||||
)
|
||||
.await??,
|
||||
)?;
|
||||
let remote_marketplace = response
|
||||
.marketplaces
|
||||
.iter()
|
||||
.find(|marketplace| marketplace.name == "openai-curated-remote")
|
||||
.expect("expected cached remote marketplace");
|
||||
assert_eq!(
|
||||
remote_marketplace.plugins[0].id,
|
||||
"linear@openai-curated-remote"
|
||||
);
|
||||
|
||||
wait_for_cached_remote_catalog_plugin_ids(codex_home.path(), &[refreshed_remote_plugin_id])
|
||||
.await?;
|
||||
let request_id = app_server
|
||||
.send_plugin_list_request(PluginListParams {
|
||||
cwds: None,
|
||||
marketplace_kinds: None,
|
||||
force_refetch: false,
|
||||
})
|
||||
.await?;
|
||||
let response: PluginListResponse = to_response(
|
||||
timeout(
|
||||
DEFAULT_TIMEOUT,
|
||||
app_server.read_stream_until_response_message(RequestId::Integer(request_id)),
|
||||
)
|
||||
.await??,
|
||||
)?;
|
||||
let remote_marketplace = response
|
||||
.marketplaces
|
||||
.iter()
|
||||
.find(|marketplace| marketplace.name == "openai-curated-remote")
|
||||
.expect("expected refreshed remote marketplace");
|
||||
assert_eq!(
|
||||
remote_marketplace.plugins[0].id,
|
||||
"notion@openai-curated-remote"
|
||||
);
|
||||
sleep(Duration::from_millis(100)).await;
|
||||
wait_for_remote_plugin_request_count(&server, "/ps/plugins/list", /*expected_count*/ 1).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn app_server_startup_skips_disabled_remote_plugin_catalog_scopes() -> Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
let server = MockServer::start().await;
|
||||
let base_url = format!("{}/backend-api/", server.uri());
|
||||
write_remote_plugin_catalog_config(codex_home.path(), &base_url)?;
|
||||
write_chatgpt_auth(
|
||||
codex_home.path(),
|
||||
ChatGptAuthFixture::new("chatgpt-token")
|
||||
.account_id("account-123")
|
||||
.chatgpt_user_id("user-123")
|
||||
.chatgpt_account_id("account-123"),
|
||||
AuthCredentialsStoreMode::File,
|
||||
)?;
|
||||
|
||||
let global_plugin_id = "plugins~Plugin_00000000000000000000000000000000";
|
||||
let user_plugin_id = "plugins~Plugin_11111111111111111111111111111111";
|
||||
let workspace_plugin_id = "plugins~Plugin_22222222222222222222222222222222";
|
||||
let global_body = remote_plugin_list_body(global_plugin_id, "global-linear", "Linear", "Plan");
|
||||
let user_body = user_remote_plugin_page_body(
|
||||
user_plugin_id,
|
||||
"private-linear",
|
||||
"Private Linear",
|
||||
"PRIVATE",
|
||||
/*enabled*/ None,
|
||||
);
|
||||
let workspace_body = workspace_remote_plugin_page_body(
|
||||
workspace_plugin_id,
|
||||
"workspace-linear",
|
||||
"Workspace Linear",
|
||||
"LISTED",
|
||||
/*enabled*/ None,
|
||||
);
|
||||
mount_remote_plugin_list(&server, "GLOBAL", &global_body).await;
|
||||
mount_remote_plugin_list(&server, "USER", &user_body).await;
|
||||
mount_remote_plugin_list(&server, "WORKSPACE", &workspace_body).await;
|
||||
for scope in ["GLOBAL", "USER", "WORKSPACE"] {
|
||||
mount_remote_installed_plugins(&server, scope, empty_remote_installed_plugins_body()).await;
|
||||
}
|
||||
|
||||
let mut app_server = TestAppServer::builder()
|
||||
.with_codex_home(codex_home.path())
|
||||
.build()
|
||||
.await?;
|
||||
timeout(DEFAULT_TIMEOUT, app_server.initialize()).await??;
|
||||
for marketplace_kinds in [
|
||||
None,
|
||||
Some(vec![
|
||||
PluginListMarketplaceKind::CreatedByMeRemote,
|
||||
PluginListMarketplaceKind::WorkspaceDirectory,
|
||||
]),
|
||||
] {
|
||||
let request_id = app_server
|
||||
.send_plugin_list_request(PluginListParams {
|
||||
cwds: None,
|
||||
marketplace_kinds,
|
||||
force_refetch: false,
|
||||
})
|
||||
.await?;
|
||||
let _: PluginListResponse = to_response(
|
||||
timeout(
|
||||
DEFAULT_TIMEOUT,
|
||||
app_server.read_stream_until_response_message(RequestId::Integer(request_id)),
|
||||
)
|
||||
.await??,
|
||||
)?;
|
||||
}
|
||||
wait_for_cached_remote_catalog_plugin_ids(
|
||||
codex_home.path(),
|
||||
&[global_plugin_id, user_plugin_id, workspace_plugin_id],
|
||||
)
|
||||
.await?;
|
||||
timeout(DEFAULT_TIMEOUT, app_server.shutdown_gracefully()).await??;
|
||||
|
||||
write_remote_plugins_disabled_config_with_base_url(codex_home.path(), &base_url)?;
|
||||
server.reset().await;
|
||||
mount_remote_plugin_list(&server, "GLOBAL", &global_body).await;
|
||||
mount_remote_plugin_list(&server, "USER", &user_body).await;
|
||||
mount_remote_plugin_list(&server, "WORKSPACE", &workspace_body).await;
|
||||
for scope in ["GLOBAL", "USER", "WORKSPACE"] {
|
||||
mount_remote_installed_plugins(&server, scope, empty_remote_installed_plugins_body()).await;
|
||||
}
|
||||
|
||||
let mut app_server = TestAppServer::builder()
|
||||
.with_codex_home(codex_home.path())
|
||||
.with_plugin_startup_tasks()
|
||||
.build()
|
||||
.await?;
|
||||
timeout(DEFAULT_TIMEOUT, app_server.initialize()).await??;
|
||||
wait_for_remote_plugin_list_scope_request_count(
|
||||
&server,
|
||||
"WORKSPACE",
|
||||
/*expected_count*/ 1,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let requested_scopes = server
|
||||
.received_requests()
|
||||
.await
|
||||
.expect("wiremock should record requests")
|
||||
.into_iter()
|
||||
.filter(|request| {
|
||||
request.method == "GET" && request.url.path().ends_with("/ps/plugins/list")
|
||||
})
|
||||
.filter_map(|request| {
|
||||
request
|
||||
.url
|
||||
.query_pairs()
|
||||
.find(|(name, _)| name == "scope")
|
||||
.map(|(_, scope)| scope.into_owned())
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(requested_scopes, vec!["WORKSPACE".to_string()]);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn plugin_list_force_refetch_bypasses_fresh_global_remote_catalog_cache() -> Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
@@ -4992,7 +4733,7 @@ async fn plugin_list_omits_featured_plugin_ids_without_chatgpt_auth() -> Result<
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn plugin_list_uses_warmed_featured_plugin_ids_cache_on_first_request() -> Result<()> {
|
||||
async fn plugin_list_fetches_and_caches_featured_plugin_ids_on_demand() -> Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
let server = MockServer::start().await;
|
||||
write_plugin_sync_config(codex_home.path(), &format!("{}/backend-api/", server.uri()))?;
|
||||
@@ -5012,33 +4753,28 @@ async fn plugin_list_uses_warmed_featured_plugin_ids_cache_on_first_request() ->
|
||||
.with_plugin_startup_tasks()
|
||||
.build_initialized_with_timeout(DEFAULT_TIMEOUT)
|
||||
.await?;
|
||||
wait_for_featured_plugin_request_count(&server, /*expected_count*/ 1).await?;
|
||||
for _ in 0..2 {
|
||||
let request_id = mcp
|
||||
.send_plugin_list_request(PluginListParams {
|
||||
cwds: None,
|
||||
marketplace_kinds: None,
|
||||
force_refetch: false,
|
||||
})
|
||||
.await?;
|
||||
|
||||
let request_id = mcp
|
||||
.send_plugin_list_request(PluginListParams {
|
||||
cwds: None,
|
||||
marketplace_kinds: None,
|
||||
force_refetch: false,
|
||||
})
|
||||
.await?;
|
||||
let response: PluginListResponse =
|
||||
timeout(DEFAULT_TIMEOUT, mcp.read_response(request_id)).await??;
|
||||
|
||||
let response: PluginListResponse =
|
||||
timeout(DEFAULT_TIMEOUT, mcp.read_response(request_id)).await??;
|
||||
|
||||
assert_eq!(
|
||||
response.featured_plugin_ids,
|
||||
vec!["linear@openai-curated".to_string()]
|
||||
);
|
||||
assert_eq!(
|
||||
response.featured_plugin_ids,
|
||||
vec!["linear@openai-curated".to_string()]
|
||||
);
|
||||
}
|
||||
timeout(DEFAULT_TIMEOUT, mcp.shutdown_gracefully()).await??;
|
||||
server.verify().await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn wait_for_featured_plugin_request_count(
|
||||
server: &MockServer,
|
||||
expected_count: usize,
|
||||
) -> Result<()> {
|
||||
wait_for_remote_plugin_request_count(server, "/plugins/featured", expected_count).await
|
||||
}
|
||||
|
||||
async fn wait_for_remote_plugin_request_count(
|
||||
server: &MockServer,
|
||||
path_suffix: &str,
|
||||
|
||||
@@ -379,7 +379,6 @@ struct RemoteCatalogCacheRefreshRequest {
|
||||
service_config: RemotePluginServiceConfig,
|
||||
auth: Option<CodexAuth>,
|
||||
scopes: BTreeSet<RemotePluginScope>,
|
||||
mode: RemoteCatalogCacheRefreshMode,
|
||||
}
|
||||
|
||||
impl RemoteCatalogCacheRefreshRequest {
|
||||
@@ -394,12 +393,6 @@ impl RemoteCatalogCacheRefreshRequest {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
enum RemoteCatalogCacheRefreshMode {
|
||||
OnlyIfStale,
|
||||
Force,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct RemoteCatalogCacheRefreshState {
|
||||
requests: VecDeque<RemoteCatalogCacheRefreshRequest>,
|
||||
@@ -1666,7 +1659,6 @@ impl PluginsManager {
|
||||
self: &Arc<Self>,
|
||||
config: &PluginsConfigInput,
|
||||
auth: Option<CodexAuth>,
|
||||
prefetch_recommendations: bool,
|
||||
on_effective_plugins_changed: Option<EffectivePluginsChangedCallback>,
|
||||
) {
|
||||
self.maybe_start_remote_installed_plugins_cache_refresh_with_notify(
|
||||
@@ -1677,9 +1669,6 @@ impl PluginsManager {
|
||||
EffectivePluginsChange::default(),
|
||||
);
|
||||
|
||||
if !prefetch_recommendations {
|
||||
return;
|
||||
}
|
||||
let manager = Arc::clone(self);
|
||||
let config = config.clone();
|
||||
tokio::spawn(async move {
|
||||
@@ -1924,7 +1913,6 @@ impl PluginsManager {
|
||||
config: &PluginsConfigInput,
|
||||
auth: Option<CodexAuth>,
|
||||
scopes: BTreeSet<RemotePluginScope>,
|
||||
mode: RemoteCatalogCacheRefreshMode,
|
||||
) {
|
||||
if !config.plugins_enabled || scopes.is_empty() {
|
||||
return;
|
||||
@@ -1934,7 +1922,6 @@ impl PluginsManager {
|
||||
service_config: self.remote_plugin_service_config(config),
|
||||
auth,
|
||||
scopes,
|
||||
mode,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1950,12 +1937,10 @@ impl PluginsManager {
|
||||
&context.global_config,
|
||||
auth.clone(),
|
||||
options.remote_catalog_cache_refresh_scopes,
|
||||
RemoteCatalogCacheRefreshMode::OnlyIfStale,
|
||||
);
|
||||
self.maybe_start_remote_plugin_caches_refresh(
|
||||
&context.global_config,
|
||||
auth.clone(),
|
||||
/*prefetch_recommendations*/ true,
|
||||
on_effective_plugins_changed.clone(),
|
||||
);
|
||||
self.maybe_start_remote_installed_plugin_bundle_sync(
|
||||
@@ -2957,7 +2942,6 @@ impl PluginsManager {
|
||||
pub fn maybe_start_plugin_startup_tasks_for_config(
|
||||
self: &Arc<Self>,
|
||||
config: &PluginsConfigInput,
|
||||
prefetch_discovery: bool,
|
||||
reload_config: ConfigLayerReload,
|
||||
on_effective_plugins_changed: Option<EffectivePluginsChangedCallback>,
|
||||
upgrade_warnings: mpsc::UnboundedSender<String>,
|
||||
@@ -3044,53 +3028,20 @@ impl PluginsManager {
|
||||
}
|
||||
let config_for_remote_sync = config.clone();
|
||||
let manager = Arc::clone(self);
|
||||
let on_effective_plugins_changed = on_effective_plugins_changed.clone();
|
||||
tokio::spawn(async move {
|
||||
let auth = manager.auth_manager.auth().await;
|
||||
manager.maybe_start_remote_plugin_caches_refresh(
|
||||
manager.maybe_start_remote_installed_plugins_cache_refresh_with_notify(
|
||||
&config_for_remote_sync,
|
||||
auth.clone(),
|
||||
prefetch_discovery,
|
||||
RemoteInstalledPluginsCacheRefreshNotify::IfCacheChanged,
|
||||
on_effective_plugins_changed.clone(),
|
||||
EffectivePluginsChange::default(),
|
||||
);
|
||||
manager.maybe_start_remote_installed_plugin_bundle_sync(
|
||||
&config_for_remote_sync,
|
||||
auth.clone(),
|
||||
auth,
|
||||
on_effective_plugins_changed,
|
||||
);
|
||||
if !prefetch_discovery {
|
||||
return;
|
||||
}
|
||||
let scopes_to_probe: &[RemotePluginScope] =
|
||||
if config_for_remote_sync.remote_plugin_enabled {
|
||||
&[RemotePluginScope::User, RemotePluginScope::Workspace]
|
||||
} else {
|
||||
&[RemotePluginScope::Workspace]
|
||||
};
|
||||
let mut scopes = crate::remote::cached_remote_plugin_catalog_scopes(
|
||||
manager.codex_home.as_path(),
|
||||
&manager.remote_plugin_service_config(&config_for_remote_sync),
|
||||
auth.as_ref(),
|
||||
scopes_to_probe,
|
||||
);
|
||||
if config_for_remote_sync.remote_plugin_enabled {
|
||||
scopes.insert(RemotePluginScope::Global);
|
||||
}
|
||||
manager.maybe_start_remote_catalog_cache_refresh(
|
||||
&config_for_remote_sync,
|
||||
auth.clone(),
|
||||
scopes,
|
||||
RemoteCatalogCacheRefreshMode::Force,
|
||||
);
|
||||
if let Err(err) = manager
|
||||
.featured_plugin_ids_for_config(&config_for_remote_sync, auth.as_ref())
|
||||
.await
|
||||
{
|
||||
warn!(
|
||||
error = %err,
|
||||
"failed to warm featured plugin ids cache"
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -3326,16 +3277,6 @@ impl PluginsManager {
|
||||
{
|
||||
pending.scopes.extend(request.scopes);
|
||||
pending.auth = request.auth;
|
||||
pending.mode = match (pending.mode, request.mode) {
|
||||
(RemoteCatalogCacheRefreshMode::Force, _)
|
||||
| (_, RemoteCatalogCacheRefreshMode::Force) => {
|
||||
RemoteCatalogCacheRefreshMode::Force
|
||||
}
|
||||
(
|
||||
RemoteCatalogCacheRefreshMode::OnlyIfStale,
|
||||
RemoteCatalogCacheRefreshMode::OnlyIfStale,
|
||||
) => RemoteCatalogCacheRefreshMode::OnlyIfStale,
|
||||
};
|
||||
} else {
|
||||
state.requests.push_back(request);
|
||||
}
|
||||
@@ -3596,14 +3537,12 @@ impl PluginsManager {
|
||||
};
|
||||
|
||||
for scope in request.scopes {
|
||||
if request.mode == RemoteCatalogCacheRefreshMode::OnlyIfStale
|
||||
&& crate::remote::has_fresh_cached_remote_plugin_catalog(
|
||||
self.codex_home.as_path(),
|
||||
&request.service_config,
|
||||
request.auth.as_ref(),
|
||||
scope,
|
||||
)
|
||||
{
|
||||
if crate::remote::has_fresh_cached_remote_plugin_catalog(
|
||||
self.codex_home.as_path(),
|
||||
&request.service_config,
|
||||
request.auth.as_ref(),
|
||||
scope,
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -6152,7 +6152,6 @@ plugins = true
|
||||
manager.maybe_start_remote_plugin_caches_refresh(
|
||||
&config,
|
||||
Some(auth.clone()),
|
||||
/*prefetch_recommendations*/ true,
|
||||
/*on_effective_plugins_changed*/ None,
|
||||
);
|
||||
|
||||
|
||||
@@ -1272,30 +1272,6 @@ pub(crate) fn has_fresh_cached_remote_plugin_catalog(
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn cached_remote_plugin_catalog_scopes(
|
||||
codex_home: &Path,
|
||||
config: &RemotePluginServiceConfig,
|
||||
auth: Option<&CodexAuth>,
|
||||
scopes: &[RemotePluginScope],
|
||||
) -> BTreeSet<RemotePluginScope> {
|
||||
let Ok(auth) = ensure_chatgpt_auth(auth) else {
|
||||
return BTreeSet::new();
|
||||
};
|
||||
scopes
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|scope| {
|
||||
catalog_cache::has_cached_directory_plugins(
|
||||
codex_home,
|
||||
config,
|
||||
auth,
|
||||
*scope,
|
||||
RemotePluginCatalogCacheMode::PreferCache,
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn cached_global_remote_discoverable_plugins(
|
||||
codex_home: &Path,
|
||||
config: &RemotePluginServiceConfig,
|
||||
|
||||
@@ -480,10 +480,10 @@ fn catalog_cache_invalidation_clears_global_collections_and_preserves_other_scop
|
||||
);
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
cached_remote_plugin_catalog_scopes(codex_home.path(), &config, Some(&auth), &scopes),
|
||||
BTreeSet::from(scopes)
|
||||
);
|
||||
let has_cached_scope = |scope| {
|
||||
has_fresh_cached_remote_plugin_catalog(codex_home.path(), &config, Some(&auth), scope)
|
||||
};
|
||||
assert!(scopes.into_iter().all(has_cached_scope));
|
||||
catalog_cache::write_cached_directory_plugins(
|
||||
codex_home.path(),
|
||||
&config,
|
||||
@@ -500,10 +500,8 @@ fn catalog_cache_invalidation_clears_global_collections_and_preserves_other_scop
|
||||
&[RemotePluginScope::Global],
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
cached_remote_plugin_catalog_scopes(codex_home.path(), &config, Some(&auth), &scopes),
|
||||
BTreeSet::from([RemotePluginScope::Workspace]),
|
||||
);
|
||||
assert!(!has_cached_scope(RemotePluginScope::Global));
|
||||
assert!(has_cached_scope(RemotePluginScope::Workspace));
|
||||
assert!(
|
||||
catalog_cache::load_cached_directory_plugins::<RemotePluginDirectoryItem>(
|
||||
codex_home.path(),
|
||||
|
||||
@@ -268,7 +268,7 @@ fn parse_feature_requirements(
|
||||
) -> BTreeMap<Feature, bool> {
|
||||
let mut pinned_features = BTreeMap::new();
|
||||
for (key, enabled) in feature_requirements.entries {
|
||||
if key == Feature::Personality.key() {
|
||||
if key == Feature::Personality.key() || key == Feature::PluginDiscoveryPrefetch.key() {
|
||||
continue;
|
||||
}
|
||||
if key == Feature::Worktrees.key() || key == Feature::RealtimeConversation.key() {
|
||||
|
||||
@@ -378,8 +378,7 @@ pub enum Feature {
|
||||
ComputerUse,
|
||||
/// Enable the PS-backed remote plugin catalog.
|
||||
RemotePlugin,
|
||||
/// Prefetch the plugin directory, featured plugins, and recommendations at app-server startup.
|
||||
/// Inventory-only hosts can disable this. Legacy suggestions read the cached directory.
|
||||
/// Removed compatibility flag for plugin discovery prefetch.
|
||||
PluginDiscoveryPrefetch,
|
||||
/// Enable remote plugin sharing flows.
|
||||
PluginSharing,
|
||||
@@ -770,7 +769,7 @@ impl Features {
|
||||
"image_detail_original" | "resize_all_images" | "item_ids" => {
|
||||
continue;
|
||||
}
|
||||
"personality" | "plugin_hooks" => {
|
||||
"personality" | "plugin_discovery_prefetch" | "plugin_hooks" => {
|
||||
continue;
|
||||
}
|
||||
"skill_env_var_dependency_prompt" => {
|
||||
@@ -1952,8 +1951,8 @@ pub const FEATURES: &[FeatureSpec] = &[
|
||||
FeatureSpec {
|
||||
id: Feature::PluginDiscoveryPrefetch,
|
||||
key: "plugin_discovery_prefetch",
|
||||
stage: Stage::Stable,
|
||||
default_enabled: true,
|
||||
stage: Stage::Removed,
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::PluginSharing,
|
||||
|
||||
Reference in New Issue
Block a user