Add plugin list force refetch

This commit is contained in:
Alex Phonpradith
2026-06-24 11:49:35 -07:00
parent a781761eda
commit 1bd4c528b1
13 changed files with 151 additions and 2 deletions
@@ -1905,6 +1905,10 @@
"null"
]
},
"forceRefetch": {
"description": "When true, bypass app/tool caches that can stay stale after external app re-auth.",
"type": "boolean"
},
"marketplaceKinds": {
"description": "Optional marketplace kind filter. When omitted, only local marketplaces are queried, plus the default remote catalog when enabled by feature flag.",
"items": {
@@ -13525,6 +13525,10 @@
"null"
]
},
"forceRefetch": {
"description": "When true, bypass app/tool caches that can stay stale after external app re-auth.",
"type": "boolean"
},
"marketplaceKinds": {
"description": "Optional marketplace kind filter. When omitted, only local marketplaces are queried, plus the default remote catalog when enabled by feature flag.",
"items": {
@@ -9929,6 +9929,10 @@
"null"
]
},
"forceRefetch": {
"description": "When true, bypass app/tool caches that can stay stale after external app re-auth.",
"type": "boolean"
},
"marketplaceKinds": {
"description": "Optional marketplace kind filter. When omitted, only local marketplaces are queried, plus the default remote catalog when enabled by feature flag.",
"items": {
@@ -27,6 +27,10 @@
"null"
]
},
"forceRefetch": {
"description": "When true, bypass app/tool caches that can stay stale after external app re-auth.",
"type": "boolean"
},
"marketplaceKinds": {
"description": "Optional marketplace kind filter. When omitted, only local marketplaces are queried, plus the default remote catalog when enabled by feature flag.",
"items": {
@@ -14,4 +14,8 @@ cwds?: Array<AbsolutePathBuf> | null,
* Optional marketplace kind filter. When omitted, only local marketplaces are queried, plus
* the default remote catalog when enabled by feature flag.
*/
marketplaceKinds?: Array<PluginListMarketplaceKind> | null, };
marketplaceKinds?: Array<PluginListMarketplaceKind> | null,
/**
* When true, bypass app/tool caches that can stay stale after external app re-auth.
*/
forceRefetch?: boolean, };
@@ -1875,6 +1875,7 @@ mod tests {
params: v2::PluginListParams {
cwds: None,
marketplace_kinds: None,
force_refetch: false,
},
};
assert_eq!(plugin_list.serialization_scope(), None);
@@ -135,6 +135,9 @@ pub struct PluginListParams {
/// the default remote catalog when enabled by feature flag.
#[ts(optional = nullable)]
pub marketplace_kinds: Option<Vec<PluginListMarketplaceKind>>,
/// When true, bypass app/tool caches that can stay stale after external app re-auth.
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub force_refetch: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
@@ -3032,6 +3032,7 @@ fn plugin_list_params_ignore_removed_force_remote_sync_field() {
PluginListParams {
cwds: None,
marketplace_kinds: None,
force_refetch: false,
},
);
}
@@ -3048,6 +3049,7 @@ fn plugin_list_params_serializes_marketplace_kind_filter() {
PluginListMarketplaceKind::SharedWithMe,
PluginListMarketplaceKind::CreatedByMeRemote,
]),
force_refetch: false,
})
.unwrap(),
json!({
@@ -3063,6 +3065,23 @@ fn plugin_list_params_serializes_marketplace_kind_filter() {
);
}
#[test]
fn plugin_list_params_serializes_force_refetch_when_enabled() {
assert_eq!(
serde_json::to_value(PluginListParams {
cwds: None,
marketplace_kinds: None,
force_refetch: true,
})
.unwrap(),
json!({
"cwds": null,
"marketplaceKinds": null,
"forceRefetch": true,
}),
);
}
#[test]
fn plugin_installed_params_serializes_install_suggestion_names() {
assert_eq!(
@@ -533,6 +533,7 @@ impl PluginRequestProcessor {
let PluginListParams {
cwds,
marketplace_kinds,
force_refetch,
} = params;
let roots = cwds.unwrap_or_default();
let explicit_marketplace_kinds = marketplace_kinds.is_some();
@@ -560,6 +561,10 @@ impl PluginRequestProcessor {
let auth_mode = auth.as_ref().map(CodexAuth::api_auth_mode);
plugins_manager.set_auth_mode(auth_mode);
let plugins_input = config.plugins_config_input();
if force_refetch {
self.force_refresh_app_caches_for_plugin_list(&config, auth.as_ref())
.await;
}
let include_shared_with_me =
marketplace_kinds.contains(&PluginListMarketplaceKind::SharedWithMe);
let include_created_by_me_remote = marketplace_kinds
@@ -738,6 +743,7 @@ impl PluginRequestProcessor {
|| include_shared_with_me
|| include_global_remote
{
let on_effective_plugins_changed = Some(self.effective_plugins_changed_callback());
plugins_manager.maybe_start_plugin_list_background_tasks_for_config(
&plugins_input,
auth.clone(),
@@ -745,8 +751,15 @@ impl PluginRequestProcessor {
PluginListBackgroundTaskOptions {
refresh_global_remote_catalog_cache,
},
Some(self.effective_plugins_changed_callback()),
on_effective_plugins_changed.clone(),
);
if force_refetch {
plugins_manager.maybe_start_remote_installed_plugins_cache_refresh_after_mutation(
&plugins_input,
auth.clone(),
on_effective_plugins_changed,
);
}
}
let featured_plugin_ids = if data.iter().any(|marketplace| {
@@ -777,6 +790,64 @@ impl PluginRequestProcessor {
})
}
async fn force_refresh_app_caches_for_plugin_list(
&self,
config: &Config,
auth: Option<&CodexAuth>,
) {
if !config
.features
.apps_enabled_for_auth(auth.is_some_and(CodexAuth::uses_codex_backend))
{
return;
}
let plugins_manager = self.thread_manager.plugins_manager();
let plugin_apps = plugins_manager
.plugins_for_config(&config.plugins_config_input())
.await
.effective_apps();
let environment_manager = self.thread_manager.environment_manager();
let (all_connectors_result, accessible_connectors_result) = tokio::join!(
connectors::list_all_connectors_with_options(
config,
/*force_refetch*/ true,
&plugin_apps,
),
connectors::list_accessible_connectors_from_mcp_tools_with_mcp_manager(
config,
/*force_refetch*/ true,
Arc::clone(&environment_manager),
self.thread_manager.mcp_manager(),
),
);
match all_connectors_result {
Ok(_) => {}
Err(err) => {
warn!("plugin/list forceRefetch failed to refresh app directory cache: {err:#}");
}
}
let mut refreshed_codex_apps_tools_cache = false;
match accessible_connectors_result {
Ok(status) => {
refreshed_codex_apps_tools_cache = true;
if !status.codex_apps_ready {
warn!(
"plugin/list forceRefetch refreshed Codex Apps cache before codex-apps was ready"
);
}
}
Err(err) => {
warn!("plugin/list forceRefetch failed to refresh Codex Apps tools cache: {err:#}");
}
}
if refreshed_codex_apps_tools_cache {
self.on_effective_plugins_changed();
}
}
async fn plugin_installed_response(
&self,
params: PluginInstalledParams,
@@ -504,6 +504,7 @@ async fn external_agent_config_import_sends_completion_notification_for_local_pl
.send_plugin_list_request(PluginListParams {
cwds: None,
marketplace_kinds: None,
force_refetch: false,
})
.await?;
let response: JSONRPCResponse = timeout(
@@ -97,6 +97,7 @@ async fn plugin_list_skips_invalid_marketplace_file_and_reports_error() -> Resul
.send_plugin_list_request(PluginListParams {
cwds: Some(vec![AbsolutePathBuf::try_from(repo_root.path())?]),
marketplace_kinds: None,
force_refetch: false,
})
.await?;
@@ -417,6 +418,7 @@ async fn plugin_list_keeps_valid_marketplaces_when_another_marketplace_fails_to_
AbsolutePathBuf::try_from(invalid_repo_root.path())?,
]),
marketplace_kinds: None,
force_refetch: false,
})
.await?;
@@ -531,6 +533,7 @@ async fn plugin_list_returns_empty_when_workspace_codex_plugins_disabled() -> Re
.send_plugin_list_request(PluginListParams {
cwds: Some(vec![AbsolutePathBuf::try_from(repo_root.path())?]),
marketplace_kinds: None,
force_refetch: false,
})
.await?;
@@ -623,6 +626,7 @@ async fn plugin_list_reuses_cached_workspace_codex_plugins_setting() -> Result<(
.send_plugin_list_request(PluginListParams {
cwds: Some(vec![AbsolutePathBuf::try_from(repo_root.path())?]),
marketplace_kinds: None,
force_refetch: false,
})
.await?;
@@ -707,6 +711,7 @@ async fn plugin_list_uses_alternate_discoverable_manifest_and_keeps_undiscoverab
.send_plugin_list_request(PluginListParams {
cwds: Some(vec![AbsolutePathBuf::try_from(repo_root.path())?]),
marketplace_kinds: None,
force_refetch: false,
})
.await?;
@@ -822,6 +827,7 @@ async fn plugin_list_accepts_omitted_cwds() -> Result<()> {
.send_plugin_list_request(PluginListParams {
cwds: None,
marketplace_kinds: None,
force_refetch: false,
})
.await?;
@@ -875,6 +881,7 @@ async fn plugin_list_returns_share_context_for_shared_local_plugin() -> Result<(
.send_plugin_list_request(PluginListParams {
cwds: Some(vec![AbsolutePathBuf::try_from(repo_root.path())?]),
marketplace_kinds: None,
force_refetch: false,
})
.await?;
@@ -967,6 +974,7 @@ enabled = false
.send_plugin_list_request(PluginListParams {
cwds: Some(vec![AbsolutePathBuf::try_from(repo_root.path())?]),
marketplace_kinds: None,
force_refetch: false,
})
.await?;
@@ -1124,6 +1132,7 @@ enabled = false
AbsolutePathBuf::try_from(workspace_default.path())?,
]),
marketplace_kinds: None,
force_refetch: false,
})
.await?;
@@ -1208,6 +1217,7 @@ async fn plugin_list_returns_plugin_interface_with_absolute_asset_paths() -> Res
.send_plugin_list_request(PluginListParams {
cwds: Some(vec![AbsolutePathBuf::try_from(repo_root.path())?]),
marketplace_kinds: None,
force_refetch: false,
})
.await?;
@@ -1321,6 +1331,7 @@ async fn plugin_list_accepts_legacy_string_default_prompt() -> Result<()> {
.send_plugin_list_request(PluginListParams {
cwds: Some(vec![AbsolutePathBuf::try_from(repo_root.path())?]),
marketplace_kinds: None,
force_refetch: false,
})
.await?;
@@ -1409,6 +1420,7 @@ enabled = true
.send_plugin_list_request(PluginListParams {
cwds: Some(vec![AbsolutePathBuf::try_from(repo_root.path())?]),
marketplace_kinds: None,
force_refetch: false,
})
.await?;
@@ -1599,6 +1611,7 @@ async fn plugin_list_sync_upgrades_and_removes_remote_installed_plugin_bundles()
.send_plugin_list_request(PluginListParams {
cwds: None,
marketplace_kinds: None,
force_refetch: false,
})
.await?;
let response: JSONRPCResponse = timeout(
@@ -1779,6 +1792,7 @@ async fn plugin_list_includes_remote_marketplaces_when_remote_plugin_enabled() -
.send_plugin_list_request(PluginListParams {
cwds: None,
marketplace_kinds: None,
force_refetch: false,
})
.await?;
@@ -1922,6 +1936,7 @@ async fn plugin_list_uses_cached_global_remote_catalog_and_refreshes_it() -> Res
.send_plugin_list_request(PluginListParams {
cwds: None,
marketplace_kinds: None,
force_refetch: false,
})
.await?;
let response: PluginListResponse = to_response(
@@ -1955,6 +1970,7 @@ async fn plugin_list_uses_cached_global_remote_catalog_and_refreshes_it() -> Res
.send_plugin_list_request(PluginListParams {
cwds: None,
marketplace_kinds: None,
force_refetch: false,
})
.await?;
let response: PluginListResponse = to_response(
@@ -2036,6 +2052,7 @@ async fn plugin_list_includes_openai_curated_remote_collection_when_requested()
.send_plugin_list_request(PluginListParams {
cwds: None,
marketplace_kinds: Some(vec![PluginListMarketplaceKind::Vertical]),
force_refetch: false,
})
.await?;
let response: JSONRPCResponse = timeout(
@@ -2124,6 +2141,7 @@ async fn plugin_list_propagates_explicit_openai_curated_remote_collection_errors
.send_plugin_list_request(PluginListParams {
cwds: None,
marketplace_kinds: Some(vec![PluginListMarketplaceKind::Vertical]),
force_refetch: false,
})
.await?;
let err = timeout(
@@ -2163,6 +2181,7 @@ async fn plugin_list_skips_explicit_openai_curated_remote_collection_for_api_aut
.send_plugin_list_request(PluginListParams {
cwds: None,
marketplace_kinds: Some(vec![PluginListMarketplaceKind::Vertical]),
force_refetch: false,
})
.await?;
let response: JSONRPCResponse = timeout(
@@ -2202,6 +2221,7 @@ async fn plugin_list_includes_api_curated_marketplace_for_api_auth_when_remote_p
.send_plugin_list_request(PluginListParams {
cwds: None,
marketplace_kinds: None,
force_refetch: false,
})
.await?;
let response: JSONRPCResponse = timeout(
@@ -2257,6 +2277,7 @@ async fn plugin_list_does_not_query_openai_curated_remote_collection_by_default(
.send_plugin_list_request(PluginListParams {
cwds: None,
marketplace_kinds: None,
force_refetch: false,
})
.await?;
let response: JSONRPCResponse = timeout(
@@ -2310,6 +2331,7 @@ async fn plugin_list_vertical_kind_noops_when_remote_plugin_enabled() -> Result<
.send_plugin_list_request(PluginListParams {
cwds: None,
marketplace_kinds: Some(vec![PluginListMarketplaceKind::Vertical]),
force_refetch: false,
})
.await?;
let response: JSONRPCResponse = timeout(
@@ -2364,6 +2386,7 @@ async fn plugin_list_does_not_append_global_remote_when_marketplace_kinds_are_ex
.send_plugin_list_request(PluginListParams {
cwds: None,
marketplace_kinds: Some(vec![PluginListMarketplaceKind::Local]),
force_refetch: false,
})
.await?;
@@ -2785,6 +2808,7 @@ async fn plugin_list_fetches_workspace_directory_kind_without_remote_plugin_flag
.send_plugin_list_request(PluginListParams {
cwds: None,
marketplace_kinds: Some(vec![PluginListMarketplaceKind::WorkspaceDirectory]),
force_refetch: false,
})
.await?;
@@ -2912,6 +2936,7 @@ plugin_sharing = false
.send_plugin_list_request(PluginListParams {
cwds: None,
marketplace_kinds: Some(vec![PluginListMarketplaceKind::CreatedByMeRemote]),
force_refetch: false,
})
.await?;
@@ -3039,6 +3064,7 @@ async fn plugin_list_fetches_shared_with_me_kind() -> Result<()> {
.send_plugin_list_request(PluginListParams {
cwds: None,
marketplace_kinds: Some(vec![PluginListMarketplaceKind::SharedWithMe]),
force_refetch: false,
})
.await?;
@@ -3196,6 +3222,7 @@ plugin_sharing = false
.send_plugin_list_request(PluginListParams {
cwds: None,
marketplace_kinds: Some(vec![PluginListMarketplaceKind::SharedWithMe]),
force_refetch: false,
})
.await?;
@@ -3256,6 +3283,7 @@ plugin_sharing = true
.send_plugin_list_request(PluginListParams {
cwds: None,
marketplace_kinds: Some(vec![PluginListMarketplaceKind::CreatedByMeRemote]),
force_refetch: false,
})
.await?;
@@ -3386,6 +3414,7 @@ async fn plugin_list_marks_remote_plugin_disabled_by_admin() -> Result<()> {
.send_plugin_list_request(PluginListParams {
cwds: None,
marketplace_kinds: None,
force_refetch: false,
})
.await?;
@@ -3446,6 +3475,7 @@ remote_plugin = true
.send_plugin_list_request(PluginListParams {
cwds: None,
marketplace_kinds: None,
force_refetch: false,
})
.await?;
@@ -3482,6 +3512,7 @@ async fn plugin_list_fetches_featured_plugin_ids_without_chatgpt_auth() -> Resul
.send_plugin_list_request(PluginListParams {
cwds: None,
marketplace_kinds: None,
force_refetch: false,
})
.await?;
@@ -3522,6 +3553,7 @@ async fn plugin_list_uses_warmed_featured_plugin_ids_cache_on_first_request() ->
.send_plugin_list_request(PluginListParams {
cwds: None,
marketplace_kinds: None,
force_refetch: false,
})
.await?;
@@ -719,6 +719,7 @@ async fn plugin_share_checkout_adds_personal_marketplace_entry() -> Result<()> {
marketplace_kinds: Some(vec![
codex_app_server_protocol::PluginListMarketplaceKind::Local,
]),
force_refetch: false,
})
.await?;
let response: JSONRPCResponse = timeout(
@@ -390,6 +390,7 @@ async fn skills_list_loads_remote_installed_plugin_skills_from_cache() -> Result
.send_plugin_list_request(PluginListParams {
cwds: None,
marketplace_kinds: None,
force_refetch: false,
})
.await?;
let plugin_list_response: JSONRPCResponse = timeout(