mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
Reapply "Move skills watcher to app-server" (#21652)
## Why PR #21460 reverted the earlier move of skills change watching from `codex-core` into app-server. This reapplies that boundary change so app-server owns client-facing `skills/changed` notifications and core no longer carries the watcher. ## What - Restore the app-server `SkillsWatcher` and register it from thread listener setup. - Remove the core-owned skills watcher and its core live-reload integration surface. - Restore app-server coverage for `skills/changed` notifications after a watched skill file changes. ## Validation - `cargo test -p codex-app-server --test all suite::v2::skills_list::skills_changed_notification_is_emitted_after_skill_change -- --exact --nocapture` - `cargo test -p codex-core --lib --no-run`
This commit is contained in:
committed by
GitHub
Unverified
parent
95ca276373
commit
408e6218ab
@@ -6,7 +6,6 @@ use crate::config::Config;
|
||||
use crate::config::ThreadStoreConfig;
|
||||
use crate::environment_selection::default_thread_environment_selections;
|
||||
use crate::environment_selection::resolve_environment_selections;
|
||||
use crate::file_watcher::FileWatcher;
|
||||
use crate::mcp::McpManager;
|
||||
use crate::rollout::truncation;
|
||||
use crate::session::Codex;
|
||||
@@ -14,8 +13,6 @@ use crate::session::CodexSpawnArgs;
|
||||
use crate::session::CodexSpawnOk;
|
||||
use crate::session::INITIAL_SUBMIT_ID;
|
||||
use crate::shell_snapshot::ShellSnapshot;
|
||||
use crate::skills_watcher::SkillsWatcher;
|
||||
use crate::skills_watcher::SkillsWatcherEvent;
|
||||
use crate::tasks::InterruptedTurnHistoryMarker;
|
||||
use crate::tasks::interrupted_turn_history_marker;
|
||||
use codex_analytics::AnalyticsEventsClient;
|
||||
@@ -71,8 +68,6 @@ use std::sync::Arc;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::time::Duration;
|
||||
use tokio::runtime::Handle;
|
||||
use tokio::runtime::RuntimeFlavor;
|
||||
use tokio::sync::RwLock;
|
||||
use tokio::sync::broadcast;
|
||||
use tracing::warn;
|
||||
@@ -106,47 +101,6 @@ impl Drop for TempCodexHomeGuard {
|
||||
}
|
||||
}
|
||||
|
||||
fn build_skills_watcher(skills_manager: Arc<SkillsManager>) -> Arc<SkillsWatcher> {
|
||||
if should_use_test_thread_manager_behavior()
|
||||
&& let Ok(handle) = Handle::try_current()
|
||||
&& handle.runtime_flavor() == RuntimeFlavor::CurrentThread
|
||||
{
|
||||
// The real watcher spins background tasks that can starve the
|
||||
// current-thread test runtime and cause event waits to time out.
|
||||
warn!("using noop skills watcher under current-thread test runtime");
|
||||
return Arc::new(SkillsWatcher::noop());
|
||||
}
|
||||
|
||||
let file_watcher = match FileWatcher::new() {
|
||||
Ok(file_watcher) => Arc::new(file_watcher),
|
||||
Err(err) => {
|
||||
warn!("failed to initialize file watcher: {err}");
|
||||
Arc::new(FileWatcher::noop())
|
||||
}
|
||||
};
|
||||
let skills_watcher = Arc::new(SkillsWatcher::new(&file_watcher));
|
||||
|
||||
let mut rx = skills_watcher.subscribe();
|
||||
let skills_manager = Arc::clone(&skills_manager);
|
||||
if let Ok(handle) = Handle::try_current() {
|
||||
handle.spawn(async move {
|
||||
loop {
|
||||
match rx.recv().await {
|
||||
Ok(SkillsWatcherEvent::SkillsChanged { .. }) => {
|
||||
skills_manager.clear_cache();
|
||||
}
|
||||
Err(broadcast::error::RecvError::Closed) => break,
|
||||
Err(broadcast::error::RecvError::Lagged(_)) => continue,
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
warn!("skills watcher listener skipped: no Tokio runtime available");
|
||||
}
|
||||
|
||||
skills_watcher
|
||||
}
|
||||
|
||||
/// Represents a newly created Codex thread (formerly called a conversation), including the first event
|
||||
/// (which is [`EventMsg::SessionConfigured`]).
|
||||
pub struct NewThread {
|
||||
@@ -247,7 +201,6 @@ pub(crate) struct ThreadManagerState {
|
||||
skills_manager: Arc<SkillsManager>,
|
||||
plugins_manager: Arc<PluginsManager>,
|
||||
mcp_manager: Arc<McpManager>,
|
||||
skills_watcher: Arc<SkillsWatcher>,
|
||||
thread_store: Arc<dyn ThreadStore>,
|
||||
attestation_provider: Option<Arc<dyn AttestationProvider>>,
|
||||
session_source: SessionSource,
|
||||
@@ -308,7 +261,6 @@ impl ThreadManager {
|
||||
config.bundled_skills_enabled(),
|
||||
restriction_product,
|
||||
));
|
||||
let skills_watcher = build_skills_watcher(Arc::clone(&skills_manager));
|
||||
Self {
|
||||
state: Arc::new(ThreadManagerState {
|
||||
threads: Arc::new(RwLock::new(HashMap::new())),
|
||||
@@ -318,7 +270,6 @@ impl ThreadManager {
|
||||
skills_manager,
|
||||
plugins_manager,
|
||||
mcp_manager,
|
||||
skills_watcher,
|
||||
thread_store,
|
||||
attestation_provider,
|
||||
auth_manager,
|
||||
@@ -399,7 +350,6 @@ impl ThreadManager {
|
||||
/*bundled_skills_enabled*/ true,
|
||||
restriction_product,
|
||||
));
|
||||
let skills_watcher = build_skills_watcher(Arc::clone(&skills_manager));
|
||||
// This test constructor has no Config input. Tests that need a non-local
|
||||
// process store should construct ThreadManager::new with an explicit store.
|
||||
let thread_store: Arc<dyn ThreadStore> = Arc::new(LocalThreadStore::new(
|
||||
@@ -420,7 +370,6 @@ impl ThreadManager {
|
||||
skills_manager,
|
||||
plugins_manager,
|
||||
mcp_manager,
|
||||
skills_watcher,
|
||||
thread_store,
|
||||
attestation_provider: None,
|
||||
auth_manager,
|
||||
@@ -1165,19 +1114,6 @@ impl ThreadManagerState {
|
||||
}
|
||||
let environment_selections =
|
||||
resolve_environment_selections(self.environment_manager.as_ref(), &environments)?;
|
||||
let watch_registration = match environment_selections.primary() {
|
||||
Some(turn_environment) if !turn_environment.environment.is_remote() => {
|
||||
self.skills_watcher
|
||||
.register_config(
|
||||
&config,
|
||||
self.skills_manager.as_ref(),
|
||||
self.plugins_manager.as_ref(),
|
||||
Some(turn_environment.environment.get_filesystem()),
|
||||
)
|
||||
.await
|
||||
}
|
||||
Some(_) | None => crate::file_watcher::WatchRegistration::default(),
|
||||
};
|
||||
let parent_rollout_thread_trace = self
|
||||
.parent_rollout_thread_trace_for_source(&session_source, &initial_history)
|
||||
.await;
|
||||
@@ -1193,7 +1129,6 @@ impl ThreadManagerState {
|
||||
skills_manager: Arc::clone(&self.skills_manager),
|
||||
plugins_manager: Arc::clone(&self.plugins_manager),
|
||||
mcp_manager: Arc::clone(&self.mcp_manager),
|
||||
skills_watcher: Arc::clone(&self.skills_watcher),
|
||||
conversation_history: initial_history,
|
||||
session_source,
|
||||
thread_source,
|
||||
@@ -1213,7 +1148,7 @@ impl ThreadManagerState {
|
||||
})
|
||||
.await?;
|
||||
let new_thread = self
|
||||
.finalize_thread_spawn(codex, thread_id, tracked_session_source, watch_registration)
|
||||
.finalize_thread_spawn(codex, thread_id, tracked_session_source)
|
||||
.await?;
|
||||
if is_resumed_thread
|
||||
&& let Err(err) = new_thread.thread.apply_goal_resume_runtime_effects().await
|
||||
@@ -1228,7 +1163,6 @@ impl ThreadManagerState {
|
||||
codex: Codex,
|
||||
thread_id: ThreadId,
|
||||
session_source: SessionSource,
|
||||
watch_registration: crate::file_watcher::WatchRegistration,
|
||||
) -> CodexResult<NewThread> {
|
||||
let event = codex.next_event().await?;
|
||||
let session_configured = match event {
|
||||
@@ -1249,7 +1183,6 @@ impl ThreadManagerState {
|
||||
session_configured.clone(),
|
||||
session_configured.rollout_path.clone(),
|
||||
session_source,
|
||||
watch_registration,
|
||||
));
|
||||
e.insert(thread.clone());
|
||||
return Ok(NewThread {
|
||||
|
||||
Reference in New Issue
Block a user