458 lines
16 KiB
Rust
458 lines
16 KiB
Rust
use std::collections::HashMap;
|
|
use serde::{Deserialize, Serialize};
|
|
use anyhow::{Result, Context};
|
|
use chrono::{DateTime, Utc, Duration};
|
|
|
|
use crate::config::Config;
|
|
use crate::persona::Persona;
|
|
use crate::transmission::TransmissionController;
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct ScheduledTask {
|
|
pub id: String,
|
|
pub task_type: TaskType,
|
|
pub next_run: DateTime<Utc>,
|
|
pub interval_hours: Option<i64>,
|
|
pub enabled: bool,
|
|
pub last_run: Option<DateTime<Utc>>,
|
|
pub run_count: u32,
|
|
pub max_runs: Option<u32>,
|
|
pub created_at: DateTime<Utc>,
|
|
pub metadata: HashMap<String, String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub enum TaskType {
|
|
DailyMaintenance,
|
|
AutoTransmission,
|
|
RelationshipDecay,
|
|
BreakthroughCheck,
|
|
MaintenanceTransmission,
|
|
Custom(String),
|
|
}
|
|
|
|
impl std::fmt::Display for TaskType {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
match self {
|
|
TaskType::DailyMaintenance => write!(f, "daily_maintenance"),
|
|
TaskType::AutoTransmission => write!(f, "auto_transmission"),
|
|
TaskType::RelationshipDecay => write!(f, "relationship_decay"),
|
|
TaskType::BreakthroughCheck => write!(f, "breakthrough_check"),
|
|
TaskType::MaintenanceTransmission => write!(f, "maintenance_transmission"),
|
|
TaskType::Custom(name) => write!(f, "custom_{}", name),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct TaskExecution {
|
|
pub task_id: String,
|
|
pub execution_time: DateTime<Utc>,
|
|
pub duration_ms: u64,
|
|
pub success: bool,
|
|
pub result: Option<String>,
|
|
pub error: Option<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct AIScheduler {
|
|
config: Config,
|
|
tasks: HashMap<String, ScheduledTask>,
|
|
execution_history: Vec<TaskExecution>,
|
|
last_check: Option<DateTime<Utc>>,
|
|
}
|
|
|
|
impl AIScheduler {
|
|
pub fn new(config: &Config) -> Result<Self> {
|
|
let (tasks, execution_history) = Self::load_scheduler_data(config)?;
|
|
|
|
let mut scheduler = AIScheduler {
|
|
config: config.clone(),
|
|
tasks,
|
|
execution_history,
|
|
last_check: None,
|
|
};
|
|
|
|
// Initialize default tasks if none exist
|
|
if scheduler.tasks.is_empty() {
|
|
scheduler.create_default_tasks()?;
|
|
}
|
|
|
|
Ok(scheduler)
|
|
}
|
|
|
|
pub async fn run_scheduled_tasks(&mut self, persona: &mut Persona, transmission_controller: &mut TransmissionController) -> Result<Vec<TaskExecution>> {
|
|
let now = Utc::now();
|
|
let mut executions = Vec::new();
|
|
|
|
// Find tasks that are due to run
|
|
let due_task_ids: Vec<String> = self.tasks
|
|
.iter()
|
|
.filter(|(_, task)| task.enabled && task.next_run <= now)
|
|
.filter(|(_, task)| {
|
|
// Check if task hasn't exceeded max runs
|
|
if let Some(max_runs) = task.max_runs {
|
|
task.run_count < max_runs
|
|
} else {
|
|
true
|
|
}
|
|
})
|
|
.map(|(id, _)| id.clone())
|
|
.collect();
|
|
|
|
for task_id in due_task_ids {
|
|
let execution = self.execute_task(&task_id, persona, transmission_controller).await?;
|
|
executions.push(execution);
|
|
}
|
|
|
|
self.last_check = Some(now);
|
|
self.save_scheduler_data()?;
|
|
|
|
Ok(executions)
|
|
}
|
|
|
|
async fn execute_task(&mut self, task_id: &str, persona: &mut Persona, transmission_controller: &mut TransmissionController) -> Result<TaskExecution> {
|
|
let start_time = Utc::now();
|
|
let mut execution = TaskExecution {
|
|
task_id: task_id.to_string(),
|
|
execution_time: start_time,
|
|
duration_ms: 0,
|
|
success: false,
|
|
result: None,
|
|
error: None,
|
|
};
|
|
|
|
// Get task type without borrowing mutably
|
|
let task_type = {
|
|
let task = self.tasks.get(task_id)
|
|
.ok_or_else(|| anyhow::anyhow!("Task not found: {}", task_id))?;
|
|
task.task_type.clone()
|
|
};
|
|
|
|
// Execute the task based on its type
|
|
let result = match &task_type {
|
|
TaskType::DailyMaintenance => self.execute_daily_maintenance(persona, transmission_controller).await,
|
|
TaskType::AutoTransmission => self.execute_auto_transmission(persona, transmission_controller).await,
|
|
TaskType::RelationshipDecay => self.execute_relationship_decay(persona).await,
|
|
TaskType::BreakthroughCheck => self.execute_breakthrough_check(persona, transmission_controller).await,
|
|
TaskType::MaintenanceTransmission => self.execute_maintenance_transmission(persona, transmission_controller).await,
|
|
TaskType::Custom(name) => self.execute_custom_task(name, persona, transmission_controller).await,
|
|
};
|
|
|
|
let end_time = Utc::now();
|
|
execution.duration_ms = (end_time - start_time).num_milliseconds() as u64;
|
|
|
|
// Now update the task state with mutable borrow
|
|
match result {
|
|
Ok(message) => {
|
|
execution.success = true;
|
|
execution.result = Some(message);
|
|
|
|
// Update task state
|
|
if let Some(task) = self.tasks.get_mut(task_id) {
|
|
task.last_run = Some(start_time);
|
|
task.run_count += 1;
|
|
|
|
// Schedule next run if recurring
|
|
if let Some(interval_hours) = task.interval_hours {
|
|
task.next_run = start_time + Duration::hours(interval_hours);
|
|
} else {
|
|
// One-time task, disable it
|
|
task.enabled = false;
|
|
}
|
|
}
|
|
}
|
|
Err(e) => {
|
|
execution.error = Some(e.to_string());
|
|
|
|
// For failed tasks, retry in a shorter interval
|
|
if let Some(task) = self.tasks.get_mut(task_id) {
|
|
if task.interval_hours.is_some() {
|
|
task.next_run = start_time + Duration::minutes(15); // Retry in 15 minutes
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
self.execution_history.push(execution.clone());
|
|
|
|
// Keep only recent execution history (last 1000 executions)
|
|
if self.execution_history.len() > 1000 {
|
|
self.execution_history.drain(..self.execution_history.len() - 1000);
|
|
}
|
|
|
|
Ok(execution)
|
|
}
|
|
|
|
async fn execute_daily_maintenance(&self, persona: &mut Persona, transmission_controller: &mut TransmissionController) -> Result<String> {
|
|
// Run daily maintenance
|
|
persona.daily_maintenance()?;
|
|
|
|
// Check for maintenance transmissions
|
|
let transmissions = transmission_controller.check_maintenance_transmissions(persona).await?;
|
|
|
|
Ok(format!("Daily maintenance completed. {} maintenance transmissions sent.", transmissions.len()))
|
|
}
|
|
|
|
async fn execute_auto_transmission(&self, _persona: &mut Persona, transmission_controller: &mut TransmissionController) -> Result<String> {
|
|
let transmissions = transmission_controller.check_autonomous_transmissions(_persona).await?;
|
|
Ok(format!("Autonomous transmission check completed. {} transmissions sent.", transmissions.len()))
|
|
}
|
|
|
|
async fn execute_relationship_decay(&self, persona: &mut Persona) -> Result<String> {
|
|
persona.daily_maintenance()?;
|
|
Ok("Relationship time decay applied.".to_string())
|
|
}
|
|
|
|
async fn execute_breakthrough_check(&self, persona: &mut Persona, transmission_controller: &mut TransmissionController) -> Result<String> {
|
|
let transmissions = transmission_controller.check_breakthrough_transmissions(persona).await?;
|
|
Ok(format!("Breakthrough check completed. {} transmissions sent.", transmissions.len()))
|
|
}
|
|
|
|
async fn execute_maintenance_transmission(&self, persona: &mut Persona, transmission_controller: &mut TransmissionController) -> Result<String> {
|
|
let transmissions = transmission_controller.check_maintenance_transmissions(persona).await?;
|
|
Ok(format!("Maintenance transmission check completed. {} transmissions sent.", transmissions.len()))
|
|
}
|
|
|
|
async fn execute_custom_task(&self, _name: &str, _persona: &mut Persona, _transmission_controller: &mut TransmissionController) -> Result<String> {
|
|
// Placeholder for custom task execution
|
|
Ok("Custom task executed.".to_string())
|
|
}
|
|
|
|
pub fn create_task(&mut self, task_type: TaskType, next_run: DateTime<Utc>, interval_hours: Option<i64>) -> Result<String> {
|
|
let task_id = uuid::Uuid::new_v4().to_string();
|
|
let now = Utc::now();
|
|
|
|
let task = ScheduledTask {
|
|
id: task_id.clone(),
|
|
task_type,
|
|
next_run,
|
|
interval_hours,
|
|
enabled: true,
|
|
last_run: None,
|
|
run_count: 0,
|
|
max_runs: None,
|
|
created_at: now,
|
|
metadata: HashMap::new(),
|
|
};
|
|
|
|
self.tasks.insert(task_id.clone(), task);
|
|
self.save_scheduler_data()?;
|
|
|
|
Ok(task_id)
|
|
}
|
|
|
|
pub fn enable_task(&mut self, task_id: &str) -> Result<()> {
|
|
if let Some(task) = self.tasks.get_mut(task_id) {
|
|
task.enabled = true;
|
|
self.save_scheduler_data()?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub fn disable_task(&mut self, task_id: &str) -> Result<()> {
|
|
if let Some(task) = self.tasks.get_mut(task_id) {
|
|
task.enabled = false;
|
|
self.save_scheduler_data()?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub fn delete_task(&mut self, task_id: &str) -> Result<()> {
|
|
self.tasks.remove(task_id);
|
|
self.save_scheduler_data()?;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn get_task(&self, task_id: &str) -> Option<&ScheduledTask> {
|
|
self.tasks.get(task_id)
|
|
}
|
|
|
|
pub fn get_tasks(&self) -> &HashMap<String, ScheduledTask> {
|
|
&self.tasks
|
|
}
|
|
|
|
pub fn get_due_tasks(&self) -> Vec<&ScheduledTask> {
|
|
let now = Utc::now();
|
|
self.tasks
|
|
.values()
|
|
.filter(|task| task.enabled && task.next_run <= now)
|
|
.collect()
|
|
}
|
|
|
|
pub fn get_execution_history(&self, limit: Option<usize>) -> Vec<&TaskExecution> {
|
|
let mut executions: Vec<_> = self.execution_history.iter().collect();
|
|
executions.sort_by(|a, b| b.execution_time.cmp(&a.execution_time));
|
|
|
|
match limit {
|
|
Some(limit) => executions.into_iter().take(limit).collect(),
|
|
None => executions,
|
|
}
|
|
}
|
|
|
|
pub fn get_scheduler_stats(&self) -> SchedulerStats {
|
|
let total_tasks = self.tasks.len();
|
|
let enabled_tasks = self.tasks.values().filter(|task| task.enabled).count();
|
|
let due_tasks = self.get_due_tasks().len();
|
|
|
|
let total_executions = self.execution_history.len();
|
|
let successful_executions = self.execution_history.iter()
|
|
.filter(|exec| exec.success)
|
|
.count();
|
|
|
|
let today = Utc::now().date_naive();
|
|
let today_executions = self.execution_history.iter()
|
|
.filter(|exec| exec.execution_time.date_naive() == today)
|
|
.count();
|
|
|
|
let avg_duration = if total_executions > 0 {
|
|
self.execution_history.iter()
|
|
.map(|exec| exec.duration_ms)
|
|
.sum::<u64>() as f64 / total_executions as f64
|
|
} else {
|
|
0.0
|
|
};
|
|
|
|
SchedulerStats {
|
|
total_tasks,
|
|
enabled_tasks,
|
|
due_tasks,
|
|
total_executions,
|
|
successful_executions,
|
|
today_executions,
|
|
success_rate: if total_executions > 0 {
|
|
successful_executions as f64 / total_executions as f64
|
|
} else {
|
|
0.0
|
|
},
|
|
avg_duration_ms: avg_duration,
|
|
}
|
|
}
|
|
|
|
fn create_default_tasks(&mut self) -> Result<()> {
|
|
let now = Utc::now();
|
|
|
|
// Daily maintenance task - run every day at 3 AM
|
|
let mut daily_maintenance_time = now.date_naive().and_hms_opt(3, 0, 0).unwrap().and_utc();
|
|
if daily_maintenance_time <= now {
|
|
daily_maintenance_time = daily_maintenance_time + Duration::days(1);
|
|
}
|
|
|
|
self.create_task(
|
|
TaskType::DailyMaintenance,
|
|
daily_maintenance_time,
|
|
Some(24), // 24 hours = 1 day
|
|
)?;
|
|
|
|
// Auto transmission check - every 4 hours
|
|
self.create_task(
|
|
TaskType::AutoTransmission,
|
|
now + Duration::hours(1),
|
|
Some(4),
|
|
)?;
|
|
|
|
// Breakthrough check - every 2 hours
|
|
self.create_task(
|
|
TaskType::BreakthroughCheck,
|
|
now + Duration::minutes(30),
|
|
Some(2),
|
|
)?;
|
|
|
|
// Maintenance transmission - once per day
|
|
let mut maintenance_time = now.date_naive().and_hms_opt(12, 0, 0).unwrap().and_utc();
|
|
if maintenance_time <= now {
|
|
maintenance_time = maintenance_time + Duration::days(1);
|
|
}
|
|
|
|
self.create_task(
|
|
TaskType::MaintenanceTransmission,
|
|
maintenance_time,
|
|
Some(24), // 24 hours = 1 day
|
|
)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn load_scheduler_data(config: &Config) -> Result<(HashMap<String, ScheduledTask>, Vec<TaskExecution>)> {
|
|
let tasks_file = config.scheduler_tasks_file();
|
|
let history_file = config.scheduler_history_file();
|
|
|
|
let tasks = if tasks_file.exists() {
|
|
let content = std::fs::read_to_string(tasks_file)
|
|
.context("Failed to read scheduler tasks file")?;
|
|
serde_json::from_str(&content)
|
|
.context("Failed to parse scheduler tasks file")?
|
|
} else {
|
|
HashMap::new()
|
|
};
|
|
|
|
let history = if history_file.exists() {
|
|
let content = std::fs::read_to_string(history_file)
|
|
.context("Failed to read scheduler history file")?;
|
|
serde_json::from_str(&content)
|
|
.context("Failed to parse scheduler history file")?
|
|
} else {
|
|
Vec::new()
|
|
};
|
|
|
|
Ok((tasks, history))
|
|
}
|
|
|
|
fn save_scheduler_data(&self) -> Result<()> {
|
|
// Save tasks
|
|
let tasks_content = serde_json::to_string_pretty(&self.tasks)
|
|
.context("Failed to serialize scheduler tasks")?;
|
|
std::fs::write(&self.config.scheduler_tasks_file(), tasks_content)
|
|
.context("Failed to write scheduler tasks file")?;
|
|
|
|
// Save execution history
|
|
let history_content = serde_json::to_string_pretty(&self.execution_history)
|
|
.context("Failed to serialize scheduler history")?;
|
|
std::fs::write(&self.config.scheduler_history_file(), history_content)
|
|
.context("Failed to write scheduler history file")?;
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
// Type alias for compatibility with CLI interface
|
|
pub type Scheduler = AIScheduler;
|
|
|
|
impl Scheduler {
|
|
pub fn list_tasks(&self) -> Result<Vec<ScheduledTaskInfo>> {
|
|
let tasks: Vec<ScheduledTaskInfo> = self.tasks
|
|
.values()
|
|
.map(|task| ScheduledTaskInfo {
|
|
name: task.task_type.to_string(),
|
|
schedule: match task.interval_hours {
|
|
Some(hours) => format!("Every {} hours", hours),
|
|
None => "One-time".to_string(),
|
|
},
|
|
next_run: task.next_run,
|
|
enabled: task.enabled,
|
|
})
|
|
.collect();
|
|
|
|
Ok(tasks)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct SchedulerStats {
|
|
pub total_tasks: usize,
|
|
pub enabled_tasks: usize,
|
|
pub due_tasks: usize,
|
|
pub total_executions: usize,
|
|
pub successful_executions: usize,
|
|
pub today_executions: usize,
|
|
pub success_rate: f64,
|
|
pub avg_duration_ms: f64,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct ScheduledTaskInfo {
|
|
pub name: String,
|
|
pub schedule: String,
|
|
pub next_run: DateTime<Utc>,
|
|
pub enabled: bool,
|
|
} |