@@ -49,6 +49,7 @@ const DEBOUNCE_DURATION: Duration = Duration::from_millis(300);
4949/// Dropping this stops the watcher.
5050pub struct SessionWatcher {
5151 _watcher : RecommendedWatcher ,
52+ _config_watcher : Option < RecommendedWatcher > ,
5253}
5354
5455/// Categorised dirty-file set, accumulated between debounce flushes.
@@ -97,11 +98,82 @@ impl SessionWatcher {
9798 rt. spawn ( flush_loop (
9899 dirty,
99100 sessions_dir,
100- event_tx,
101+ event_tx. clone ( ) ,
101102 current_session_id,
102103 ) ) ;
103104
104- Ok ( Self { _watcher : watcher } )
105+ // --- config file watcher ---
106+ let config_watcher = Self :: start_config_watcher ( event_tx, & rt) ;
107+
108+ Ok ( Self {
109+ _watcher : watcher,
110+ _config_watcher : config_watcher,
111+ } )
112+ }
113+
114+ /// Start a separate watcher for the config directory (providers.json, models.json).
115+ fn start_config_watcher (
116+ event_tx : async_channel:: Sender < UiEvent > ,
117+ rt : & tokio:: runtime:: Handle ,
118+ ) -> Option < RecommendedWatcher > {
119+ let config_path = llm:: provider_config:: ConfigurationSystem :: providers_config_path ( ) ;
120+ let config_dir = config_path. parent ( ) ?. to_path_buf ( ) ;
121+
122+ if !config_dir. exists ( ) {
123+ debug ! ( "Config directory does not exist yet, skipping config watcher" ) ;
124+ return None ;
125+ }
126+
127+ debug ! ( "Starting config file watcher on {}" , config_dir. display( ) ) ;
128+
129+ let config_dirty = Arc :: new ( Mutex :: new ( false ) ) ;
130+ let config_dirty_for_callback = config_dirty. clone ( ) ;
131+
132+ let mut watcher =
133+ match notify:: recommended_watcher ( move |res : Result < Event , notify:: Error > | match res {
134+ Ok ( event) => {
135+ for path in & event. paths {
136+ if let Some ( name) = path. file_name ( ) . and_then ( |n| n. to_str ( ) ) {
137+ if name == "providers.json" || name == "models.json" {
138+ trace ! ( "Config watcher: {name} changed ({:?})" , event. kind) ;
139+ * config_dirty_for_callback. lock ( ) . unwrap ( ) = true ;
140+ }
141+ }
142+ }
143+ }
144+ Err ( e) => warn ! ( "Config watcher error: {e}" ) ,
145+ } ) {
146+ Ok ( w) => w,
147+ Err ( e) => {
148+ warn ! ( "Failed to create config watcher: {e}" ) ;
149+ return None ;
150+ }
151+ } ;
152+
153+ if let Err ( e) = watcher. watch ( & config_dir, RecursiveMode :: NonRecursive ) {
154+ warn ! ( "Failed to watch config directory: {e}" ) ;
155+ return None ;
156+ }
157+
158+ // Debounce flush for config changes
159+ let event_tx = event_tx. clone ( ) ;
160+ rt. spawn ( async move {
161+ loop {
162+ tokio:: time:: sleep ( DEBOUNCE_DURATION ) . await ;
163+ let dirty = {
164+ let mut flag = config_dirty. lock ( ) . unwrap ( ) ;
165+ let was_dirty = * flag;
166+ * flag = false ;
167+ was_dirty
168+ } ;
169+ if dirty {
170+ debug ! ( "Config watcher flush: emitting ConfigChanged" ) ;
171+ let _ = event_tx. try_send ( UiEvent :: ConfigChanged ) ;
172+ }
173+ }
174+ } ) ;
175+
176+ Some ( watcher)
105177 }
106178}
107179
0 commit comments