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

@@ -2,13 +2,14 @@ mod api;
mod agent;
mod db;
mod llm;
mod ssh;
mod exec;
mod timer;
mod ws;
use std::sync::Arc;
use axum::Router;
use tower_http::cors::CorsLayer;
use tower_http::services::ServeDir;
use tower_http::services::{ServeDir, ServeFile};
pub struct AppState {
pub db: db::Database,
@@ -19,7 +20,6 @@ pub struct AppState {
#[derive(Debug, Clone, serde::Deserialize)]
pub struct Config {
pub llm: LlmConfig,
pub ssh: SshConfig,
pub server: ServerConfig,
pub database: DatabaseConfig,
}
@@ -31,13 +31,6 @@ pub struct LlmConfig {
pub model: String,
}
#[derive(Debug, Clone, serde::Deserialize)]
pub struct SshConfig {
pub host: String,
pub user: String,
pub key_path: String,
}
#[derive(Debug, Clone, serde::Deserialize)]
pub struct ServerConfig {
pub host: String,
@@ -66,9 +59,10 @@ async fn main() -> anyhow::Result<()> {
let agent_mgr = agent::AgentManager::new(
database.pool.clone(),
config.llm.clone(),
config.ssh.clone(),
);
timer::start_timer_runner(database.pool.clone(), agent_mgr.clone());
let state = Arc::new(AppState {
db: database,
config: config.clone(),
@@ -78,7 +72,7 @@ async fn main() -> anyhow::Result<()> {
let app = Router::new()
.nest("/api", api::router(state))
.nest("/ws", ws::router(agent_mgr))
.fallback_service(ServeDir::new("web/dist"))
.fallback_service(ServeDir::new("web/dist").fallback(ServeFile::new("web/dist/index.html")))
.layer(CorsLayer::permissive());
let addr = format!("{}:{}", config.server.host, config.server.port);