mirror of
https://github.com/openai/codex.git
synced 2026-10-20 17:38:45 +00:00
## Why Repeated plugin discovery and loading reparse unchanged manifests and repeat warnings for invalid fields. ## What changed - Share a manifest cache through `PluginStore` across marketplace listing, plugin loading, capability inspection, and tool suggestion metadata. - Reuse successful parses by plugin root, manifest path, and content digests, including the Codex overlay. Reread files on each load so manifest edits and overlay changes or removal remain visible. - Bound the cache to 128 entries and bypass caching for inputs larger than 64 KiB. Retry failed parses and share entries across store clones while keeping independent stores isolated. - Reuse loaded manifest data when loading apps and MCP metadata. ## Testing Add unit coverage for parse reuse, warning deduplication, revision invalidation, repair after parse failures, equivalent Windows paths, store isolation, and oversized inputs. Add an app-server regression test that verifies repeated `plugin/list` requests observe manifest and overlay revisions in one running server. GitOrigin-RevId: ed082cd7d198015cd8ed35db7f2af74e13f49915
96 lines
3.6 KiB
Rust
96 lines
3.6 KiB
Rust
//! Exercise manifest and overlay invalidation across RPCs in one running app-server.
|
|
|
|
use anyhow::Result;
|
|
use app_test_support::TestAppServer;
|
|
use codex_app_server_protocol::PluginListMarketplaceKind;
|
|
use codex_app_server_protocol::PluginListParams;
|
|
use codex_app_server_protocol::PluginListResponse;
|
|
use codex_utils_absolute_path::AbsolutePathBuf;
|
|
use pretty_assertions::assert_eq;
|
|
use serde_json::json;
|
|
use std::time::Duration;
|
|
use tempfile::TempDir;
|
|
use tokio::time::timeout;
|
|
|
|
#[tokio::test]
|
|
async fn plugin_list_observes_manifest_and_overlay_revisions_in_one_server() -> Result<()> {
|
|
let codex_home = TempDir::new()?;
|
|
let repository = TempDir::new()?;
|
|
let root = repository.path();
|
|
for directory in [".git", ".agents/plugins", "sample/.codex-plugin"] {
|
|
std::fs::create_dir_all(root.join(directory))?;
|
|
}
|
|
std::fs::write(
|
|
codex_home.path().join("config.toml"),
|
|
"[features]\nplugins = true\nremote_plugin = false\n",
|
|
)?;
|
|
std::fs::write(
|
|
root.join(".agents/plugins/marketplace.json"),
|
|
serde_json::to_vec(&json!({
|
|
"name": "cache-test", "plugins": [{"name": "sample", "source": {"source": "local", "path": "./sample"}}]
|
|
}))?,
|
|
)?;
|
|
let mut server = TestAppServer::builder()
|
|
.with_codex_home(codex_home.path())
|
|
.build_initialized_with_timeout(Duration::from_secs(30))
|
|
.await?;
|
|
let overlay_path = root.join("sample/.codex-plugin/plugin.json");
|
|
for (version, overlay_name, expected_name) in [
|
|
("1.0.0", Some("First"), "First"),
|
|
("2.0.0", Some("First"), "First"),
|
|
("2.0.0", Some("Changed"), "Changed"),
|
|
("2.0.0", None, "sample"),
|
|
] {
|
|
std::fs::write(
|
|
root.join("sample/plugin.json"),
|
|
serde_json::to_vec(&json!({
|
|
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
|
|
"name": "sample", "version": version
|
|
}))?,
|
|
)?;
|
|
if let Some(display_name) = overlay_name {
|
|
std::fs::write(
|
|
&overlay_path,
|
|
serde_json::to_vec(&json!({"interface": {"displayName": display_name}}))?,
|
|
)?;
|
|
} else {
|
|
std::fs::remove_file(&overlay_path)?;
|
|
}
|
|
let mut previous = None;
|
|
for _ in 0..2 {
|
|
let id = server
|
|
.send_plugin_list_request(PluginListParams {
|
|
cwds: Some(vec![AbsolutePathBuf::try_from(root)?]),
|
|
marketplace_kinds: Some(vec![PluginListMarketplaceKind::Local]),
|
|
force_refetch: false,
|
|
})
|
|
.await?;
|
|
let response: PluginListResponse =
|
|
timeout(Duration::from_secs(30), server.read_response(id)).await??;
|
|
assert_eq!(response.marketplace_load_errors, Vec::new());
|
|
let marketplace = response
|
|
.marketplaces
|
|
.iter()
|
|
.find(|entry| entry.name == "cache-test")
|
|
.expect("local marketplace");
|
|
let plugin = &marketplace.plugins[0];
|
|
assert_eq!(
|
|
(
|
|
plugin.id.as_str(),
|
|
plugin.local_version.as_deref(),
|
|
plugin
|
|
.interface
|
|
.as_ref()
|
|
.and_then(|value| value.display_name.as_deref())
|
|
),
|
|
("sample@cache-test", Some(version), Some(expected_name))
|
|
);
|
|
if let Some(previous) = previous {
|
|
assert_eq!(marketplace, &previous);
|
|
}
|
|
previous = Some(marketplace.clone());
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|