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>
This commit is contained in:
2026-02-28 22:35:33 +00:00
parent e2d5a6a7eb
commit 2df4e12d30
31 changed files with 3924 additions and 571 deletions

View File

@@ -47,6 +47,7 @@ impl Database {
workflow_id TEXT NOT NULL REFERENCES workflows(id),
step_order INTEGER NOT NULL,
description TEXT NOT NULL,
command TEXT NOT NULL DEFAULT '',
status TEXT NOT NULL DEFAULT 'pending',
output TEXT NOT NULL DEFAULT ''
)"
@@ -65,6 +66,49 @@ impl Database {
.execute(&self.pool)
.await?;
// Migration: add report column to workflows
let _ = sqlx::query(
"ALTER TABLE workflows ADD COLUMN report TEXT NOT NULL DEFAULT ''"
)
.execute(&self.pool)
.await;
// Migration: add created_at to plan_steps
let _ = sqlx::query(
"ALTER TABLE plan_steps ADD COLUMN created_at TEXT NOT NULL DEFAULT ''"
)
.execute(&self.pool)
.await;
// Migration: add kind to plan_steps ('plan' or 'log')
let _ = sqlx::query(
"ALTER TABLE plan_steps ADD COLUMN kind TEXT NOT NULL DEFAULT 'log'"
)
.execute(&self.pool)
.await;
// Migration: add plan_step_id to plan_steps (log entries reference their parent plan step)
let _ = sqlx::query(
"ALTER TABLE plan_steps ADD COLUMN plan_step_id TEXT NOT NULL DEFAULT ''"
)
.execute(&self.pool)
.await;
sqlx::query(
"CREATE TABLE IF NOT EXISTS timers (
id TEXT PRIMARY KEY,
project_id TEXT NOT NULL REFERENCES projects(id),
name TEXT NOT NULL,
interval_secs INTEGER NOT NULL,
requirement TEXT NOT NULL,
enabled INTEGER NOT NULL DEFAULT 1,
last_run_at TEXT NOT NULL DEFAULT '',
created_at TEXT NOT NULL DEFAULT (datetime('now'))
)"
)
.execute(&self.pool)
.await?;
Ok(())
}
}
@@ -85,6 +129,7 @@ pub struct Workflow {
pub requirement: String,
pub status: String,
pub created_at: String,
pub report: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
@@ -93,8 +138,12 @@ pub struct PlanStep {
pub workflow_id: String,
pub step_order: i32,
pub description: String,
pub command: String,
pub status: String,
pub output: String,
pub created_at: String,
pub kind: String,
pub plan_step_id: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
@@ -104,3 +153,15 @@ pub struct Comment {
pub content: String,
pub created_at: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
pub struct Timer {
pub id: String,
pub project_id: String,
pub name: String,
pub interval_secs: i64,
pub requirement: String,
pub enabled: bool,
pub last_run_at: String,
pub created_at: String,
}