mirror of
https://github.com/openai/codex.git
synced 2026-05-29 23:40:29 +00:00
Fix remote installed bundle cache publication
This commit is contained in:
@@ -171,8 +171,12 @@ pub fn remote_installed_plugins_to_config(
|
||||
}
|
||||
};
|
||||
// Remote installed refresh materializes bundles before publishing state. Keep this
|
||||
// check so partial refresh failures do not make missing local bundles effective.
|
||||
store.active_plugin_root(&plugin_id)?;
|
||||
// version check so partial refresh failures do not make missing or stale local bundles
|
||||
// effective.
|
||||
let release_version = plugin.release_version.as_deref()?;
|
||||
if store.active_plugin_version(&plugin_id).as_deref() != Some(release_version) {
|
||||
return None;
|
||||
}
|
||||
Some((
|
||||
plugin_id.as_key(),
|
||||
PluginConfig {
|
||||
|
||||
@@ -1708,7 +1708,8 @@ impl PluginsManager {
|
||||
let installed_plugins =
|
||||
crate::remote::fetch_remote_installed_plugins(service_config, auth).await?;
|
||||
let mut bundles_changed = false;
|
||||
for plugin in &installed_plugins {
|
||||
let mut publishable_plugins = Vec::new();
|
||||
for plugin in installed_plugins {
|
||||
let validated_bundle = match validate_remote_plugin_bundle(
|
||||
&plugin.id,
|
||||
&plugin.marketplace_name,
|
||||
@@ -1728,12 +1729,12 @@ impl PluginsManager {
|
||||
continue;
|
||||
}
|
||||
};
|
||||
if self
|
||||
.store
|
||||
.active_plugin_version(&validated_bundle.plugin_id)
|
||||
.as_deref()
|
||||
== Some(validated_bundle.plugin_version.as_str())
|
||||
let plugin_id = validated_bundle.plugin_id.clone();
|
||||
let plugin_version = validated_bundle.plugin_version.clone();
|
||||
if self.store.active_plugin_version(&plugin_id).as_deref()
|
||||
== Some(plugin_version.as_str())
|
||||
{
|
||||
publishable_plugins.push(plugin);
|
||||
continue;
|
||||
}
|
||||
match download_and_install_remote_plugin_bundle(
|
||||
@@ -1750,6 +1751,7 @@ impl PluginsManager {
|
||||
path = %result.installed_path.display(),
|
||||
"installed remote plugin bundle during installed-plugin refresh"
|
||||
);
|
||||
publishable_plugins.push(plugin);
|
||||
}
|
||||
Err(err) => {
|
||||
warn!(
|
||||
@@ -1762,7 +1764,11 @@ impl PluginsManager {
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(self.write_remote_installed_plugins_cache(installed_plugins) || bundles_changed)
|
||||
let cache_changed = self.write_remote_installed_plugins_cache(publishable_plugins);
|
||||
if bundles_changed {
|
||||
self.clear_enabled_outcome_cache();
|
||||
}
|
||||
Ok(cache_changed || bundles_changed)
|
||||
}
|
||||
|
||||
fn run_non_curated_plugin_cache_refresh_loop(self: Arc<Self>) {
|
||||
|
||||
@@ -621,6 +621,37 @@ remote_plugin = true
|
||||
assert_eq!(outcome, PluginLoadOutcome::default());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn remote_installed_cache_ignores_plugins_with_stale_local_version() {
|
||||
let codex_home = TempDir::new().unwrap();
|
||||
write_plugin(
|
||||
&codex_home.path().join("plugins/cache/chatgpt-global"),
|
||||
"linear/1.0.0",
|
||||
"linear",
|
||||
);
|
||||
write_file(
|
||||
&codex_home.path().join(CONFIG_TOML_FILE),
|
||||
r#"[features]
|
||||
plugins = true
|
||||
remote_plugin = true
|
||||
"#,
|
||||
);
|
||||
|
||||
let config = load_config(codex_home.path(), codex_home.path()).await;
|
||||
let manager = PluginsManager::new(codex_home.path().to_path_buf());
|
||||
manager.write_remote_installed_plugins_cache(vec![RemoteInstalledPlugin {
|
||||
marketplace_name: "chatgpt-global".to_string(),
|
||||
id: "plugins~Plugin_linear".to_string(),
|
||||
name: "linear".to_string(),
|
||||
enabled: true,
|
||||
release_version: Some("2.0.0".to_string()),
|
||||
bundle_download_url: Some("https://example.com/linear.tar.gz".to_string()),
|
||||
}]);
|
||||
|
||||
let outcome = manager.plugins_for_test_config(&config).await;
|
||||
assert_eq!(outcome, PluginLoadOutcome::default());
|
||||
}
|
||||
|
||||
async fn mount_remote_installed_plugin_pages(
|
||||
server: &MockServer,
|
||||
global_plugins: &str,
|
||||
@@ -688,6 +719,40 @@ fn remote_installed_plugin_json(plugin_name: &str, enabled: bool) -> String {
|
||||
)
|
||||
}
|
||||
|
||||
fn remote_installed_plugin_json_with_release(
|
||||
plugin_name: &str,
|
||||
enabled: bool,
|
||||
version: &str,
|
||||
bundle_download_url: Option<&str>,
|
||||
) -> String {
|
||||
let bundle_download_url = bundle_download_url
|
||||
.map(|url| format!(r#""bundle_download_url": "{url}","#))
|
||||
.unwrap_or_default();
|
||||
format!(
|
||||
r#"{{
|
||||
"id": "plugins~Plugin_{plugin_name}",
|
||||
"name": "{plugin_name}",
|
||||
"scope": "GLOBAL",
|
||||
"installation_policy": "AVAILABLE",
|
||||
"authentication_policy": "ON_USE",
|
||||
"release": {{
|
||||
"version": "{version}",
|
||||
{bundle_download_url}
|
||||
"display_name": "{plugin_name}",
|
||||
"description": "{plugin_name} plugin",
|
||||
"app_ids": [],
|
||||
"interface": {{
|
||||
"short_description": "{plugin_name}",
|
||||
"capabilities": []
|
||||
}},
|
||||
"skills": []
|
||||
}},
|
||||
"enabled": {enabled},
|
||||
"disabled_skill_names": []
|
||||
}}"#
|
||||
)
|
||||
}
|
||||
|
||||
async fn wait_for_counter(counter: &AtomicUsize, expected: usize) {
|
||||
tokio::time::timeout(Duration::from_secs(5), async {
|
||||
loop {
|
||||
@@ -701,6 +766,46 @@ async fn wait_for_counter(counter: &AtomicUsize, expected: usize) {
|
||||
.expect("counter should reach expected value");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn remote_installed_plugins_cache_refresh_does_not_publish_stale_plugin_when_bundle_unavailable()
|
||||
{
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
write_plugin(
|
||||
&tmp.path().join("plugins/cache/chatgpt-global"),
|
||||
"linear/1.0.0",
|
||||
"linear",
|
||||
);
|
||||
write_file(
|
||||
&tmp.path().join(CONFIG_TOML_FILE),
|
||||
r#"[features]
|
||||
plugins = true
|
||||
remote_plugin = true
|
||||
"#,
|
||||
);
|
||||
|
||||
let server = MockServer::start().await;
|
||||
mount_remote_installed_plugin_pages(
|
||||
&server,
|
||||
&remote_installed_plugin_json_with_release("linear", true, "2.0.0", None),
|
||||
"",
|
||||
)
|
||||
.await;
|
||||
|
||||
let config = load_config(tmp.path(), tmp.path()).await;
|
||||
let manager = PluginsManager::new(tmp.path().to_path_buf());
|
||||
let changed = manager
|
||||
.refresh_remote_installed_plugins_cache(
|
||||
&remote_plugin_service_config(&format!("{}/backend-api/", server.uri())),
|
||||
Some(&CodexAuth::create_dummy_chatgpt_auth_for_testing()),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(changed);
|
||||
let outcome = manager.plugins_for_test_config(&config).await;
|
||||
assert_eq!(outcome, PluginLoadOutcome::default());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn remote_installed_plugins_cache_refresh_reconciles_cached_bundles_without_config_writes() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
|
||||
Reference in New Issue
Block a user