mirror of
https://github.com/openai/codex.git
synced 2026-11-13 14:46:14 +00:00
Load host skill roots through the skills extension (#37162)
## What changed - Load ordinary recursive host skill roots with the skills extension's host loader. - Keep plugin-specific roots on the existing loader so plugin snapshot caching and namespacing continue to work. - Merge both kinds of root snapshots through the existing precedence and deduplication logic, including discovery paths and source filesystem mappings. - Move `HostSkillsSnapshot` into the skills extension as the owner of host skill loading. ## Testing - Cover merged host and plugin catalogs, precedence for symlinked roots, discovery paths, and end-to-end rendering and invocation of host, repository, and plugin skills. GitOrigin-RevId: c18080afb917c33c57bc17e36b40dd0f3c72d1d7
This commit is contained in:
1 parent
c38a60ded2
commit
bd36d69aae
21 files changed
+515
-162
No files matched your search
@@ -17,7 +17,6 @@ pub const MAX_SKILL_PROMPT_BYTES: usize = 8_000;
|
||||
pub(crate) use invocation_utils::build_implicit_skill_path_indexes;
|
||||
pub use invocation_utils::detect_implicit_skill_invocation_for_command;
|
||||
pub use mention_counts::build_skill_name_counts;
|
||||
pub use model::HostSkillsSnapshot;
|
||||
pub use model::SkillError;
|
||||
pub use model::SkillLoadOutcome;
|
||||
pub use model::SkillMetadata;
|
||||
|
||||
@@ -2,6 +2,7 @@ mod discovery;
|
||||
mod environment;
|
||||
mod namespace;
|
||||
|
||||
pub use crate::root_loader::load_skill_root_snapshot;
|
||||
pub use environment::EnvironmentSkillLoadOutcome;
|
||||
pub use environment::EnvironmentSkillMetadata;
|
||||
pub use environment::load_environment_skills_from_root;
|
||||
@@ -161,8 +162,12 @@ where
|
||||
.await
|
||||
}
|
||||
|
||||
/// One loaded skill root and the filesystem that supplied it.
|
||||
///
|
||||
/// This is exposed temporarily while host discovery moves out of `core-skills`; callers should
|
||||
/// combine snapshots through [`SkillLoadOutcome::from_root_snapshots`].
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct SkillRootSnapshot {
|
||||
pub struct SkillRootSnapshot {
|
||||
pub(crate) root: AbsolutePathBuf,
|
||||
pub(crate) is_agent_plugin: bool,
|
||||
pub(crate) skills: Vec<SkillMetadata>,
|
||||
@@ -171,6 +176,25 @@ pub(crate) struct SkillRootSnapshot {
|
||||
pub(crate) file_system: Arc<dyn ExecutorFileSystem>,
|
||||
}
|
||||
|
||||
impl SkillRootSnapshot {
|
||||
pub fn new(
|
||||
root: AbsolutePathBuf,
|
||||
skills: Vec<SkillMetadata>,
|
||||
skill_discovery_path_by_path: Arc<HashMap<AbsolutePathBuf, AbsolutePathBuf>>,
|
||||
errors: Vec<SkillError>,
|
||||
file_system: Arc<dyn ExecutorFileSystem>,
|
||||
) -> Self {
|
||||
Self {
|
||||
root,
|
||||
is_agent_plugin: false,
|
||||
skills,
|
||||
skill_discovery_path_by_path,
|
||||
errors,
|
||||
file_system,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn load_skill_root(root: SkillRoot) -> SkillRootSnapshot {
|
||||
let canonical_root =
|
||||
canonicalize_for_skill_identity(root.file_system.as_ref(), &root.path).await;
|
||||
|
||||
@@ -102,27 +102,18 @@ impl SkillLoadOutcome {
|
||||
self.file_systems_by_skill_path
|
||||
.get(&skill.path_to_skills_md)
|
||||
}
|
||||
}
|
||||
|
||||
/// Immutable snapshot of host-owned skills and the filesystem mapping needed
|
||||
/// to read each skill through the environment that discovered it.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct HostSkillsSnapshot {
|
||||
outcome: Arc<SkillLoadOutcome>,
|
||||
}
|
||||
|
||||
impl HostSkillsSnapshot {
|
||||
pub fn new(outcome: Arc<SkillLoadOutcome>) -> Self {
|
||||
Self { outcome }
|
||||
}
|
||||
|
||||
pub fn outcome(&self) -> &SkillLoadOutcome {
|
||||
self.outcome.as_ref()
|
||||
/// Builds the legacy aggregate from independently loaded roots.
|
||||
///
|
||||
/// This is a temporary migration boundary while host root loading moves to the skills
|
||||
/// extension. It deliberately reuses the existing merge, precedence, and deduplication logic.
|
||||
pub fn from_root_snapshots(snapshots: Vec<crate::loader::SkillRootSnapshot>) -> Self {
|
||||
crate::root_loader::merge_skill_root_snapshots(snapshots)
|
||||
}
|
||||
|
||||
/// Reads one loaded skill through the filesystem that discovered it.
|
||||
pub async fn read_skill_text(&self, skill: &SkillMetadata) -> io::Result<String> {
|
||||
let fs = self
|
||||
.outcome
|
||||
.file_system_for_skill(skill)
|
||||
.unwrap_or_else(|| Arc::clone(&LOCAL_FS));
|
||||
let path = PathUri::from_abs_path(&skill.path_to_skills_md);
|
||||
|
||||
@@ -71,48 +71,7 @@ where
|
||||
.acquire()
|
||||
.await
|
||||
.unwrap_or_else(|_| unreachable!());
|
||||
let cache_key = match (
|
||||
root.plugin_identity.clone(),
|
||||
root.plugin_namespace.clone(),
|
||||
root.plugin_root.clone(),
|
||||
) {
|
||||
(Some(plugin_identity), Some(plugin_namespace), Some(plugin_root)) => {
|
||||
Some(PluginSkillRoot {
|
||||
path: root.path.clone(),
|
||||
plugin_identity,
|
||||
plugin_namespace,
|
||||
plugin_root,
|
||||
discovery_mode: root.discovery_mode,
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
let cached_snapshot = cache_key.as_ref().and_then(|cache_key| {
|
||||
let plugin_skill_snapshots = plugin_skill_snapshots?;
|
||||
plugin_skill_snapshots
|
||||
.snapshots_by_root
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.get(cache_key)
|
||||
.cloned()
|
||||
});
|
||||
|
||||
let snapshot = match cached_snapshot {
|
||||
Some(snapshot) => snapshot,
|
||||
None => {
|
||||
let snapshot = load_skill_root(root).await;
|
||||
if let Some(plugin_skill_snapshots) = plugin_skill_snapshots
|
||||
&& let Some(cache_key) = cache_key
|
||||
{
|
||||
plugin_skill_snapshots
|
||||
.snapshots_by_root
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.insert(cache_key, snapshot.clone());
|
||||
}
|
||||
snapshot
|
||||
}
|
||||
};
|
||||
let snapshot = load_skill_root_snapshot(root, plugin_skill_snapshots).await;
|
||||
(root_index, snapshot)
|
||||
})
|
||||
// Keep each load's scan queue bounded while avoiding head-of-line blocking.
|
||||
@@ -129,7 +88,59 @@ where
|
||||
merge_skill_root_snapshots(root_snapshots)
|
||||
}
|
||||
|
||||
fn merge_skill_root_snapshots(snapshots: Vec<SkillRootSnapshot>) -> SkillLoadOutcome {
|
||||
/// Loads one root while preserving the existing plugin-loader snapshot reuse.
|
||||
///
|
||||
/// This is a temporary migration boundary for callers that load non-plugin roots elsewhere but
|
||||
/// still need plugin roots to use the legacy preload cache.
|
||||
pub async fn load_skill_root_snapshot(
|
||||
root: SkillRoot,
|
||||
plugin_skill_snapshots: Option<&PluginSkillSnapshots>,
|
||||
) -> SkillRootSnapshot {
|
||||
let cache_key = match (
|
||||
root.plugin_identity.clone(),
|
||||
root.plugin_namespace.clone(),
|
||||
root.plugin_root.clone(),
|
||||
) {
|
||||
(Some(plugin_identity), Some(plugin_namespace), Some(plugin_root)) => {
|
||||
Some(PluginSkillRoot {
|
||||
path: root.path.clone(),
|
||||
plugin_identity,
|
||||
plugin_namespace,
|
||||
plugin_root,
|
||||
discovery_mode: root.discovery_mode,
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
let cached_snapshot = cache_key.as_ref().and_then(|cache_key| {
|
||||
let plugin_skill_snapshots = plugin_skill_snapshots?;
|
||||
plugin_skill_snapshots
|
||||
.snapshots_by_root
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.get(cache_key)
|
||||
.cloned()
|
||||
});
|
||||
|
||||
match cached_snapshot {
|
||||
Some(snapshot) => snapshot,
|
||||
None => {
|
||||
let snapshot = load_skill_root(root).await;
|
||||
if let Some(plugin_skill_snapshots) = plugin_skill_snapshots
|
||||
&& let Some(cache_key) = cache_key
|
||||
{
|
||||
plugin_skill_snapshots
|
||||
.snapshots_by_root
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.insert(cache_key, snapshot.clone());
|
||||
}
|
||||
snapshot
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn merge_skill_root_snapshots(snapshots: Vec<SkillRootSnapshot>) -> SkillLoadOutcome {
|
||||
fn scope_rank(scope: codex_protocol::protocol::SkillScope) -> u8 {
|
||||
use codex_protocol::protocol::SkillScope;
|
||||
|
||||
|
||||
Reference in new issue
Block a user