Revert "Mnovich/temporal scheduler (#2745)" (#2839)

This commit is contained in:
Max Novich
2025-06-09 15:41:46 -07:00
committed by GitHub
parent e6f89cde76
commit 996f727ec5
35 changed files with 172 additions and 4896 deletions
+2 -25
View File
@@ -11,8 +11,7 @@ use crate::commands::project::{handle_project_default, handle_projects_interacti
use crate::commands::recipe::{handle_deeplink, handle_validate};
// Import the new handlers from commands::schedule
use crate::commands::schedule::{
handle_schedule_add, handle_schedule_cron_help, handle_schedule_list, handle_schedule_remove,
handle_schedule_run_now, handle_schedule_services_status, handle_schedule_services_stop,
handle_schedule_add, handle_schedule_list, handle_schedule_remove, handle_schedule_run_now,
handle_schedule_sessions,
};
use crate::commands::session::{handle_session_list, handle_session_remove};
@@ -124,11 +123,7 @@ enum SchedulerCommand {
Add {
#[arg(long, help = "Unique ID for the job")]
id: String,
#[arg(
long,
help = "Cron expression for the schedule",
long_help = "Cron expression for when to run the job. Examples:\n '0 * * * *' - Every hour at minute 0\n '0 */2 * * *' - Every 2 hours\n '@hourly' - Every hour (shorthand)\n '0 9 * * *' - Every day at 9:00 AM\n '0 9 * * 1' - Every Monday at 9:00 AM\n '0 0 1 * *' - First day of every month at midnight"
)]
#[arg(long, help = "Cron string for the schedule (e.g., '0 0 * * * *')")]
cron: String,
#[arg(
long,
@@ -160,15 +155,6 @@ enum SchedulerCommand {
#[arg(long, help = "ID of the schedule to run")] // Explicitly make it --id
id: String,
},
/// Check status of Temporal services (temporal scheduler only)
#[command(about = "Check status of Temporal services")]
ServicesStatus {},
/// Stop Temporal services (temporal scheduler only)
#[command(about = "Stop Temporal services")]
ServicesStop {},
/// Show cron expression examples and help
#[command(about = "Show cron expression examples and help")]
CronHelp {},
}
#[derive(Subcommand)]
@@ -782,15 +768,6 @@ pub async fn cli() -> Result<()> {
// New arm
handle_schedule_run_now(id).await?;
}
SchedulerCommand::ServicesStatus {} => {
handle_schedule_services_status().await?;
}
SchedulerCommand::ServicesStop {} => {
handle_schedule_services_stop().await?;
}
SchedulerCommand::CronHelp {} => {
handle_schedule_cron_help().await?;
}
}
return Ok(());
}
+8 -208
View File
@@ -1,11 +1,9 @@
use anyhow::{bail, Context, Result};
use base64::engine::{general_purpose::STANDARD as BASE64_STANDARD, Engine};
use goose::scheduler::{
get_default_scheduled_recipes_dir, get_default_scheduler_storage_path, ScheduledJob,
get_default_scheduled_recipes_dir, get_default_scheduler_storage_path, ScheduledJob, Scheduler,
SchedulerError,
};
use goose::scheduler_factory::SchedulerFactory;
use goose::temporal_scheduler::TemporalScheduler;
use std::path::Path;
// Base64 decoding function - might be needed if recipe_source_arg can be base64
@@ -17,64 +15,6 @@ async fn _decode_base64_recipe(source: &str) -> Result<String> {
String::from_utf8(bytes).with_context(|| "Decoded Base64 recipe source is not valid UTF-8.")
}
fn validate_cron_expression(cron: &str) -> Result<()> {
// Basic validation and helpful suggestions
if cron.trim().is_empty() {
bail!("Cron expression cannot be empty");
}
// Check for common mistakes and provide helpful suggestions
let parts: Vec<&str> = cron.split_whitespace().collect();
match parts.len() {
5 => {
// Standard 5-field cron (minute hour day month weekday)
println!("✅ Using standard 5-field cron format: {}", cron);
}
6 => {
// 6-field cron with seconds (second minute hour day month weekday)
println!("✅ Using 6-field cron format with seconds: {}", cron);
}
1 if cron.starts_with('@') => {
// Shorthand expressions like @hourly, @daily, etc.
let valid_shorthands = [
"@yearly",
"@annually",
"@monthly",
"@weekly",
"@daily",
"@midnight",
"@hourly",
];
if valid_shorthands.contains(&cron) {
println!("✅ Using cron shorthand: {}", cron);
} else {
println!(
"⚠️ Unknown cron shorthand '{}'. Valid options: {}",
cron,
valid_shorthands.join(", ")
);
}
}
_ => {
println!("⚠️ Unusual cron format detected: '{}'", cron);
println!(" Common formats:");
println!(" - 5 fields: '0 * * * *' (minute hour day month weekday)");
println!(" - 6 fields: '0 0 * * * *' (second minute hour day month weekday)");
println!(" - Shorthand: '@hourly', '@daily', '@weekly', '@monthly'");
}
}
// Provide examples for common scheduling needs
if cron == "* * * * *" {
println!("⚠️ This will run every minute! Did you mean:");
println!(" - '0 * * * *' for every hour?");
println!(" - '0 0 * * *' for every day?");
}
Ok(())
}
pub async fn handle_schedule_add(
id: String,
cron: String,
@@ -85,9 +25,6 @@ pub async fn handle_schedule_add(
id, cron, recipe_source_arg
);
// Validate cron expression and provide helpful feedback
validate_cron_expression(&cron)?;
// The Scheduler's add_scheduled_job will handle copying the recipe from recipe_source_arg
// to its internal storage and validating the path.
let job = ScheduledJob {
@@ -103,7 +40,7 @@ pub async fn handle_schedule_add(
let scheduler_storage_path =
get_default_scheduler_storage_path().context("Failed to get scheduler storage path")?;
let scheduler = SchedulerFactory::create(scheduler_storage_path)
let scheduler = Scheduler::new(scheduler_storage_path)
.await
.context("Failed to initialize scheduler")?;
@@ -148,28 +85,19 @@ pub async fn handle_schedule_add(
pub async fn handle_schedule_list() -> Result<()> {
let scheduler_storage_path =
get_default_scheduler_storage_path().context("Failed to get scheduler storage path")?;
let scheduler = SchedulerFactory::create(scheduler_storage_path)
let scheduler = Scheduler::new(scheduler_storage_path)
.await
.context("Failed to initialize scheduler")?;
let jobs = scheduler.list_scheduled_jobs().await?;
let jobs = scheduler.list_scheduled_jobs().await;
if jobs.is_empty() {
println!("No scheduled jobs found.");
} else {
println!("Scheduled Jobs:");
for job in jobs {
let status = if job.currently_running {
"🟢 RUNNING"
} else if job.paused {
"⏸️ PAUSED"
} else {
"⏹️ IDLE"
};
println!(
"- ID: {}\n Status: {}\n Cron: {}\n Recipe Source (in store): {}\n Last Run: {}",
"- ID: {}\n Cron: {}\n Recipe Source (in store): {}\n Last Run: {}",
job.id,
status,
job.cron,
job.source, // This source is now the path within scheduled_recipes_dir
job.last_run
@@ -183,7 +111,7 @@ pub async fn handle_schedule_list() -> Result<()> {
pub async fn handle_schedule_remove(id: String) -> Result<()> {
let scheduler_storage_path =
get_default_scheduler_storage_path().context("Failed to get scheduler storage path")?;
let scheduler = SchedulerFactory::create(scheduler_storage_path)
let scheduler = Scheduler::new(scheduler_storage_path)
.await
.context("Failed to initialize scheduler")?;
@@ -205,7 +133,7 @@ pub async fn handle_schedule_remove(id: String) -> Result<()> {
pub async fn handle_schedule_sessions(id: String, limit: Option<u32>) -> Result<()> {
let scheduler_storage_path =
get_default_scheduler_storage_path().context("Failed to get scheduler storage path")?;
let scheduler = SchedulerFactory::create(scheduler_storage_path)
let scheduler = Scheduler::new(scheduler_storage_path)
.await
.context("Failed to initialize scheduler")?;
@@ -238,7 +166,7 @@ pub async fn handle_schedule_sessions(id: String, limit: Option<u32>) -> Result<
pub async fn handle_schedule_run_now(id: String) -> Result<()> {
let scheduler_storage_path =
get_default_scheduler_storage_path().context("Failed to get scheduler storage path")?;
let scheduler = SchedulerFactory::create(scheduler_storage_path)
let scheduler = Scheduler::new(scheduler_storage_path)
.await
.context("Failed to initialize scheduler")?;
@@ -258,131 +186,3 @@ pub async fn handle_schedule_run_now(id: String) -> Result<()> {
}
Ok(())
}
pub async fn handle_schedule_services_status() -> Result<()> {
// Check if we're using temporal scheduler
let scheduler_type =
std::env::var("GOOSE_SCHEDULER_TYPE").unwrap_or_else(|_| "temporal".to_string());
if scheduler_type != "temporal" {
println!("Service management is only available for temporal scheduler.");
println!("Set GOOSE_SCHEDULER_TYPE=temporal to use Temporal services.");
return Ok(());
}
println!("Checking Temporal services status...");
// Create a temporary TemporalScheduler to check status
match TemporalScheduler::new().await {
Ok(scheduler) => {
let info = scheduler.get_service_info().await;
println!("{}", info);
}
Err(e) => {
println!("Failed to check services: {}", e);
println!("\nThis might mean:");
println!("- Temporal CLI is not installed");
println!("- Go service binary is not built");
println!("- Services are not running");
}
}
Ok(())
}
pub async fn handle_schedule_services_stop() -> Result<()> {
// Check if we're using temporal scheduler
let scheduler_type =
std::env::var("GOOSE_SCHEDULER_TYPE").unwrap_or_else(|_| "temporal".to_string());
if scheduler_type != "temporal" {
println!("Service management is only available for temporal scheduler.");
println!("Set GOOSE_SCHEDULER_TYPE=temporal to use Temporal services.");
return Ok(());
}
println!("Stopping Temporal services...");
// Create a temporary TemporalScheduler to stop services
match TemporalScheduler::new().await {
Ok(scheduler) => match scheduler.stop_services().await {
Ok(result) => {
println!("{}", result);
println!("\nNote: Services were running independently and have been stopped.");
println!("They will be automatically restarted when needed.");
}
Err(e) => {
println!("Failed to stop services: {}", e);
}
},
Err(e) => {
println!("Failed to initialize scheduler: {}", e);
println!("Services may not be running or may have already been stopped.");
}
}
Ok(())
}
pub async fn handle_schedule_cron_help() -> Result<()> {
println!("📅 Cron Expression Guide for Goose Scheduler");
println!("===========================================\\n");
println!("🕐 HOURLY SCHEDULES (Most Common Request):");
println!(" 0 * * * * - Every hour at minute 0 (e.g., 1:00, 2:00, 3:00...)");
println!(" 30 * * * * - Every hour at minute 30 (e.g., 1:30, 2:30, 3:30...)");
println!(" 0 */2 * * * - Every 2 hours at minute 0 (e.g., 2:00, 4:00, 6:00...)");
println!(" 0 */3 * * * - Every 3 hours at minute 0 (e.g., 3:00, 6:00, 9:00...)");
println!(" @hourly - Every hour (same as \"0 * * * *\")\\n");
println!("📅 DAILY SCHEDULES:");
println!(" 0 9 * * * - Every day at 9:00 AM");
println!(" 30 14 * * * - Every day at 2:30 PM");
println!(" 0 0 * * * - Every day at midnight");
println!(" @daily - Every day at midnight\\n");
println!("📆 WEEKLY SCHEDULES:");
println!(" 0 9 * * 1 - Every Monday at 9:00 AM");
println!(" 0 17 * * 5 - Every Friday at 5:00 PM");
println!(" 0 0 * * 0 - Every Sunday at midnight");
println!(" @weekly - Every Sunday at midnight\\n");
println!("🗓️ MONTHLY SCHEDULES:");
println!(" 0 9 1 * * - First day of every month at 9:00 AM");
println!(" 0 0 15 * * - 15th of every month at midnight");
println!(" @monthly - First day of every month at midnight\\n");
println!("📝 CRON FORMAT:");
println!(" Standard 5-field: minute hour day month weekday");
println!(" ┌───────────── minute (0 - 59)");
println!(" │ ┌─────────── hour (0 - 23)");
println!(" │ │ ┌───────── day of month (1 - 31)");
println!(" │ │ │ ┌─────── month (1 - 12)");
println!(" │ │ │ │ ┌───── day of week (0 - 7, Sunday = 0 or 7)");
println!(" │ │ │ │ │");
println!(" * * * * *\\n");
println!("🔧 SPECIAL CHARACTERS:");
println!(" * - Any value (every minute, hour, day, etc.)");
println!(" */n - Every nth interval (*/5 = every 5 minutes)");
println!(" n-m - Range (1-5 = 1,2,3,4,5)");
println!(" n,m - List (1,3,5 = 1 or 3 or 5)\\n");
println!("⚡ SHORTHAND EXPRESSIONS:");
println!(" @yearly - Once a year (0 0 1 1 *)");
println!(" @monthly - Once a month (0 0 1 * *)");
println!(" @weekly - Once a week (0 0 * * 0)");
println!(" @daily - Once a day (0 0 * * *)");
println!(" @hourly - Once an hour (0 * * * *)\\n");
println!("💡 EXAMPLES:");
println!(
" goose schedule add --id hourly-report --cron \"0 * * * *\" --recipe-source report.yaml"
);
println!(
" goose schedule add --id daily-backup --cron \"@daily\" --recipe-source backup.yaml"
);
println!(" goose schedule add --id weekly-summary --cron \"0 9 * * 1\" --recipe-source summary.yaml");
Ok(())
}