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:
felixxia-oai authored and copyberry committed 2026-08-05 21:17:54 +00:00
1 parent c38a60ded2
commit bd36d69aae
21 files changed
+515 -162

No files matched your search

+57 -8
View File
@@ -12,6 +12,8 @@ use codex_protocol::protocol::SkillScope;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_plugins::PluginIdentity;
use codex_utils_plugins::PluginSkillRoot;
use codex_utils_plugins::SkillDiscoveryMode;
use futures::StreamExt;
use tokio::sync::OnceCell;
use tokio::sync::Semaphore;
use tracing::info;
@@ -19,18 +21,22 @@ use tracing::instrument;
use tracing::warn;
use codex_config::SkillsConfig;
use codex_core_skills::HostSkillsSnapshot;
use codex_core_skills::PluginSkillSnapshots;
use codex_core_skills::SkillError;
use codex_core_skills::SkillLoadOutcome;
use codex_core_skills::config_rules::SkillConfigRules;
use codex_core_skills::config_rules::resolve_disabled_skill_paths;
use codex_core_skills::config_rules::skill_config_rules_from_stack;
use codex_core_skills::loader::MAX_CONCURRENT_ROOT_SCANS;
use codex_core_skills::loader::SkillRoot;
use codex_core_skills::loader::load_skills_from_roots;
use codex_core_skills::loader::SkillRootSnapshot;
use codex_core_skills::loader::load_skill_root_snapshot;
use codex_skills::install_system_skills;
use crate::HostSkillsSnapshot;
use crate::host_roots::resolve_skill_roots;
use crate::loader::HostSkillRoot;
use crate::loader::load_host_skill_root;
#[derive(Debug, Clone)]
pub struct HostSkillsLoadInput {
@@ -282,12 +288,55 @@ impl HostSkillsService {
roots: Vec<SkillRoot>,
skill_config_rules: &SkillConfigRules,
) -> SkillLoadOutcome {
let outcome = load_skills_from_roots(
roots,
input.plugin_skill_snapshots.as_ref(),
Arc::clone(&self.root_scan_slots),
)
.await;
let plugin_skill_snapshots = input.plugin_skill_snapshots.as_ref();
let mut indexed_snapshots = futures::stream::iter(roots.into_iter().enumerate())
.map(|(root_index, root)| async move {
let _root_scan_slot = self
.root_scan_slots
.acquire()
.await
.unwrap_or_else(|_| unreachable!());
let use_legacy_loader = root.plugin_identity.is_some()
|| root.plugin_namespace.is_some()
|| root.plugin_root.is_some()
|| root.discovery_mode != SkillDiscoveryMode::Recursive;
let snapshot = if use_legacy_loader {
load_skill_root_snapshot(root, plugin_skill_snapshots).await
} else {
let snapshot = load_host_skill_root(HostSkillRoot {
path: root.path,
scope: root.scope,
file_system: root.file_system,
plugin_root: root.plugin_root,
})
.await;
SkillRootSnapshot::new(
snapshot.root,
snapshot.skills,
snapshot.skill_discovery_path_by_path,
snapshot
.errors
.into_iter()
.map(|error| SkillError {
path: error.path,
message: error.message,
})
.collect(),
snapshot.file_system,
)
};
(root_index, snapshot)
})
.buffer_unordered(MAX_CONCURRENT_ROOT_SCANS)
.collect::<Vec<_>>()
.await;
indexed_snapshots.sort_unstable_by_key(|(root_index, _)| *root_index);
let outcome = SkillLoadOutcome::from_root_snapshots(
indexed_snapshots
.into_iter()
.map(|(_, snapshot)| snapshot)
.collect(),
);
let outcome = codex_core_skills::filter_skill_load_outcome_for_product(
outcome,
self.restriction_product,