tori/src/timer.rs
Fam Zheng 2df4e12d30 Agent loop state machine refactor, unified LLM interface, and UI improvements
- Rewrite agent loop as Planning→Executing(N)→Completed state machine with
  per-step context isolation to prevent token explosion
- Split tools and prompts by phase (planning vs execution)
- Add advance_step/save_memo tools for step transitions and cross-step memory
- Unify LLM interface: remove duplicate types, single chat_with_tools path
- Add UTF-8 safe truncation (truncate_str) to prevent panics on Chinese text
- Extract CreateForm component, add auto-scroll to execution log
- Add report generation with app access URL, non-blocking title generation
- Add timer system, file serving, app proxy, exec module
- Update Dockerfile with uv, deployment config

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 22:35:33 +00:00

76 lines
2.2 KiB
Rust

use std::sync::Arc;
use sqlx::sqlite::SqlitePool;
use crate::agent::{AgentEvent, AgentManager};
use crate::db::Timer;
pub fn start_timer_runner(pool: SqlitePool, agent_mgr: Arc<AgentManager>) {
tokio::spawn(timer_loop(pool, agent_mgr));
}
async fn timer_loop(pool: SqlitePool, agent_mgr: Arc<AgentManager>) {
tracing::info!("Timer runner started");
loop {
tokio::time::sleep(tokio::time::Duration::from_secs(30)).await;
if let Err(e) = check_timers(&pool, &agent_mgr).await {
tracing::error!("Timer check error: {}", e);
}
}
}
async fn check_timers(pool: &SqlitePool, agent_mgr: &Arc<AgentManager>) -> anyhow::Result<()> {
let timers = sqlx::query_as::<_, Timer>(
"SELECT * FROM timers WHERE enabled = 1"
)
.fetch_all(pool)
.await?;
let now = chrono::Utc::now();
for timer in timers {
let due = if timer.last_run_at.is_empty() {
true
} else if let Ok(last) = chrono::NaiveDateTime::parse_from_str(&timer.last_run_at, "%Y-%m-%d %H:%M:%S") {
let last_utc = last.and_utc();
let elapsed = now.signed_duration_since(last_utc).num_seconds();
elapsed >= timer.interval_secs
} else {
true
};
if !due {
continue;
}
tracing::info!("Timer '{}' fired for project {}", timer.name, timer.project_id);
// Update last_run_at
let now_str = now.format("%Y-%m-%d %H:%M:%S").to_string();
let _ = sqlx::query("UPDATE timers SET last_run_at = ? WHERE id = ?")
.bind(&now_str)
.bind(&timer.id)
.execute(pool)
.await;
// Create a workflow for this timer
let workflow_id = uuid::Uuid::new_v4().to_string();
let _ = sqlx::query(
"INSERT INTO workflows (id, project_id, requirement) VALUES (?, ?, ?)"
)
.bind(&workflow_id)
.bind(&timer.project_id)
.bind(&timer.requirement)
.execute(pool)
.await;
// Send event to agent
agent_mgr.send_event(&timer.project_id, AgentEvent::NewRequirement {
workflow_id,
requirement: timer.requirement.clone(),
}).await;
}
Ok(())
}