Skip to content
Merged
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
6 changes: 5 additions & 1 deletion codex-rs/app-server/src/message_processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,11 @@ impl MessageProcessor {
thread_manager
.plugins_manager()
.set_analytics_events_client(analytics_events_client.clone());
let skills_watcher = SkillsWatcher::new(thread_manager.skills_service(), outgoing.clone());
let skills_watcher = SkillsWatcher::new(
thread_manager.skills_service(),
&config.codex_home,
outgoing.clone(),
);

let pending_thread_unloads = Arc::new(Mutex::new(HashSet::new()));
let thread_watch_manager =
Expand Down
28 changes: 24 additions & 4 deletions codex-rs/app-server/src/skills_watcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ use codex_file_watcher::Receiver;
use codex_file_watcher::ThrottledWatchReceiver;
use codex_file_watcher::WatchPath;
use codex_file_watcher::WatchRegistration;
use codex_protocol::protocol::SkillScope;
use codex_protocol::protocol::TurnEnvironmentSelection;
use codex_skills::system_cache_root_dir;
use codex_utils_absolute_path::AbsolutePathBuf;
use tokio_util::sync::CancellationToken;
use tokio_util::sync::DropGuard;
Expand All @@ -36,6 +38,7 @@ pub(crate) struct SkillsWatcher {
impl SkillsWatcher {
pub(crate) fn new(
skills_service: Arc<SkillsService>,
codex_home: &AbsolutePathBuf,
outgoing: Arc<OutgoingMessageSender>,
) -> Arc<Self> {
let file_watcher = match FileWatcher::new() {
Expand All @@ -48,7 +51,14 @@ impl SkillsWatcher {
let (subscriber, rx) = file_watcher.add_subscriber();
let shutdown_token = CancellationToken::new();
let shutdown_drop_guard = shutdown_token.clone().drop_guard();
Self::spawn_event_loop(rx, skills_service, outgoing, shutdown_token.child_token());
let system_skills_root = system_cache_root_dir(codex_home);
Self::spawn_event_loop(
rx,
skills_service,
system_skills_root,
outgoing,
shutdown_token.child_token(),
);
Arc::new(Self {
subscriber,
runtime_extra_roots_registration: Mutex::new(WatchRegistration::default()),
Expand Down Expand Up @@ -114,8 +124,9 @@ impl SkillsWatcher {
.skill_roots_for_config(&skills_input, Some(environment.get_filesystem()))
.await
.into_iter()
// Plugin roots are invalidated by plugin lifecycle operations.
.filter(|root| root.plugin_identity.is_none())
// Plugin roots have explicit lifecycle invalidation; generated system skills are
// installed before this watcher starts.
.filter(|root| root.plugin_identity.is_none() && root.scope != SkillScope::System)
.map(|root| WatchPath {
path: root.path.into_path_buf(),
recursive: true,
Expand All @@ -127,6 +138,7 @@ impl SkillsWatcher {
fn spawn_event_loop(
rx: Receiver,
skills_service: Arc<SkillsService>,
system_skills_root: AbsolutePathBuf,
outgoing: Arc<OutgoingMessageSender>,
shutdown_token: CancellationToken,
) {
Expand All @@ -141,8 +153,16 @@ impl SkillsWatcher {
_ = shutdown_token.cancelled() => break,
event = rx.recv() => event,
};
if event.is_none() {
let Some(event) = event else {
break;
};
// The legacy user-skills root contains `.system` and is watched recursively.
if event
.paths
.iter()
.all(|path| path.starts_with(system_skills_root.as_path()))
{
continue;
}
skills_service.clear_cache();
outgoing
Expand Down
Loading