mirror of
https://github.com/openai/codex.git
synced 2026-06-01 19:02:59 +00:00
Read cached metadata for installed Git plugins (#20825)
## Summary - Populate `plugin/list` interface metadata for installed Git-sourced marketplace plugins from the active cached plugin bundle. - Preserve marketplace category precedence so list behavior matches `plugin/read`. - Keep existing fallback behavior when the cache or manifest is missing or invalid. ## Test Plan - `cd codex-rs && just fmt` - `cd codex-rs && cargo test -p codex-core-plugins list_marketplaces_installed_git_source_reads_metadata_from_cache_without_cloning` - `cd codex-rs && cargo test -p codex-app-server plugin_list_returns_installed_git_source_interface_from_cache` - `cd codex-rs && just fix -p codex-core-plugins` - `cd codex-rs && just fix -p codex-app-server` - `git diff --check` Server-truth check: OpenAI monorepo app-server generated types already expose `PluginSummary.interface`, and the webview consumes it for plugin cards. This PR keeps the protocol/schema unchanged and fills the existing field from the cached installed bundle for Git-backed cross-repo plugins.
This commit is contained in:
@@ -1141,6 +1141,124 @@ async fn plugin_list_accepts_legacy_string_default_prompt() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn plugin_list_returns_installed_git_source_interface_from_cache() -> Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
let repo_root = TempDir::new()?;
|
||||
let missing_remote_repo = repo_root.path().join("missing-remote-plugin-repo");
|
||||
let missing_remote_repo_url = url::Url::from_directory_path(&missing_remote_repo)
|
||||
.unwrap()
|
||||
.to_string();
|
||||
std::fs::create_dir_all(repo_root.path().join(".git"))?;
|
||||
std::fs::create_dir_all(repo_root.path().join(".agents/plugins"))?;
|
||||
std::fs::write(
|
||||
repo_root.path().join(".agents/plugins/marketplace.json"),
|
||||
format!(
|
||||
r#"{{
|
||||
"name": "debug",
|
||||
"plugins": [
|
||||
{{
|
||||
"name": "toolkit",
|
||||
"source": {{
|
||||
"source": "git-subdir",
|
||||
"url": "{missing_remote_repo_url}",
|
||||
"path": "plugins/toolkit"
|
||||
}},
|
||||
"category": "Developer Tools"
|
||||
}}
|
||||
]
|
||||
}}"#
|
||||
),
|
||||
)?;
|
||||
let cached_plugin_root = codex_home.path().join("plugins/cache/debug/toolkit/local");
|
||||
std::fs::create_dir_all(cached_plugin_root.join(".codex-plugin"))?;
|
||||
std::fs::write(
|
||||
cached_plugin_root.join(".codex-plugin/plugin.json"),
|
||||
r##"{
|
||||
"name": "toolkit",
|
||||
"interface": {
|
||||
"displayName": "Toolkit",
|
||||
"shortDescription": "Search cached data",
|
||||
"category": "Cached Category",
|
||||
"brandColor": "#3B82F6",
|
||||
"composerIcon": "./assets/icon.png",
|
||||
"logo": "./assets/logo.png"
|
||||
}
|
||||
}"##,
|
||||
)?;
|
||||
std::fs::write(
|
||||
codex_home.path().join("config.toml"),
|
||||
r#"[features]
|
||||
plugins = true
|
||||
|
||||
[plugins."toolkit@debug"]
|
||||
enabled = true
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let mut mcp = McpProcess::new(codex_home.path()).await?;
|
||||
timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??;
|
||||
|
||||
let request_id = mcp
|
||||
.send_plugin_list_request(PluginListParams {
|
||||
cwds: Some(vec![AbsolutePathBuf::try_from(repo_root.path())?]),
|
||||
marketplace_kinds: None,
|
||||
})
|
||||
.await?;
|
||||
|
||||
let response: JSONRPCResponse = timeout(
|
||||
DEFAULT_TIMEOUT,
|
||||
mcp.read_stream_until_response_message(RequestId::Integer(request_id)),
|
||||
)
|
||||
.await??;
|
||||
let response: PluginListResponse = to_response(response)?;
|
||||
|
||||
let plugin = response
|
||||
.marketplaces
|
||||
.iter()
|
||||
.flat_map(|marketplace| marketplace.plugins.iter())
|
||||
.find(|plugin| plugin.name == "toolkit")
|
||||
.expect("expected toolkit entry");
|
||||
|
||||
assert_eq!(plugin.id, "toolkit@debug");
|
||||
assert_eq!(plugin.installed, true);
|
||||
assert_eq!(plugin.enabled, true);
|
||||
assert_eq!(
|
||||
plugin.source,
|
||||
PluginSource::Git {
|
||||
url: missing_remote_repo_url,
|
||||
path: Some("plugins/toolkit".to_string()),
|
||||
ref_name: None,
|
||||
sha: None,
|
||||
}
|
||||
);
|
||||
let interface = plugin
|
||||
.interface
|
||||
.as_ref()
|
||||
.expect("expected cached plugin interface");
|
||||
assert_eq!(interface.display_name.as_deref(), Some("Toolkit"));
|
||||
assert_eq!(
|
||||
interface.short_description.as_deref(),
|
||||
Some("Search cached data")
|
||||
);
|
||||
assert_eq!(interface.category.as_deref(), Some("Developer Tools"));
|
||||
assert_eq!(interface.brand_color.as_deref(), Some("#3B82F6"));
|
||||
let canonical_cached_plugin_root = std::fs::canonicalize(&cached_plugin_root)?;
|
||||
assert_eq!(
|
||||
interface.composer_icon,
|
||||
Some(AbsolutePathBuf::try_from(
|
||||
canonical_cached_plugin_root.join("assets/icon.png")
|
||||
)?)
|
||||
);
|
||||
assert_eq!(
|
||||
interface.logo,
|
||||
Some(AbsolutePathBuf::try_from(
|
||||
canonical_cached_plugin_root.join("assets/logo.png")
|
||||
)?)
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn app_server_startup_remote_plugin_sync_runs_once() -> Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
|
||||
Reference in New Issue
Block a user