diff --git a/crates/goose/src/scheduler.rs b/crates/goose/src/scheduler.rs index 972534ee72..8ac1c1c640 100644 --- a/crates/goose/src/scheduler.rs +++ b/crates/goose/src/scheduler.rs @@ -462,7 +462,77 @@ impl Scheduler { } } + async fn sync_from_storage(&self) { + if !self.storage_path.exists() { + return; + } + let data = match fs::read_to_string(&self.storage_path) { + Ok(d) => d, + Err(_) => return, + }; + if data.trim().is_empty() { + return; + } + let disk_jobs: Vec = match serde_json::from_str(&data) { + Ok(jobs) => jobs, + Err(_) => return, + }; + + let disk_ids: std::collections::HashSet = + disk_jobs.iter().map(|j| j.id.clone()).collect(); + + let (jobs_to_add, jobs_to_remove): (Vec, Vec<(String, JobId)>) = { + let jobs_guard = self.jobs.lock().await; + let to_add = disk_jobs + .into_iter() + .filter(|j| !jobs_guard.contains_key(&j.id)) + .collect(); + let to_remove = jobs_guard + .iter() + .filter(|(id, (_, j))| !disk_ids.contains(*id) && !j.currently_running) + .map(|(id, (uuid, _))| (id.clone(), *uuid)) + .collect(); + (to_add, to_remove) + }; + + for job in jobs_to_add { + if !Path::new(&job.source).exists() { + tracing::warn!( + "Skipping sync of job '{}': recipe file not found at {}", + job.id, + job.source + ); + continue; + } + let cron_task = match self.create_cron_task(job.clone()) { + Ok(t) => t, + Err(e) => { + tracing::error!( + "Failed to create cron task for '{}' during sync: {}", + job.id, + e + ); + continue; + } + }; + let uuid = match self.tokio_scheduler.add(cron_task).await { + Ok(u) => u, + Err(e) => { + tracing::error!("Failed to register job '{}' during sync: {}", job.id, e); + continue; + } + }; + self.jobs.lock().await.insert(job.id.clone(), (uuid, job)); + } + + for (id, uuid) in jobs_to_remove { + let _ = self.tokio_scheduler.remove(&uuid).await; + self.jobs.lock().await.remove(&id); + } + } + pub async fn list_scheduled_jobs(&self) -> Vec { + self.sync_from_storage().await; self.jobs .lock() .await