Make skill loading filesystem-aware (#17720)

Migrates skill loading to support reading repo skills from the remote
environment.
This commit is contained in:
pakrym-oai
2026-04-14 15:40:40 -07:00
committed by GitHub
parent 5ecaf09ab0
commit 96254a763a
20 changed files with 950 additions and 264 deletions
+6 -1
View File
@@ -658,7 +658,12 @@ enabled = false
let plugin_outcome = plugins_manager.plugins_for_config(&config).await;
let effective_skill_roots = plugin_outcome.effective_skill_roots();
let skills_input = skills_load_input_from_config(&config, effective_skill_roots);
let outcome = skills_manager.skills_for_config(&skills_input);
let outcome = skills_manager
.skills_for_config(
&skills_input,
Some(Arc::clone(&codex_exec_server::LOCAL_FS)),
)
.await;
let skill = outcome
.skills
.iter()
+22 -7
View File
@@ -496,10 +496,17 @@ impl Codex {
let (tx_sub, rx_sub) = async_channel::bounded(SUBMISSION_CHANNEL_CAPACITY);
let (tx_event, rx_event) = async_channel::unbounded();
let environment = environment_manager
.current()
.await
.map_err(|err| CodexErr::Fatal(format!("failed to create environment: {err}")))?;
let fs = environment
.as_ref()
.map(|environment| environment.get_filesystem());
let plugin_outcome = plugins_manager.plugins_for_config(&config).await;
let effective_skill_roots = plugin_outcome.effective_skill_roots();
let skills_input = skills_load_input_from_config(&config, effective_skill_roots);
let loaded_skills = skills_manager.skills_for_config(&skills_input);
let loaded_skills = skills_manager.skills_for_config(&skills_input, fs).await;
for err in &loaded_skills.errors {
error!(
@@ -544,10 +551,6 @@ impl Codex {
config.startup_warnings.push(message);
}
let environment = environment_manager
.current()
.await
.map_err(|err| CodexErr::Fatal(format!("failed to create environment: {err}")))?;
let user_instructions = get_user_instructions(&config, environment.as_deref()).await;
let exec_policy = if crate::guardian::is_guardian_reviewer_source(&session_source) {
@@ -2670,10 +2673,16 @@ impl Session {
.await;
let effective_skill_roots = plugin_outcome.effective_skill_roots();
let skills_input = skills_load_input_from_config(&per_turn_config, effective_skill_roots);
let fs = self
.services
.environment
.as_ref()
.map(|environment| environment.get_filesystem());
let skills_outcome = Arc::new(
self.services
.skills_manager
.skills_for_config(&skills_input),
.skills_for_config(&skills_input, fs)
.await,
);
let mut turn_context: TurnContext = Self::make_turn_context(
self.conversation_id,
@@ -5398,6 +5407,11 @@ mod handlers {
let skills_manager = &sess.services.skills_manager;
let plugins_manager = &sess.services.plugins_manager;
let fs = sess
.services
.environment
.as_ref()
.map(|environment| environment.get_filesystem());
let config = sess.get_config().await;
let codex_home = sess.codex_home().await;
let mut skills = Vec::new();
@@ -5454,7 +5468,7 @@ mod handlers {
config.bundled_skills_enabled(),
);
let outcome = skills_manager
.skills_for_cwd(&skills_input, force_reload)
.skills_for_cwd(&skills_input, force_reload, fs.clone())
.await;
let errors = super::errors_to_info(&outcome.errors);
let skills_metadata = super::skills_to_info(&outcome.skills, &outcome.disabled_paths);
@@ -6232,6 +6246,7 @@ pub(crate) async fn run_turn(
warnings: skill_warnings,
} = build_skill_injections(
&mentioned_skills,
skills_outcome,
Some(&session_telemetry),
&sess.services.analytics_events_client,
tracking.clone(),
+21 -2
View File
@@ -2450,12 +2450,19 @@ async fn new_default_turn_uses_config_aware_skills_for_role_overrides() {
)
.expect("write skill");
let skill_fs = session
.services
.environment
.as_ref()
.map(|environment| environment.get_filesystem())
.unwrap_or_else(|| std::sync::Arc::clone(&codex_exec_server::LOCAL_FS));
let parent_outcome = session
.services
.skills_manager
.skills_for_cwd(
&crate::skills_load_input_from_config(&parent_config, Vec::new()),
/*force_reload*/ true,
Some(Arc::clone(&skill_fs)),
)
.await;
let parent_skill = parent_outcome
@@ -2845,7 +2852,13 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
let effective_skill_roots = plugin_outcome.effective_skill_roots();
let skills_input =
crate::skills_load_input_from_config(&per_turn_config, effective_skill_roots);
let skills_outcome = Arc::new(services.skills_manager.skills_for_config(&skills_input));
let skill_fs = environment.get_filesystem();
let skills_outcome = Arc::new(
services
.skills_manager
.skills_for_config(&skills_input, Some(Arc::clone(&skill_fs)))
.await,
);
let turn_context = Session::make_turn_context(
conversation_id,
Some(Arc::clone(&auth_manager)),
@@ -3691,7 +3704,13 @@ pub(crate) async fn make_session_and_context_with_dynamic_tools_and_rx(
let effective_skill_roots = plugin_outcome.effective_skill_roots();
let skills_input =
crate::skills_load_input_from_config(&per_turn_config, effective_skill_roots);
let skills_outcome = Arc::new(services.skills_manager.skills_for_config(&skills_input));
let skill_fs = environment.get_filesystem();
let skills_outcome = Arc::new(
services
.skills_manager
.skills_for_config(&skills_input, Some(Arc::clone(&skill_fs)))
.await,
);
let turn_context = Arc::new(Session::make_turn_context(
conversation_id,
Some(Arc::clone(&auth_manager)),
+10
View File
@@ -252,6 +252,16 @@ pub struct WatchRegistration {
watched_paths: Vec<WatchPath>,
}
impl Default for WatchRegistration {
fn default() -> Self {
Self {
file_watcher: std::sync::Weak::new(),
subscriber_id: 0,
watched_paths: Vec::new(),
}
}
}
impl Drop for WatchRegistration {
fn drop(&mut self) {
if let Some(file_watcher) = self.file_watcher.upgrade() {
+15 -11
View File
@@ -46,6 +46,7 @@ use codex_app_server_protocol::ConfigValueWriteParams;
use codex_app_server_protocol::MergeStrategy;
use codex_config::types::McpServerConfig;
use codex_config::types::PluginConfig;
use codex_exec_server::LOCAL_FS;
use codex_features::Feature;
use codex_login::AuthManager;
use codex_login::CodexAuth;
@@ -1031,7 +1032,8 @@ impl PluginsManager {
manifest_paths,
self.restriction_product,
&skill_config_rules,
);
)
.await;
let apps = load_apps_from_paths(
source_path.as_path(),
plugin_app_config_paths(source_path.as_path(), manifest_paths),
@@ -1751,7 +1753,8 @@ async fn load_plugin(
manifest_paths,
restriction_product,
skill_config_rules,
);
)
.await;
let has_enabled_skills = resolved_skills.has_enabled_skills();
loaded_plugin.disabled_skill_paths = resolved_skills.disabled_skill_paths;
loaded_plugin.has_enabled_skills = has_enabled_skills;
@@ -1795,20 +1798,21 @@ impl ResolvedPluginSkills {
}
}
fn load_plugin_skills(
async fn load_plugin_skills(
plugin_root: &AbsolutePathBuf,
manifest_paths: &PluginManifestPaths,
restriction_product: Option<Product>,
skill_config_rules: &SkillConfigRules,
) -> ResolvedPluginSkills {
let outcome = load_skills_from_roots(
plugin_skill_roots(plugin_root, manifest_paths)
.into_iter()
.map(|path| SkillRoot {
path,
scope: SkillScope::User,
}),
);
let roots = plugin_skill_roots(plugin_root, manifest_paths)
.into_iter()
.map(|path| SkillRoot {
path,
scope: SkillScope::User,
file_system: Arc::clone(&LOCAL_FS),
})
.collect::<Vec<_>>();
let outcome = load_skills_from_roots(roots).await;
let had_errors = !outcome.errors.is_empty();
let skills = outcome
.skills
+3 -1
View File
@@ -59,12 +59,14 @@ impl SkillsWatcher {
config: &Config,
skills_manager: &SkillsManager,
plugins_manager: &PluginsManager,
fs: Option<Arc<dyn codex_exec_server::ExecutorFileSystem>>,
) -> WatchRegistration {
let plugin_outcome = plugins_manager.plugins_for_config(config).await;
let effective_skill_roots = plugin_outcome.effective_skill_roots();
let skills_input = skills_load_input_from_config(config, effective_skill_roots);
let roots = skills_manager
.skill_roots_for_config(&skills_input)
.skill_roots_for_config(&skills_input, fs)
.await
.into_iter()
.map(|root| WatchPath {
path: root.path.into_path_buf(),
+22 -8
View File
@@ -365,6 +365,10 @@ impl ThreadManager {
self.state.mcp_manager.clone()
}
pub fn environment_manager(&self) -> Arc<EnvironmentManager> {
self.state.environment_manager.clone()
}
pub fn get_models_manager(&self) -> Arc<ModelsManager> {
self.state.models_manager.clone()
}
@@ -903,14 +907,24 @@ impl ThreadManagerState {
parent_trace: Option<W3cTraceContext>,
user_shell_override: Option<crate::shell::Shell>,
) -> CodexResult<NewThread> {
let watch_registration = self
.skills_watcher
.register_config(
&config,
self.skills_manager.as_ref(),
self.plugins_manager.as_ref(),
)
.await;
let environment = self
.environment_manager
.current()
.await
.map_err(|err| CodexErr::Fatal(format!("failed to create environment: {err}")))?;
let watch_registration = match environment.as_ref() {
Some(environment) if !environment.is_remote() => {
self.skills_watcher
.register_config(
&config,
self.skills_manager.as_ref(),
self.plugins_manager.as_ref(),
Some(environment.get_filesystem()),
)
.await
}
Some(_) | None => crate::file_watcher::WatchRegistration::default(),
};
let CodexSpawnOk {
codex, thread_id, ..
} = Codex::spawn(CodexSpawnArgs {