mirror of
https://github.com/openai/codex.git
synced 2026-10-21 17:48:44 +00:00
Simplify plugin skill root snapshot cache
This commit is contained in:
@@ -19,8 +19,8 @@ use codex_config::HooksFile;
|
||||
use codex_config::types::McpServerConfig;
|
||||
use codex_config::types::PluginConfig;
|
||||
use codex_config::types::PluginMcpServerConfig;
|
||||
use codex_core_skills::PluginSkillRootCache;
|
||||
use codex_core_skills::SkillMetadata;
|
||||
use codex_core_skills::SkillRootLoader;
|
||||
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;
|
||||
@@ -72,7 +72,7 @@ enum PluginLoadScope<'a> {
|
||||
AllCapabilities {
|
||||
restriction_product: Option<Product>,
|
||||
skill_config_rules: &'a SkillConfigRules,
|
||||
skill_root_loader: &'a SkillRootLoader,
|
||||
plugin_skill_root_cache: &'a PluginSkillRootCache,
|
||||
},
|
||||
HooksOnly,
|
||||
}
|
||||
@@ -114,7 +114,7 @@ pub(crate) async fn load_plugins_from_layer_stack(
|
||||
config_layer_stack: &ConfigLayerStack,
|
||||
extra_plugins: HashMap<String, PluginConfig>,
|
||||
store: &PluginStore,
|
||||
skill_root_loader: &SkillRootLoader,
|
||||
plugin_skill_root_cache: &PluginSkillRootCache,
|
||||
restriction_product: Option<Product>,
|
||||
prefer_remote_curated_conflicts: bool,
|
||||
) -> Vec<LoadedPlugin<McpServerConfig>> {
|
||||
@@ -127,7 +127,7 @@ pub(crate) async fn load_plugins_from_layer_stack(
|
||||
PluginLoadScope::AllCapabilities {
|
||||
restriction_product,
|
||||
skill_config_rules: &skill_config_rules,
|
||||
skill_root_loader,
|
||||
plugin_skill_root_cache,
|
||||
},
|
||||
)
|
||||
.await
|
||||
@@ -716,7 +716,7 @@ async fn load_plugin(
|
||||
PluginLoadScope::AllCapabilities {
|
||||
restriction_product,
|
||||
skill_config_rules,
|
||||
skill_root_loader,
|
||||
plugin_skill_root_cache,
|
||||
} => {
|
||||
loaded_plugin.manifest_name = Some(manifest.display_name().to_string());
|
||||
loaded_plugin.manifest_description = manifest.description.clone();
|
||||
@@ -727,7 +727,7 @@ async fn load_plugin(
|
||||
&manifest,
|
||||
*restriction_product,
|
||||
skill_config_rules,
|
||||
skill_root_loader,
|
||||
plugin_skill_root_cache,
|
||||
)
|
||||
.await;
|
||||
let has_enabled_skills = resolved_skills.has_enabled_skills();
|
||||
@@ -796,7 +796,7 @@ pub async fn load_plugin_skills(
|
||||
manifest: &PluginManifest,
|
||||
restriction_product: Option<Product>,
|
||||
skill_config_rules: &SkillConfigRules,
|
||||
skill_root_loader: &SkillRootLoader,
|
||||
plugin_skill_root_cache: &PluginSkillRootCache,
|
||||
) -> ResolvedPluginSkills {
|
||||
let roots = plugin_skill_roots(plugin_root, &manifest.paths)
|
||||
.into_iter()
|
||||
@@ -809,7 +809,7 @@ pub async fn load_plugin_skills(
|
||||
plugin_root: Some(plugin_root.clone()),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let outcome = skill_root_loader.load_skills_from_roots(roots).await;
|
||||
let outcome = plugin_skill_root_cache.load_skills_from_roots(roots).await;
|
||||
let had_errors = !outcome.errors.is_empty();
|
||||
let skills = outcome
|
||||
.skills
|
||||
|
||||
@@ -5,7 +5,7 @@ use codex_config::ConfigLayerEntry;
|
||||
use codex_config::ConfigLayerSource;
|
||||
use codex_config::ConfigRequirements;
|
||||
use codex_config::ConfigRequirementsToml;
|
||||
use codex_core_skills::SkillRootLoader;
|
||||
use codex_core_skills::PluginSkillRootCache;
|
||||
use codex_plugin::PluginId;
|
||||
use pretty_assertions::assert_eq;
|
||||
use tempfile::TempDir;
|
||||
@@ -156,13 +156,13 @@ enabled = true
|
||||
)
|
||||
.expect("valid config layer stack");
|
||||
let store = PluginStore::new(temp_dir.path().to_path_buf());
|
||||
let skill_root_loader = SkillRootLoader::default();
|
||||
let plugin_skill_root_cache = PluginSkillRootCache::default();
|
||||
|
||||
let full = load_plugins_from_layer_stack(
|
||||
&stack,
|
||||
HashMap::new(),
|
||||
&store,
|
||||
&skill_root_loader,
|
||||
&plugin_skill_root_cache,
|
||||
Some(Product::Codex),
|
||||
/*prefer_remote_curated_conflicts*/ false,
|
||||
)
|
||||
|
||||
@@ -59,8 +59,8 @@ use codex_config::set_user_plugin_enabled;
|
||||
use codex_config::types::PluginConfig;
|
||||
use codex_config::types::ToolSuggestDisabledTool;
|
||||
use codex_config::types::ToolSuggestDiscoverableType;
|
||||
use codex_core_skills::PluginSkillRootCache;
|
||||
use codex_core_skills::SkillMetadata;
|
||||
use codex_core_skills::SkillRootLoader;
|
||||
use codex_core_skills::config_rules::SkillConfigRules;
|
||||
use codex_core_skills::config_rules::skill_config_rules_from_stack;
|
||||
use codex_hooks::plugin_hook_declarations;
|
||||
@@ -355,7 +355,7 @@ pub struct PluginsManager {
|
||||
// Keep the cache auth-independent so auth changes only need to resolve capabilities again.
|
||||
loaded_plugins_cache: RwLock<LoadedPluginsCache>,
|
||||
loaded_plugins_load_semaphore: Semaphore,
|
||||
skill_root_loader: Arc<SkillRootLoader>,
|
||||
plugin_skill_root_cache: Arc<PluginSkillRootCache>,
|
||||
remote_installed_plugins_cache: RwLock<Option<Vec<RemoteInstalledPlugin>>>,
|
||||
remote_installed_plugins_cache_refresh_state: RwLock<RemoteInstalledPluginsCacheRefreshState>,
|
||||
global_remote_catalog_cache_refresh_state: RwLock<GlobalRemoteCatalogCacheRefreshState>,
|
||||
@@ -412,7 +412,7 @@ impl PluginsManager {
|
||||
non_curated_cache_refresh_state: RwLock::new(NonCuratedCacheRefreshState::default()),
|
||||
loaded_plugins_cache: RwLock::new(LoadedPluginsCache::default()),
|
||||
loaded_plugins_load_semaphore: Semaphore::new(/*permits*/ 1),
|
||||
skill_root_loader: Arc::new(SkillRootLoader::default()),
|
||||
plugin_skill_root_cache: Arc::new(PluginSkillRootCache::default()),
|
||||
remote_installed_plugins_cache: RwLock::new(None),
|
||||
remote_installed_plugins_cache_refresh_state: RwLock::new(
|
||||
RemoteInstalledPluginsCacheRefreshState::default(),
|
||||
@@ -438,9 +438,9 @@ impl PluginsManager {
|
||||
true
|
||||
}
|
||||
|
||||
/// Returns the loader whose plugin-root snapshots are populated while loading plugins.
|
||||
pub fn skill_root_loader(&self) -> Arc<SkillRootLoader> {
|
||||
Arc::clone(&self.skill_root_loader)
|
||||
/// Returns the shared cache populated while loading plugin skill roots.
|
||||
pub fn plugin_skill_root_cache(&self) -> Arc<PluginSkillRootCache> {
|
||||
Arc::clone(&self.plugin_skill_root_cache)
|
||||
}
|
||||
|
||||
pub fn auth_mode(&self) -> Option<AuthMode> {
|
||||
@@ -513,7 +513,7 @@ impl PluginsManager {
|
||||
&config.config_layer_stack,
|
||||
self.remote_installed_plugin_configs(),
|
||||
&self.store,
|
||||
self.skill_root_loader.as_ref(),
|
||||
self.plugin_skill_root_cache.as_ref(),
|
||||
self.restriction_product,
|
||||
config.remote_plugin_enabled,
|
||||
)
|
||||
@@ -560,7 +560,7 @@ impl PluginsManager {
|
||||
}
|
||||
|
||||
fn clear_loaded_plugins_cache(&self) {
|
||||
self.skill_root_loader.clear_cache();
|
||||
self.plugin_skill_root_cache.clear_cache();
|
||||
let mut cache = match self.loaded_plugins_cache.write() {
|
||||
Ok(cache) => cache,
|
||||
Err(err) => err.into_inner(),
|
||||
@@ -582,7 +582,7 @@ impl PluginsManager {
|
||||
config_layer_stack,
|
||||
self.remote_installed_plugin_configs(),
|
||||
&self.store,
|
||||
self.skill_root_loader.as_ref(),
|
||||
self.plugin_skill_root_cache.as_ref(),
|
||||
self.restriction_product,
|
||||
config.remote_plugin_enabled,
|
||||
)
|
||||
@@ -1607,7 +1607,7 @@ impl PluginsManager {
|
||||
&codex_core_skills::config_rules::skill_config_rules_from_stack(
|
||||
&config.config_layer_stack,
|
||||
),
|
||||
self.skill_root_loader.as_ref(),
|
||||
self.plugin_skill_root_cache.as_ref(),
|
||||
)
|
||||
.await;
|
||||
let plugin_data_root = self.store.plugin_data_root(&plugin_id);
|
||||
|
||||
@@ -34,7 +34,7 @@ use codex_config::McpServerConfig;
|
||||
use codex_config::McpServerOAuthConfig;
|
||||
use codex_config::McpServerToolConfig;
|
||||
use codex_config::types::McpServerTransportConfig;
|
||||
use codex_core_skills::SkillRootLoader;
|
||||
use codex_core_skills::PluginSkillRootCache;
|
||||
use codex_core_skills::config_rules::SkillConfigRules;
|
||||
use codex_login::CodexAuth;
|
||||
use codex_plugin::AppDeclaration;
|
||||
@@ -1467,7 +1467,7 @@ async fn load_plugin_skills_dedupes_overlapping_manifest_roots() {
|
||||
interface: None,
|
||||
};
|
||||
let plugin_id = PluginId::parse("sample@test").expect("plugin id should parse");
|
||||
let skill_root_loader = SkillRootLoader::default();
|
||||
let plugin_skill_root_cache = PluginSkillRootCache::default();
|
||||
|
||||
let resolved = load_plugin_skills(
|
||||
&plugin_root,
|
||||
@@ -1475,7 +1475,7 @@ async fn load_plugin_skills_dedupes_overlapping_manifest_roots() {
|
||||
&manifest,
|
||||
/*restriction_product*/ None,
|
||||
&SkillConfigRules::default(),
|
||||
&skill_root_loader,
|
||||
&plugin_skill_root_cache,
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -4823,12 +4823,12 @@ async fn load_plugins_ignores_project_config_files() {
|
||||
)
|
||||
.expect("config layer stack should build");
|
||||
|
||||
let skill_root_loader = SkillRootLoader::default();
|
||||
let plugin_skill_root_cache = PluginSkillRootCache::default();
|
||||
let plugins = load_plugins_from_layer_stack(
|
||||
&stack,
|
||||
std::collections::HashMap::new(),
|
||||
&PluginStore::new(codex_home.path().to_path_buf()),
|
||||
&skill_root_loader,
|
||||
&plugin_skill_root_cache,
|
||||
Some(Product::Codex),
|
||||
/*prefer_remote_curated_conflicts*/ false,
|
||||
)
|
||||
|
||||
@@ -4,9 +4,9 @@ pub(crate) mod invocation_utils;
|
||||
pub mod loader;
|
||||
mod mention_counts;
|
||||
pub mod model;
|
||||
mod plugin_skill_root_cache;
|
||||
pub mod remote;
|
||||
pub mod render;
|
||||
mod root_loader;
|
||||
pub mod service;
|
||||
mod skill_instructions;
|
||||
pub mod system;
|
||||
@@ -20,6 +20,7 @@ pub use model::SkillLoadOutcome;
|
||||
pub use model::SkillMetadata;
|
||||
pub use model::SkillPolicy;
|
||||
pub use model::filter_skill_load_outcome_for_product;
|
||||
pub use plugin_skill_root_cache::PluginSkillRootCache;
|
||||
pub use render::AvailableSkills;
|
||||
pub use render::SKILLS_HOW_TO_USE_WITH_ABSOLUTE_PATHS;
|
||||
pub use render::SKILLS_HOW_TO_USE_WITH_ALIASES;
|
||||
@@ -30,7 +31,6 @@ pub use render::SkillRenderReport;
|
||||
pub use render::build_available_skills;
|
||||
pub use render::default_skill_metadata_budget;
|
||||
pub use render::render_available_skills_body;
|
||||
pub use root_loader::SkillRootLoader;
|
||||
pub use service::SkillsLoadInput;
|
||||
pub use service::SkillsService;
|
||||
pub use skill_instructions::SkillInstructions;
|
||||
|
||||
@@ -5,7 +5,7 @@ use crate::model::SkillLoadOutcome;
|
||||
use crate::model::SkillMetadata;
|
||||
use crate::model::SkillPolicy;
|
||||
use crate::model::SkillToolDependency;
|
||||
use crate::root_loader::SkillRootLoader;
|
||||
use crate::plugin_skill_root_cache::PluginSkillRootCache;
|
||||
use crate::system::system_cache_root_dir;
|
||||
use codex_app_server_protocol::ConfigLayerSource;
|
||||
use codex_config::ConfigLayerStack;
|
||||
@@ -164,7 +164,7 @@ pub async fn load_skills_from_roots<I>(roots: I) -> SkillLoadOutcome
|
||||
where
|
||||
I: IntoIterator<Item = SkillRoot>,
|
||||
{
|
||||
SkillRootLoader::default()
|
||||
PluginSkillRootCache::default()
|
||||
.load_skills_from_roots(roots)
|
||||
.await
|
||||
}
|
||||
|
||||
+28
-88
@@ -1,11 +1,8 @@
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
use std::hash::Hash;
|
||||
use std::hash::Hasher;
|
||||
use std::sync::Arc;
|
||||
use std::sync::RwLock;
|
||||
|
||||
use codex_exec_server::ExecutorFileSystem;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
|
||||
use crate::SkillLoadOutcome;
|
||||
@@ -16,62 +13,16 @@ use crate::model::SkillFileSystemsByPath;
|
||||
|
||||
const MAX_CACHED_PLUGIN_SKILL_ROOTS: usize = 256;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct FileSystemIdentity(Arc<dyn ExecutorFileSystem>);
|
||||
|
||||
impl PartialEq for FileSystemIdentity {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
Arc::ptr_eq(&self.0, &other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for FileSystemIdentity {}
|
||||
|
||||
impl Hash for FileSystemIdentity {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
(Arc::as_ptr(&self.0) as *const ()).hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Hash)]
|
||||
struct SkillRootCacheKey {
|
||||
path: AbsolutePathBuf,
|
||||
scope_rank: u8,
|
||||
file_system: FileSystemIdentity,
|
||||
plugin_id: String,
|
||||
plugin_namespace: String,
|
||||
plugin_root: AbsolutePathBuf,
|
||||
}
|
||||
|
||||
impl SkillRootCacheKey {
|
||||
fn from_root(root: &SkillRoot) -> Option<Self> {
|
||||
Some(Self {
|
||||
path: root.path.clone(),
|
||||
scope_rank: scope_rank(root.scope),
|
||||
file_system: FileSystemIdentity(Arc::clone(&root.file_system)),
|
||||
plugin_id: root.plugin_id.clone()?,
|
||||
plugin_namespace: root.plugin_namespace.clone()?,
|
||||
plugin_root: root.plugin_root.clone()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Loads skill roots and reuses parsed plugin-root snapshots until explicitly invalidated.
|
||||
/// Shares parsed plugin skill-root snapshots between plugin and skill loading.
|
||||
///
|
||||
/// Non-plugin roots are always loaded directly because their filesystem lifecycle is owned by the
|
||||
/// environment or config layer that supplied them.
|
||||
#[derive(Default)]
|
||||
struct PluginRootCache {
|
||||
generation: u64,
|
||||
snapshots: HashMap<SkillRootCacheKey, SkillRootSnapshot>,
|
||||
pub struct PluginSkillRootCache {
|
||||
snapshots: RwLock<HashMap<AbsolutePathBuf, SkillRootSnapshot>>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct SkillRootLoader {
|
||||
plugin_root_cache: RwLock<PluginRootCache>,
|
||||
}
|
||||
|
||||
impl SkillRootLoader {
|
||||
impl PluginSkillRootCache {
|
||||
/// Loads and merges roots, reusing snapshots for roots owned by a plugin.
|
||||
pub async fn load_skills_from_roots<I>(&self, roots: I) -> SkillLoadOutcome
|
||||
where
|
||||
@@ -79,16 +30,28 @@ impl SkillRootLoader {
|
||||
{
|
||||
let mut snapshots = Vec::new();
|
||||
for root in roots {
|
||||
let cache_key = SkillRootCacheKey::from_root(&root);
|
||||
let (cache_generation, cached_snapshot) = cache_key
|
||||
// Plugin skill roots always use local filesystem and User scope, so the absolute skill
|
||||
// root path is sufficient to share their snapshot between plugin and skill loading.
|
||||
let cache_key = root.plugin_root.as_ref().map(|_| root.path.clone());
|
||||
let cached_snapshot = cache_key
|
||||
.as_ref()
|
||||
.map_or((0, None), |key| self.cached_snapshot(key));
|
||||
.and_then(|root| match self.snapshots.read() {
|
||||
Ok(cache) => cache.get(root).cloned(),
|
||||
Err(err) => err.into_inner().get(root).cloned(),
|
||||
});
|
||||
let snapshot = match cached_snapshot {
|
||||
Some(snapshot) => snapshot,
|
||||
None => {
|
||||
let snapshot = load_skill_root(root).await;
|
||||
if let Some(cache_key) = cache_key {
|
||||
self.cache_snapshot(cache_generation, cache_key, snapshot.clone());
|
||||
if let Some(root) = cache_key {
|
||||
let mut cache = self
|
||||
.snapshots
|
||||
.write()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
if cache.len() < MAX_CACHED_PLUGIN_SKILL_ROOTS || cache.contains_key(&root)
|
||||
{
|
||||
cache.insert(root, snapshot.clone());
|
||||
}
|
||||
}
|
||||
snapshot
|
||||
}
|
||||
@@ -102,34 +65,10 @@ impl SkillRootLoader {
|
||||
/// Invalidates every cached plugin-root snapshot.
|
||||
pub fn clear_cache(&self) {
|
||||
let mut cache = self
|
||||
.plugin_root_cache
|
||||
.snapshots
|
||||
.write()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
cache.generation = cache.generation.wrapping_add(1);
|
||||
cache.snapshots.clear();
|
||||
}
|
||||
|
||||
fn cached_snapshot(&self, key: &SkillRootCacheKey) -> (u64, Option<SkillRootSnapshot>) {
|
||||
match self.plugin_root_cache.read() {
|
||||
Ok(cache) => (cache.generation, cache.snapshots.get(key).cloned()),
|
||||
Err(err) => {
|
||||
let cache = err.into_inner();
|
||||
(cache.generation, cache.snapshots.get(key).cloned())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn cache_snapshot(&self, generation: u64, key: SkillRootCacheKey, snapshot: SkillRootSnapshot) {
|
||||
let mut cache = self
|
||||
.plugin_root_cache
|
||||
.write()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
if cache.generation == generation
|
||||
&& (cache.snapshots.len() < MAX_CACHED_PLUGIN_SKILL_ROOTS
|
||||
|| cache.snapshots.contains_key(&key))
|
||||
{
|
||||
cache.snapshots.insert(key, snapshot);
|
||||
}
|
||||
cache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,9 +117,10 @@ fn merge_skill_root_snapshots(snapshots: Vec<SkillRootSnapshot>) -> SkillLoadOut
|
||||
outcome.skill_root_by_path = Arc::new(skill_root_by_path);
|
||||
outcome.file_systems_by_skill_path = SkillFileSystemsByPath::new(file_systems_by_skill_path);
|
||||
|
||||
// The merged outcome includes non-plugin roots, so preserve the global scope ordering.
|
||||
outcome.skills.sort_by(|a, b| {
|
||||
scope_rank(a.scope)
|
||||
.cmp(&scope_rank(b.scope))
|
||||
merged_skill_scope_rank(a.scope)
|
||||
.cmp(&merged_skill_scope_rank(b.scope))
|
||||
.then_with(|| a.name.cmp(&b.name))
|
||||
.then_with(|| a.path_to_skills_md.cmp(&b.path_to_skills_md))
|
||||
});
|
||||
@@ -188,7 +128,7 @@ fn merge_skill_root_snapshots(snapshots: Vec<SkillRootSnapshot>) -> SkillLoadOut
|
||||
outcome
|
||||
}
|
||||
|
||||
fn scope_rank(scope: codex_protocol::protocol::SkillScope) -> u8 {
|
||||
fn merged_skill_scope_rank(scope: codex_protocol::protocol::SkillScope) -> u8 {
|
||||
use codex_protocol::protocol::SkillScope;
|
||||
|
||||
match scope {
|
||||
@@ -200,5 +140,5 @@ fn scope_rank(scope: codex_protocol::protocol::SkillScope) -> u8 {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "root_loader_tests.rs"]
|
||||
#[path = "plugin_skill_root_cache_tests.rs"]
|
||||
mod tests;
|
||||
+7
-28
@@ -5,10 +5,8 @@ use codex_exec_server::LOCAL_FS;
|
||||
use codex_protocol::protocol::SkillScope;
|
||||
use codex_utils_absolute_path::test_support::PathExt;
|
||||
|
||||
use super::SkillRootCacheKey;
|
||||
use super::SkillRootLoader;
|
||||
use super::PluginSkillRootCache;
|
||||
use crate::loader::SkillRoot;
|
||||
use crate::loader::load_skill_root;
|
||||
|
||||
fn plugin_root(path: &std::path::Path) -> SkillRoot {
|
||||
SkillRoot {
|
||||
@@ -32,45 +30,26 @@ fn write_skill(path: &std::path::Path, description: &str) {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn reuses_plugin_root_snapshot_until_cache_is_cleared() {
|
||||
async fn reuses_plugin_skill_root_snapshot_until_cache_is_cleared() {
|
||||
let tempdir = tempfile::tempdir().expect("tempdir");
|
||||
let loader = SkillRootLoader::default();
|
||||
let cache = PluginSkillRootCache::default();
|
||||
|
||||
write_skill(tempdir.path(), "first");
|
||||
let first = loader
|
||||
let first = cache
|
||||
.load_skills_from_roots([plugin_root(tempdir.path())])
|
||||
.await;
|
||||
|
||||
write_skill(tempdir.path(), "second");
|
||||
let cached = loader
|
||||
let cached = cache
|
||||
.load_skills_from_roots([plugin_root(tempdir.path())])
|
||||
.await;
|
||||
assert_eq!(cached.skills, first.skills);
|
||||
|
||||
loader.clear_cache();
|
||||
let refreshed = loader
|
||||
cache.clear_cache();
|
||||
let refreshed = cache
|
||||
.load_skills_from_roots([plugin_root(tempdir.path())])
|
||||
.await;
|
||||
let mut expected = first.skills;
|
||||
expected[0].description = "second".to_string();
|
||||
assert_eq!(refreshed.skills, expected);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn clear_cache_rejects_an_in_flight_snapshot() {
|
||||
let tempdir = tempfile::tempdir().expect("tempdir");
|
||||
let loader = SkillRootLoader::default();
|
||||
write_skill(tempdir.path(), "first");
|
||||
|
||||
let root = plugin_root(tempdir.path());
|
||||
let cache_key = SkillRootCacheKey::from_root(&root).expect("plugin root cache key");
|
||||
let (generation, cached_snapshot) = loader.cached_snapshot(&cache_key);
|
||||
assert!(cached_snapshot.is_none());
|
||||
|
||||
let snapshot = load_skill_root(root).await;
|
||||
loader.clear_cache();
|
||||
loader.cache_snapshot(generation, cache_key.clone(), snapshot);
|
||||
|
||||
let (_, cached_snapshot) = loader.cached_snapshot(&cache_key);
|
||||
assert!(cached_snapshot.is_none());
|
||||
}
|
||||
@@ -14,8 +14,8 @@ use tracing::instrument;
|
||||
use tracing::warn;
|
||||
|
||||
use crate::HostSkillsSnapshot;
|
||||
use crate::PluginSkillRootCache;
|
||||
use crate::SkillLoadOutcome;
|
||||
use crate::SkillRootLoader;
|
||||
use crate::build_implicit_skill_path_indexes;
|
||||
use crate::config_rules::SkillConfigRules;
|
||||
use crate::config_rules::resolve_disabled_skill_paths;
|
||||
@@ -56,7 +56,7 @@ impl SkillsLoadInput {
|
||||
pub struct SkillsService {
|
||||
codex_home: AbsolutePathBuf,
|
||||
restriction_product: Option<Product>,
|
||||
skill_root_loader: Arc<SkillRootLoader>,
|
||||
plugin_skill_root_cache: Arc<PluginSkillRootCache>,
|
||||
extra_roots: RwLock<Vec<AbsolutePathBuf>>,
|
||||
cache_by_cwd: RwLock<HashMap<AbsolutePathBuf, HostSkillsSnapshot>>,
|
||||
cache_by_config: RwLock<HashMap<ConfigSkillsCacheKey, HostSkillsSnapshot>>,
|
||||
@@ -75,7 +75,7 @@ impl SkillsService {
|
||||
let service = Self {
|
||||
codex_home,
|
||||
restriction_product,
|
||||
skill_root_loader: Arc::new(SkillRootLoader::default()),
|
||||
plugin_skill_root_cache: Arc::new(PluginSkillRootCache::default()),
|
||||
extra_roots: RwLock::new(Vec::new()),
|
||||
cache_by_cwd: RwLock::new(HashMap::new()),
|
||||
cache_by_config: RwLock::new(HashMap::new()),
|
||||
@@ -90,9 +90,12 @@ impl SkillsService {
|
||||
service
|
||||
}
|
||||
|
||||
/// Reuses plugin-root snapshots populated by another owner of the loader.
|
||||
pub fn with_skill_root_loader(mut self, skill_root_loader: Arc<SkillRootLoader>) -> Self {
|
||||
self.skill_root_loader = skill_root_loader;
|
||||
/// Reuses plugin-root snapshots populated by another owner of the cache.
|
||||
pub fn with_plugin_skill_root_cache(
|
||||
mut self,
|
||||
plugin_skill_root_cache: Arc<PluginSkillRootCache>,
|
||||
) -> Self {
|
||||
self.plugin_skill_root_cache = plugin_skill_root_cache;
|
||||
self
|
||||
}
|
||||
|
||||
@@ -207,7 +210,9 @@ impl SkillsService {
|
||||
skill_config_rules: &SkillConfigRules,
|
||||
) -> SkillLoadOutcome {
|
||||
let outcome = crate::filter_skill_load_outcome_for_product(
|
||||
self.skill_root_loader.load_skills_from_roots(roots).await,
|
||||
self.plugin_skill_root_cache
|
||||
.load_skills_from_roots(roots)
|
||||
.await,
|
||||
self.restriction_product,
|
||||
);
|
||||
let disabled_paths = resolve_disabled_skill_paths(&outcome.skills, skill_config_rules);
|
||||
@@ -215,7 +220,7 @@ impl SkillsService {
|
||||
}
|
||||
|
||||
pub fn clear_cache(&self) {
|
||||
self.skill_root_loader.clear_cache();
|
||||
self.plugin_skill_root_cache.clear_cache();
|
||||
let cleared_cwd = {
|
||||
let mut cache = self
|
||||
.cache_by_cwd
|
||||
|
||||
@@ -287,7 +287,7 @@ impl ThreadManager {
|
||||
config.bundled_skills_enabled(),
|
||||
restriction_product,
|
||||
)
|
||||
.with_skill_root_loader(plugins_manager.skill_root_loader()),
|
||||
.with_plugin_skill_root_cache(plugins_manager.plugin_skill_root_cache()),
|
||||
);
|
||||
Self {
|
||||
state: Arc::new(ThreadManagerState {
|
||||
@@ -382,7 +382,7 @@ impl ThreadManager {
|
||||
/*bundled_skills_enabled*/ true,
|
||||
restriction_product,
|
||||
)
|
||||
.with_skill_root_loader(plugins_manager.skill_root_loader()),
|
||||
.with_plugin_skill_root_cache(plugins_manager.plugin_skill_root_cache()),
|
||||
);
|
||||
// This test constructor has no Config input. Tests that need a non-local
|
||||
// process store should construct ThreadManager::new with an explicit store.
|
||||
|
||||
Reference in New Issue
Block a user