- 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>
76 lines
2.2 KiB
Rust
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(())
|
|
}
|