Match core skill ordering in extension catalogs (#34746)

## Why

Core-compatible catalog rendering needs to preserve the core renderer's skill
priority, including when metadata budgets omit lower-priority entries.

## What changed

- Carry each host skill's `SkillScope` into its catalog entry.
- Under `CoreCompatible`, order visible skills by system, admin, repo, and user
  scope, then by name and prompt resource ID.
- Preserve insertion order under `ExtensionCompatible`.

## Testing

Add coverage for propagating host skill scope and for both rendering policies.

GitOrigin-RevId: 9594a177edb2166abf1b5719c51a46fcee000600
This commit is contained in:
felixxia-oai authored and copyberry committed 2026-07-22 12:40:16 +00:00
1 parent f21f98936c
commit 2ffe8cd579
5 files changed
+197 -4

No files matched your search

+5
View File
@@ -116,6 +116,7 @@ fn catalog_entry_from_skill(skill: &SkillMetadata, enabled: bool) -> SkillCatalo
)
.with_short_description(skill.short_description.clone())
.with_display_path(display_path)
.with_prompt_scope(skill.scope)
.with_dependencies(skill.dependencies.clone());
if !enabled {
@@ -127,3 +128,7 @@ fn catalog_entry_from_skill(skill: &SkillMetadata, enabled: bool) -> SkillCatalo
entry
}
#[cfg(test)]
#[path = "host_tests.rs"]
mod tests;
@@ -0,0 +1,54 @@
use std::sync::Arc;
use std::time::SystemTime;
use std::time::UNIX_EPOCH;
use codex_core_skills::loader::SkillRoot;
use codex_core_skills::loader::load_skills_from_roots;
use codex_exec_server::LOCAL_FS;
use codex_protocol::protocol::SkillScope;
use codex_utils_absolute_path::AbsolutePathBuf;
use pretty_assertions::assert_eq;
use tokio::sync::Semaphore;
use super::catalog_from_outcome;
#[tokio::test]
async fn host_catalog_entries_carry_their_prompt_scope() -> Result<(), Box<dyn std::error::Error>> {
let unique = SystemTime::now().duration_since(UNIX_EPOCH)?.as_nanos();
let root = std::env::temp_dir().join(format!(
"codex-skills-extension-host-provider-{}-{unique}",
std::process::id()
));
let skill_path = root.join("demo").join("SKILL.md");
std::fs::create_dir_all(
skill_path
.parent()
.ok_or("skill path should have a parent")?,
)?;
std::fs::write(
&skill_path,
"---\nname: demo\ndescription: Demo skill.\n---\n# Demo\n",
)?;
let root = AbsolutePathBuf::try_from(std::fs::canonicalize(root)?)?;
let outcome = load_skills_from_roots(
[SkillRoot {
path: root.clone(),
scope: SkillScope::User,
file_system: Arc::clone(&LOCAL_FS),
plugin_id: None,
plugin_namespace: None,
plugin_root: None,
}],
/*plugin_skill_snapshots*/ None,
Arc::new(Semaphore::new(1)),
)
.await;
let catalog = catalog_from_outcome(&outcome);
assert_eq!(catalog.entries.len(), 1);
assert_eq!(catalog.entries[0].prompt_scope(), Some(SkillScope::User));
std::fs::remove_dir_all(root.as_path())?;
Ok(())
}