Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 18 additions & 10 deletions codex-rs/app-server/src/request_processors/catalog_processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ const SKILLS_LIST_CWD_CONCURRENCY: usize = 5;

fn skills_to_info(
skills: &[codex_core::skills::SkillMetadata],
disabled_paths: &HashSet<AbsolutePathBuf>,
disabled_paths: &HashSet<codex_exec_server::EnvironmentPathRef>,
) -> Vec<codex_app_server_protocol::SkillMetadata> {
skills
.iter()
.map(|skill| {
let enabled = !disabled_paths.contains(&skill.path_to_skills_md);
let enabled = !disabled_paths.contains(&skill.source_path);
codex_app_server_protocol::SkillMetadata {
name: skill.name.clone(),
description: skill.description.clone(),
Expand Down Expand Up @@ -513,15 +513,17 @@ impl CatalogRequestProcessor {
.await;
let skills_manager = self.thread_manager.skills_manager();
let plugins_manager = self.thread_manager.plugins_manager();
let fs = self
.thread_manager
.environment_manager()
.default_environment()
.map(|environment| environment.get_filesystem());
let environment_manager = self.thread_manager.environment_manager();
// TODO: Reintroduce env-scoped `skills/list` only after cwd/config-layer discovery can run
// against the selected environment and the design decides which non-repo roots stay local
// versus follow that environment.
let selected_environment = environment_manager.default_environment();
let local_file_system = Some(Arc::clone(&codex_exec_server::LOCAL_FS));
let mut data = futures::stream::iter(cwds.into_iter().enumerate())
.map(|(index, cwd)| {
let config = &config;
let fs = fs.clone();
let local_file_system = local_file_system.clone();
let selected_environment = selected_environment.clone();
let plugins_manager = &plugins_manager;
let skills_manager = &skills_manager;
async move {
Expand Down Expand Up @@ -554,13 +556,19 @@ impl CatalogRequestProcessor {
Vec::new()
};
let skills_input = codex_core::skills::SkillsLoadInput::new(
cwd_abs.clone(),
selected_environment.as_ref().map(|environment| {
codex_exec_server::EnvironmentPathRef::new(
environment.get_filesystem(),
cwd_abs.clone(),
)
}),
local_file_system,
effective_skill_roots,
config_layer_stack,
config.bundled_skills_enabled(),
);
let outcome = skills_manager
.skills_for_cwd(&skills_input, force_reload, fs)
.skills_for_cwd(&skills_input, force_reload)
.await;
let errors = errors_to_info(&outcome.errors);
let skills = skills_to_info(&outcome.skills, &outcome.disabled_paths);
Expand Down
11 changes: 8 additions & 3 deletions codex-rs/app-server/src/skills_watcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use codex_core::ThreadManager;
use codex_core::config::Config;
use codex_core::skills::SkillsLoadInput;
use codex_core::skills::SkillsManager;
use codex_exec_server::EnvironmentPathRef;
use codex_file_watcher::FileWatcher;
use codex_file_watcher::FileWatcherSubscriber;
use codex_file_watcher::Receiver;
Expand Down Expand Up @@ -100,22 +101,26 @@ impl SkillsWatcher {
return WatchRegistration::default();
}

let root_path_ref =
EnvironmentPathRef::new(environment.get_filesystem(), config.cwd.clone());
let local_file_system = Some(Arc::clone(&codex_exec_server::LOCAL_FS));
let plugins_input = config.plugins_config_input();
let plugins_manager = thread_manager.plugins_manager();
let plugin_outcome = plugins_manager.plugins_for_config(&plugins_input).await;
let skills_input = SkillsLoadInput::new(
config.cwd.clone(),
Some(root_path_ref),
local_file_system,
plugin_outcome.effective_plugin_skill_roots(),
config.config_layer_stack.clone(),
config.bundled_skills_enabled(),
);
let roots = thread_manager
.skills_manager()
.skill_roots_for_config(&skills_input, Some(environment.get_filesystem()))
.skill_roots_for_config(&skills_input)
.await
.into_iter()
.map(|root| WatchPath {
path: root.path.into_path_buf(),
path: root.path.path().to_path_buf(),
recursive: true,
})
.collect();
Expand Down
19 changes: 11 additions & 8 deletions codex-rs/core-plugins/src/loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ 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::SkillRoot;
use codex_core_skills::loader::load_skills_from_roots;
use codex_exec_server::LOCAL_FS;
use codex_exec_server::EnvironmentPathRef;
use codex_plugin::AppConnectorId;
use codex_plugin::LoadedPlugin;
use codex_plugin::PluginCapabilitySummary;
Expand All @@ -40,7 +40,6 @@ use std::collections::HashSet;
use std::fs;
use std::path::Path;
use std::process::Command;
use std::sync::Arc;
use tempfile::TempDir;
use tracing::warn;

Expand Down Expand Up @@ -564,8 +563,10 @@ async fn load_plugin(
.or_else(|| Some(manifest.name.clone()));
loaded_plugin.manifest_description = manifest.description.clone();
loaded_plugin.skill_roots = plugin_skill_roots(&plugin_root, manifest_paths);
// TODO: Support multi-env plugin skill loading. Plugin discovery is local-only today, so bind
// plugin skill reads to the local environment instead of the selected turn environment.
let resolved_skills = load_plugin_skills(
&plugin_root,
EnvironmentPathRef::local(plugin_root.clone()),
&loaded_plugin_id,
manifest_paths,
restriction_product,
Expand Down Expand Up @@ -642,18 +643,17 @@ impl ResolvedPluginSkills {
}

pub async fn load_plugin_skills(
plugin_root: &AbsolutePathBuf,
plugin_root: EnvironmentPathRef,
plugin_id: &PluginId,
manifest_paths: &PluginManifestPaths,
restriction_product: Option<Product>,
skill_config_rules: &SkillConfigRules,
) -> ResolvedPluginSkills {
let roots = plugin_skill_roots(plugin_root, manifest_paths)
let roots = plugin_skill_roots(plugin_root.path(), manifest_paths)
.into_iter()
.map(|path| SkillRoot {
path,
path: plugin_root.with_path(path),
scope: SkillScope::User,
file_system: Arc::clone(&LOCAL_FS),
plugin_id: Some(plugin_id.as_key()),
plugin_root: Some(plugin_root.clone()),
})
Expand All @@ -665,7 +665,10 @@ pub async fn load_plugin_skills(
.into_iter()
.filter(|skill| skill.matches_product_restriction_for_product(restriction_product))
.collect::<Vec<_>>();
let disabled_skill_paths = resolve_disabled_skill_paths(&skills, skill_config_rules);
let disabled_skill_paths = resolve_disabled_skill_paths(&skills, skill_config_rules)
.into_iter()
.map(|path| path.path().clone())
.collect();

ResolvedPluginSkills {
skills,
Expand Down
4 changes: 3 additions & 1 deletion codex-rs/core-plugins/src/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1419,8 +1419,10 @@ impl PluginsManager {
manifest.interface.clone(),
marketplace_category,
);
// TODO: Support multi-env plugin skill loading. Marketplace plugins are installed and
// scanned locally today, so bind plugin skill reads to the local environment.
let resolved_skills = load_plugin_skills(
&source_path,
codex_exec_server::EnvironmentPathRef::local(source_path.clone()),
&plugin_id,
&manifest.paths,
self.restriction_product,
Expand Down
25 changes: 16 additions & 9 deletions codex-rs/core-skills/src/config_rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use codex_config::ConfigLayerStack;
use codex_config::ConfigLayerStackOrdering;
use codex_config::SkillConfig;
use codex_config::SkillsConfig;
use codex_exec_server::EnvironmentPathRef;
use codex_utils_absolute_path::AbsolutePathBuf;
use tracing::warn;

Expand Down Expand Up @@ -71,28 +72,34 @@ pub fn skill_config_rules_from_stack(config_layer_stack: &ConfigLayerStack) -> S
pub fn resolve_disabled_skill_paths(
skills: &[SkillMetadata],
rules: &SkillConfigRules,
) -> HashSet<AbsolutePathBuf> {
) -> HashSet<EnvironmentPathRef> {
let mut disabled_paths = HashSet::new();

for entry in &rules.entries {
match &entry.selector {
SkillConfigRuleSelector::Path(path) => {
if entry.enabled {
disabled_paths.remove(path);
} else {
disabled_paths.insert(path.clone());
for source_path in skills
.iter()
.filter(|skill| skill.path_to_skills_md == *path)
.map(|skill| skill.source_path.clone())
{
if entry.enabled {
disabled_paths.remove(&source_path);
} else {
disabled_paths.insert(source_path);
}
}
}
SkillConfigRuleSelector::Name(name) => {
for path in skills
for source_path in skills
.iter()
.filter(|skill| skill.name == *name)
.map(|skill| skill.path_to_skills_md.clone())
.map(|skill| skill.source_path.clone())
{
if entry.enabled {
disabled_paths.remove(&path);
disabled_paths.remove(&source_path);
} else {
disabled_paths.insert(path);
disabled_paths.insert(source_path);
}
}
}
Expand Down
Loading
Loading