mirror of
https://github.com/openai/codex.git
synced 2026-11-13 14:46:14 +00:00
Support Agent Plugins manifests (#35105)
## What changed - Recognize root `plugin.json` files using the Agent Plugins 1.0 schema and map their portable metadata, `skills/`, and `mcp.json` into Codex plugin manifests. - Apply Codex-specific apps, hooks, and interface settings from the inline `com.openai` extension, with `.codex-plugin/plugin.json` as a fallback overlay. - Preserve legacy manifest precedence when a root `plugin.json` is unrelated, and reject unsupported Agent Plugins schema versions. - Add a direct-child skill discovery mode that excludes nested skills and paths resolving outside the plugin root. ## Testing - Cover manifest metadata, validation, extension precedence, legacy fallback, and direct-child skill path boundaries. GitOrigin-RevId: eab24139f13a5cc5cb3ad3fb444d8e904511aca6
This commit is contained in:
1 parent
f61b51ddd9
commit
a28374e0db
16 files changed
+872
-43
No files matched your search
@@ -31,6 +31,7 @@ use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use codex_utils_absolute_path::AbsolutePathBufGuard;
|
||||
use codex_utils_path_uri::PathUri;
|
||||
use codex_utils_plugins::PluginSkillRoot;
|
||||
use codex_utils_plugins::SkillDiscoveryMode;
|
||||
use dirs::home_dir;
|
||||
use discovery::DirectorySymlinkPolicy;
|
||||
use discovery::DiscoveredSkill;
|
||||
@@ -197,6 +198,7 @@ pub struct SkillRoot {
|
||||
pub plugin_id: Option<String>,
|
||||
pub plugin_namespace: Option<String>,
|
||||
pub plugin_root: Option<AbsolutePathBuf>,
|
||||
pub discovery_mode: SkillDiscoveryMode,
|
||||
}
|
||||
|
||||
pub async fn load_skills_from_roots<I>(
|
||||
@@ -222,31 +224,15 @@ pub(crate) struct SkillRootSnapshot {
|
||||
}
|
||||
|
||||
pub(crate) async fn load_skill_root(root: SkillRoot) -> SkillRootSnapshot {
|
||||
let SkillRoot {
|
||||
path,
|
||||
scope,
|
||||
file_system,
|
||||
plugin_id,
|
||||
plugin_namespace,
|
||||
plugin_root,
|
||||
} = root;
|
||||
let root = canonicalize_for_skill_identity(file_system.as_ref(), &path).await;
|
||||
let canonical_root =
|
||||
canonicalize_for_skill_identity(root.file_system.as_ref(), &root.path).await;
|
||||
let mut outcome = SkillLoadOutcome::default();
|
||||
load_skills_under_root(
|
||||
file_system.as_ref(),
|
||||
&root,
|
||||
scope,
|
||||
plugin_id.as_deref(),
|
||||
plugin_namespace.as_deref(),
|
||||
plugin_root.as_ref(),
|
||||
&mut outcome,
|
||||
)
|
||||
.await;
|
||||
load_skills_under_root(&root, &canonical_root, &mut outcome).await;
|
||||
SkillRootSnapshot {
|
||||
root,
|
||||
root: canonical_root,
|
||||
skills: outcome.skills,
|
||||
errors: outcome.errors,
|
||||
file_system,
|
||||
file_system: root.file_system,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -286,6 +272,7 @@ async fn skill_roots_with_home_dir(
|
||||
plugin_id: Some(root.plugin_id),
|
||||
plugin_namespace: Some(root.plugin_namespace),
|
||||
plugin_root: Some(root.plugin_root),
|
||||
discovery_mode: root.discovery_mode,
|
||||
}));
|
||||
roots.extend(extra_skill_roots.into_iter().map(|path| SkillRoot {
|
||||
path,
|
||||
@@ -294,6 +281,7 @@ async fn skill_roots_with_home_dir(
|
||||
plugin_id: None,
|
||||
plugin_namespace: None,
|
||||
plugin_root: None,
|
||||
discovery_mode: SkillDiscoveryMode::Recursive,
|
||||
}));
|
||||
roots.extend(repo_agents_skill_roots(fs, config_layer_stack, cwd).await);
|
||||
dedupe_skill_roots_by_path(&mut roots);
|
||||
@@ -325,6 +313,7 @@ fn skill_roots_from_layer_stack_inner(
|
||||
plugin_id: None,
|
||||
plugin_namespace: None,
|
||||
plugin_root: None,
|
||||
discovery_mode: SkillDiscoveryMode::Recursive,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -338,6 +327,7 @@ fn skill_roots_from_layer_stack_inner(
|
||||
plugin_id: None,
|
||||
plugin_namespace: None,
|
||||
plugin_root: None,
|
||||
discovery_mode: SkillDiscoveryMode::Recursive,
|
||||
});
|
||||
|
||||
// `$HOME/.agents/skills` (user-installed skills).
|
||||
@@ -349,6 +339,7 @@ fn skill_roots_from_layer_stack_inner(
|
||||
plugin_id: None,
|
||||
plugin_namespace: None,
|
||||
plugin_root: None,
|
||||
discovery_mode: SkillDiscoveryMode::Recursive,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -361,6 +352,7 @@ fn skill_roots_from_layer_stack_inner(
|
||||
plugin_id: None,
|
||||
plugin_namespace: None,
|
||||
plugin_root: None,
|
||||
discovery_mode: SkillDiscoveryMode::Recursive,
|
||||
});
|
||||
}
|
||||
ConfigLayerSource::System { .. } => {
|
||||
@@ -373,6 +365,7 @@ fn skill_roots_from_layer_stack_inner(
|
||||
plugin_id: None,
|
||||
plugin_namespace: None,
|
||||
plugin_root: None,
|
||||
discovery_mode: SkillDiscoveryMode::Recursive,
|
||||
});
|
||||
}
|
||||
ConfigLayerSource::Mdm { .. }
|
||||
@@ -418,6 +411,7 @@ async fn repo_agents_skill_roots(
|
||||
plugin_id: None,
|
||||
plugin_namespace: None,
|
||||
plugin_root: None,
|
||||
discovery_mode: SkillDiscoveryMode::Recursive,
|
||||
}),
|
||||
Ok(_) => {}
|
||||
Err(err) if err.kind() == io::ErrorKind::NotFound => {}
|
||||
@@ -531,19 +525,16 @@ async fn canonicalize_for_skill_identity(
|
||||
}
|
||||
|
||||
async fn load_skills_under_root(
|
||||
fs: &dyn ExecutorFileSystem,
|
||||
skill_root: &SkillRoot,
|
||||
root: &AbsolutePathBuf,
|
||||
scope: SkillScope,
|
||||
plugin_id: Option<&str>,
|
||||
plugin_namespace: Option<&str>,
|
||||
plugin_root: Option<&AbsolutePathBuf>,
|
||||
outcome: &mut SkillLoadOutcome,
|
||||
) {
|
||||
let plugin_root = match plugin_root {
|
||||
let fs = skill_root.file_system.as_ref();
|
||||
let plugin_root = match skill_root.plugin_root.as_ref() {
|
||||
Some(plugin_root) => Some(canonicalize_for_skill_identity(fs, plugin_root).await),
|
||||
None => None,
|
||||
};
|
||||
let directory_symlinks = match scope {
|
||||
let directory_symlinks = match skill_root.scope {
|
||||
SkillScope::User | SkillScope::Repo | SkillScope::Admin => DirectorySymlinkPolicy::Follow,
|
||||
SkillScope::System => DirectorySymlinkPolicy::Ignore,
|
||||
};
|
||||
@@ -560,6 +551,7 @@ async fn load_skills_under_root(
|
||||
SkillDiscoveryOptions {
|
||||
directory_symlinks,
|
||||
hidden_directories: HiddenDirectoryPolicy::Skip,
|
||||
mode: skill_root.discovery_mode,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
@@ -571,12 +563,20 @@ async fn load_skills_under_root(
|
||||
return;
|
||||
}
|
||||
let root_uri = PathUri::from_abs_path(root);
|
||||
let resolved_plugin_root = plugin_root.as_ref();
|
||||
let resolved_skills = futures::stream::iter(skills)
|
||||
.map(|skill| async move {
|
||||
let path_uri = fs
|
||||
.canonicalize(&skill.path, /*sandbox*/ None)
|
||||
.await
|
||||
.unwrap_or_else(|_| skill.path.clone());
|
||||
let path_uri = match fs.canonicalize(&skill.path, /*sandbox*/ None).await {
|
||||
Ok(path) => path,
|
||||
Err(err) if skill_root.discovery_mode == SkillDiscoveryMode::DirectChildren => {
|
||||
error!(
|
||||
"failed to resolve Agent Plugin skill path {}: {err}",
|
||||
skill.path
|
||||
);
|
||||
return None;
|
||||
}
|
||||
Err(_) => skill.path.clone(),
|
||||
};
|
||||
let path = match path_uri.to_abs_path() {
|
||||
Ok(path) => path,
|
||||
Err(err) => {
|
||||
@@ -584,6 +584,37 @@ async fn load_skills_under_root(
|
||||
return None;
|
||||
}
|
||||
};
|
||||
if skill_root.discovery_mode == SkillDiscoveryMode::DirectChildren {
|
||||
let Some(plugin_root) = resolved_plugin_root else {
|
||||
error!("Agent Plugin skill root is missing its plugin root");
|
||||
return None;
|
||||
};
|
||||
if !path.as_path().starts_with(plugin_root.as_path()) {
|
||||
error!(
|
||||
"Agent Plugin skill path {} resolves outside plugin root {}",
|
||||
path.display(),
|
||||
plugin_root.display()
|
||||
);
|
||||
return None;
|
||||
}
|
||||
match fs.get_metadata(&path_uri, /*sandbox*/ None).await {
|
||||
Ok(metadata) if metadata.is_file => {}
|
||||
Ok(_) => {
|
||||
error!(
|
||||
"Agent Plugin skill path {} is not a regular file",
|
||||
path.display()
|
||||
);
|
||||
return None;
|
||||
}
|
||||
Err(err) => {
|
||||
error!(
|
||||
"failed to inspect Agent Plugin skill path {}: {err}",
|
||||
path.display()
|
||||
);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(ResolvedDiscoveredSkill {
|
||||
skill,
|
||||
path,
|
||||
@@ -604,7 +635,7 @@ async fn load_skills_under_root(
|
||||
.map(|skill| skill.path_uri.clone())
|
||||
.collect::<Vec<_>>();
|
||||
let namespace_resolver = async {
|
||||
match plugin_namespace {
|
||||
match skill_root.plugin_namespace.as_deref() {
|
||||
Some(namespace) => SkillNamespaceResolver::with_provided_namespace(namespace),
|
||||
None => {
|
||||
SkillNamespaceResolver::discover(
|
||||
@@ -627,8 +658,8 @@ async fn load_skills_under_root(
|
||||
&skill.skill,
|
||||
&skill.path,
|
||||
&skill.path_uri,
|
||||
scope,
|
||||
plugin_id,
|
||||
skill_root.scope,
|
||||
skill_root.plugin_id.as_deref(),
|
||||
plugin_root,
|
||||
)
|
||||
.await
|
||||
@@ -651,7 +682,7 @@ async fn load_skills_under_root(
|
||||
});
|
||||
match result {
|
||||
Ok(skill) => outcome.skills.push(skill),
|
||||
Err(err) if scope != SkillScope::System => {
|
||||
Err(err) if skill_root.scope != SkillScope::System => {
|
||||
outcome.errors.push(SkillError { path, message: err })
|
||||
}
|
||||
Err(_) => {}
|
||||
|
||||
@@ -6,6 +6,7 @@ use codex_exec_server::WalkEntryKind;
|
||||
use codex_exec_server::WalkOptions;
|
||||
use codex_utils_path_uri::PathUri;
|
||||
use codex_utils_plugins::DISCOVERABLE_PLUGIN_MANIFEST_PATHS;
|
||||
use codex_utils_plugins::SkillDiscoveryMode;
|
||||
|
||||
use super::MAX_SCAN_DEPTH;
|
||||
use super::MAX_SKILLS_DIRS_PER_ROOT;
|
||||
@@ -29,6 +30,7 @@ pub(super) enum HiddenDirectoryPolicy {
|
||||
pub(super) struct SkillDiscoveryOptions {
|
||||
pub directory_symlinks: DirectorySymlinkPolicy,
|
||||
pub hidden_directories: HiddenDirectoryPolicy,
|
||||
pub mode: SkillDiscoveryMode,
|
||||
}
|
||||
|
||||
pub(super) struct SkillDiscovery {
|
||||
@@ -64,7 +66,10 @@ pub(super) async fn discover_skills(
|
||||
.walk(
|
||||
root,
|
||||
WalkOptions {
|
||||
max_depth: MAX_SCAN_DEPTH,
|
||||
max_depth: match options.mode {
|
||||
SkillDiscoveryMode::Recursive => MAX_SCAN_DEPTH,
|
||||
SkillDiscoveryMode::DirectChildren => 2,
|
||||
},
|
||||
max_directories: MAX_SKILLS_DIRS_PER_ROOT,
|
||||
max_entries: MAX_SKILLS_ENTRIES_PER_ROOT,
|
||||
follow_directory_symlinks: matches!(
|
||||
@@ -137,7 +142,10 @@ pub(super) async fn discover_skills(
|
||||
}
|
||||
WalkEntryKind::File => {
|
||||
file_paths.insert(entry.path.clone());
|
||||
if entry.path.basename().as_deref() == Some(SKILLS_FILENAME) {
|
||||
if entry.path.basename().as_deref() == Some(SKILLS_FILENAME)
|
||||
&& (options.mode == SkillDiscoveryMode::Recursive
|
||||
|| is_direct_child_skill_path(&entry.path, root))
|
||||
{
|
||||
skill_files.push(entry.path);
|
||||
}
|
||||
}
|
||||
@@ -164,6 +172,10 @@ pub(super) async fn discover_skills(
|
||||
}
|
||||
}
|
||||
|
||||
fn is_direct_child_skill_path(path: &PathUri, root: &PathUri) -> bool {
|
||||
path.parent().and_then(|parent| parent.parent()).as_ref() == Some(root)
|
||||
}
|
||||
|
||||
fn has_hidden_ancestor_below_root(path: &PathUri, root: &PathUri) -> bool {
|
||||
let mut ancestor = path.parent();
|
||||
while let Some(current) = ancestor {
|
||||
|
||||
@@ -121,6 +121,7 @@ pub async fn load_environment_skills_from_root(
|
||||
SkillDiscoveryOptions {
|
||||
directory_symlinks: DirectorySymlinkPolicy::Follow,
|
||||
hidden_directories: HiddenDirectoryPolicy::Include,
|
||||
mode: codex_utils_plugins::SkillDiscoveryMode::Recursive,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -574,6 +574,7 @@ async fn load_user_skills_root(root: &Path) -> SkillLoadOutcome {
|
||||
plugin_id: None,
|
||||
plugin_namespace: None,
|
||||
plugin_root: None,
|
||||
discovery_mode: SkillDiscoveryMode::Recursive,
|
||||
}],
|
||||
/*plugin_skill_snapshots*/ None,
|
||||
Arc::new(Semaphore::new(MAX_CONCURRENT_ROOT_SCANS)),
|
||||
@@ -1061,6 +1062,7 @@ interface:
|
||||
plugin_id: Some("twilio-developer-kit@test".to_string()),
|
||||
plugin_namespace: None,
|
||||
plugin_root: Some(plugin_root_abs.clone()),
|
||||
discovery_mode: SkillDiscoveryMode::Recursive,
|
||||
}],
|
||||
/*plugin_skill_snapshots*/ None,
|
||||
Arc::new(Semaphore::new(MAX_CONCURRENT_ROOT_SCANS)),
|
||||
@@ -1123,6 +1125,7 @@ interface:
|
||||
plugin_id: Some("twilio-developer-kit@test".to_string()),
|
||||
plugin_namespace: None,
|
||||
plugin_root: Some(plugin_root.abs()),
|
||||
discovery_mode: SkillDiscoveryMode::Recursive,
|
||||
}],
|
||||
/*plugin_skill_snapshots*/ None,
|
||||
Arc::new(Semaphore::new(MAX_CONCURRENT_ROOT_SCANS)),
|
||||
@@ -1300,6 +1303,7 @@ async fn loads_skills_via_symlinked_subdir_for_admin_scope() {
|
||||
plugin_id: None,
|
||||
plugin_namespace: None,
|
||||
plugin_root: None,
|
||||
discovery_mode: SkillDiscoveryMode::Recursive,
|
||||
}],
|
||||
/*plugin_skill_snapshots*/ None,
|
||||
Arc::new(Semaphore::new(MAX_CONCURRENT_ROOT_SCANS)),
|
||||
@@ -1387,6 +1391,7 @@ async fn system_scope_ignores_symlinked_subdir() {
|
||||
plugin_id: None,
|
||||
plugin_namespace: None,
|
||||
plugin_root: None,
|
||||
discovery_mode: SkillDiscoveryMode::Recursive,
|
||||
}],
|
||||
/*plugin_skill_snapshots*/ None,
|
||||
Arc::new(Semaphore::new(MAX_CONCURRENT_ROOT_SCANS)),
|
||||
@@ -1426,6 +1431,7 @@ async fn respects_max_scan_depth_for_user_scope() {
|
||||
plugin_id: None,
|
||||
plugin_namespace: None,
|
||||
plugin_root: None,
|
||||
discovery_mode: SkillDiscoveryMode::Recursive,
|
||||
}],
|
||||
/*plugin_skill_snapshots*/ None,
|
||||
Arc::new(Semaphore::new(MAX_CONCURRENT_ROOT_SCANS)),
|
||||
@@ -1538,6 +1544,7 @@ async fn namespaces_plugin_skills_using_provided_namespace() {
|
||||
plugin_id: Some("sample@test".to_string()),
|
||||
plugin_namespace: Some("sample".to_string()),
|
||||
plugin_root: Some(plugin_root.abs()),
|
||||
discovery_mode: SkillDiscoveryMode::Recursive,
|
||||
}],
|
||||
/*plugin_skill_snapshots*/ None,
|
||||
Arc::new(Semaphore::new(MAX_CONCURRENT_ROOT_SCANS)),
|
||||
@@ -1786,6 +1793,7 @@ async fn plugin_skill_name_length_limit_allows_max_qualified_name() {
|
||||
plugin_id: Some("sample@test".to_string()),
|
||||
plugin_namespace: Some(plugin_name.clone()),
|
||||
plugin_root: Some(plugin_root.abs()),
|
||||
discovery_mode: SkillDiscoveryMode::Recursive,
|
||||
}],
|
||||
/*plugin_skill_snapshots*/ None,
|
||||
Arc::new(Semaphore::new(MAX_CONCURRENT_ROOT_SCANS)),
|
||||
@@ -1836,6 +1844,7 @@ async fn plugin_skill_name_length_limit_rejects_overlong_qualified_name() {
|
||||
plugin_id: Some("sample@test".to_string()),
|
||||
plugin_namespace: Some(plugin_name.clone()),
|
||||
plugin_root: Some(plugin_root.abs()),
|
||||
discovery_mode: SkillDiscoveryMode::Recursive,
|
||||
}],
|
||||
/*plugin_skill_snapshots*/ None,
|
||||
Arc::new(Semaphore::new(MAX_CONCURRENT_ROOT_SCANS)),
|
||||
@@ -1851,6 +1860,76 @@ async fn plugin_skill_name_length_limit_rejects_overlong_qualified_name() {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn direct_child_discovery_ignores_nested_skills() {
|
||||
let root = tempfile::tempdir().expect("tempdir");
|
||||
let plugin_root = root.path().join("plugin");
|
||||
let skills_root = plugin_root.join("skills");
|
||||
let direct = write_skill_at(&skills_root, "direct", "direct", "direct skill");
|
||||
write_skill_at(&skills_root, "nested/too-deep", "too-deep", "nested skill");
|
||||
|
||||
let outcome = load_skills_from_roots(
|
||||
[SkillRoot {
|
||||
path: skills_root.abs(),
|
||||
scope: SkillScope::User,
|
||||
file_system: Arc::clone(&LOCAL_FS),
|
||||
plugin_id: Some("plugin@test".to_string()),
|
||||
plugin_namespace: Some("plugin".to_string()),
|
||||
plugin_root: Some(plugin_root.abs()),
|
||||
discovery_mode: SkillDiscoveryMode::DirectChildren,
|
||||
}],
|
||||
/*plugin_skill_snapshots*/ None,
|
||||
Arc::new(Semaphore::new(MAX_CONCURRENT_ROOT_SCANS)),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert!(outcome.errors.is_empty());
|
||||
assert_eq!(
|
||||
outcome.skills,
|
||||
vec![SkillMetadata {
|
||||
name: "plugin:direct".to_string(),
|
||||
description: "direct skill".to_string(),
|
||||
short_description: None,
|
||||
interface: None,
|
||||
dependencies: None,
|
||||
policy: None,
|
||||
path_to_skills_md: normalized(&direct),
|
||||
scope: SkillScope::User,
|
||||
plugin_id: Some("plugin@test".to_string()),
|
||||
}]
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[tokio::test]
|
||||
async fn direct_child_discovery_skips_skills_resolving_outside_plugin_root() {
|
||||
let root = tempfile::tempdir().expect("tempdir");
|
||||
let plugin_root = root.path().join("plugin");
|
||||
let skills_root = plugin_root.join("skills");
|
||||
let outside_root = root.path().join("outside");
|
||||
write_skill_at(&outside_root, "escaped", "escaped", "escaped skill");
|
||||
fs::create_dir_all(&skills_root).expect("create skills root");
|
||||
std::os::unix::fs::symlink(outside_root.join("escaped"), skills_root.join("escaped"))
|
||||
.expect("create skill symlink");
|
||||
|
||||
let outcome = load_skills_from_roots(
|
||||
[SkillRoot {
|
||||
path: skills_root.abs(),
|
||||
scope: SkillScope::User,
|
||||
file_system: Arc::clone(&LOCAL_FS),
|
||||
plugin_id: Some("plugin@test".to_string()),
|
||||
plugin_namespace: Some("plugin".to_string()),
|
||||
plugin_root: Some(plugin_root.abs()),
|
||||
discovery_mode: SkillDiscoveryMode::DirectChildren,
|
||||
}],
|
||||
/*plugin_skill_snapshots*/ None,
|
||||
Arc::new(Semaphore::new(MAX_CONCURRENT_ROOT_SCANS)),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert!(outcome.skills.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn loads_short_description_from_metadata() {
|
||||
let codex_home = tempfile::tempdir().expect("tempdir");
|
||||
@@ -2385,6 +2464,7 @@ async fn merges_root_results_in_input_order_when_scans_finish_out_of_order() {
|
||||
plugin_id: None,
|
||||
plugin_namespace: Some("test".to_string()),
|
||||
plugin_root: None,
|
||||
discovery_mode: SkillDiscoveryMode::Recursive,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let root_scan_slots = Semaphore::new(MAX_CONCURRENT_ROOT_SCANS);
|
||||
@@ -2450,6 +2530,7 @@ async fn skill_root_scans_wait_for_shared_capacity() {
|
||||
plugin_id: None,
|
||||
plugin_namespace: Some("test".to_string()),
|
||||
plugin_root: None,
|
||||
discovery_mode: SkillDiscoveryMode::Recursive,
|
||||
}],
|
||||
/*plugin_skill_snapshots*/ None,
|
||||
&root_scan_slots,
|
||||
@@ -2518,6 +2599,7 @@ async fn deduplicates_by_path_preferring_first_root() {
|
||||
plugin_id: None,
|
||||
plugin_namespace: None,
|
||||
plugin_root: None,
|
||||
discovery_mode: SkillDiscoveryMode::Recursive,
|
||||
},
|
||||
SkillRoot {
|
||||
path: root.path().abs(),
|
||||
@@ -2526,6 +2608,7 @@ async fn deduplicates_by_path_preferring_first_root() {
|
||||
plugin_id: None,
|
||||
plugin_namespace: None,
|
||||
plugin_root: None,
|
||||
discovery_mode: SkillDiscoveryMode::Recursive,
|
||||
},
|
||||
],
|
||||
/*plugin_skill_snapshots*/ None,
|
||||
|
||||
@@ -66,6 +66,7 @@ where
|
||||
plugin_id,
|
||||
plugin_namespace,
|
||||
plugin_root,
|
||||
discovery_mode: root.discovery_mode,
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
|
||||
@@ -12,6 +12,7 @@ use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use codex_utils_absolute_path::test_support::PathBufExt;
|
||||
use codex_utils_absolute_path::test_support::PathExt;
|
||||
use codex_utils_plugins::PluginSkillRoot;
|
||||
use codex_utils_plugins::SkillDiscoveryMode;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::collections::HashSet;
|
||||
use std::fs;
|
||||
@@ -72,6 +73,7 @@ fn plugin_skill_root_for_skill_path(
|
||||
plugin_id: plugin_id.to_string(),
|
||||
plugin_namespace: plugin_namespace.to_string(),
|
||||
plugin_root: plugin_root.abs(),
|
||||
discovery_mode: SkillDiscoveryMode::Recursive,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -472,6 +472,7 @@ async fn host_loading_reuses_walk_inventory_for_symlinked_skill_pack() {
|
||||
plugin_id: None,
|
||||
plugin_namespace: None,
|
||||
plugin_root: None,
|
||||
discovery_mode: codex_utils_plugins::SkillDiscoveryMode::Recursive,
|
||||
}],
|
||||
/*plugin_skill_snapshots*/ None,
|
||||
Arc::new(tokio::sync::Semaphore::new(MAX_CONCURRENT_ROOT_SCANS)),
|
||||
|
||||
Reference in new issue
Block a user