Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 28 additions & 13 deletions codex-rs/utils/plugins/src/plugin_namespace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,28 +39,27 @@ pub fn agent_plugin_schema_status(contents: &str) -> AgentPluginSchemaStatus {

pub fn find_plugin_manifest_path(plugin_root: &Path) -> Option<PathBuf> {
let agent_manifest_path = plugin_root.join(AGENT_PLUGIN_MANIFEST_RELATIVE_PATH);
match std::fs::symlink_metadata(&agent_manifest_path) {
let agent_plugin_manifest = match std::fs::symlink_metadata(&agent_manifest_path) {
Ok(metadata) if metadata.file_type().is_symlink() || !metadata.file_type().is_file() => {
return None;
}
Ok(_) => {
if std::fs::read_to_string(&agent_manifest_path)
.ok()
.is_some_and(|contents| {
agent_plugin_schema_status(&contents) != AgentPluginSchemaStatus::Unrelated
})
{
return Some(agent_manifest_path);
}
}
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {}
Ok(_) => std::fs::read_to_string(&agent_manifest_path)
.ok()
.is_some_and(|contents| {
agent_plugin_schema_status(&contents) != AgentPluginSchemaStatus::Unrelated
})
.then_some(agent_manifest_path),
Err(err) if err.kind() == std::io::ErrorKind::NotFound => None,
Err(_) => return None,
}
};

// Prefer a Codex-side manifest over the Agent Plugin spec: the spec can't yet
// forward env vars to MCP servers (openai/codex#38438). Revert at parity.
DISCOVERABLE_PLUGIN_MANIFEST_PATHS
.iter()
.map(|relative_path| plugin_root.join(relative_path))
.find(|manifest_path| manifest_path.is_file())
.or(agent_plugin_manifest)
}

#[derive(serde::Deserialize)]
Expand Down Expand Up @@ -203,6 +202,22 @@ mod tests {
assert_eq!(find_plugin_manifest_path(&plugin_root), Some(manifest_path));
}

#[test]
fn prefers_codex_plugin_over_schema_declared_root_manifest() {
let tmp = tempdir().expect("tempdir");
let plugin_root = tmp.path().join("plugins/portable");
let codex_path = plugin_root.join(".codex-plugin/plugin.json");
fs::create_dir_all(codex_path.parent().expect("parent")).expect("mkdir");
fs::write(
plugin_root.join(AGENT_PLUGIN_MANIFEST_RELATIVE_PATH),
format!(r#"{{"$schema":"{AGENT_PLUGIN_SCHEMA_URI}","name":"portable"}}"#),
)
.expect("write spec manifest");
fs::write(&codex_path, r#"{"name":"portable"}"#).expect("write codex manifest");

assert_eq!(find_plugin_manifest_path(&plugin_root), Some(codex_path));
}

#[test]
fn ignores_unrelated_root_plugin_manifest_before_legacy_fallback() {
let tmp = tempdir().expect("tempdir");
Expand Down