Tori: AI agent workflow manager - initial implementation

Rust (Axum) + Vue 3 + SQLite. Features:
- Project CRUD REST API with proper error handling
- Per-project agent loop (mpsc + broadcast channels)
- LLM-driven plan generation and replan on user feedback
- SSH command execution with status streaming
- WebSocket real-time updates to frontend
- Four-zone UI: requirement, plan (left), execution (right), comment

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-28 10:36:50 +00:00
parent 1122ab27dd
commit 7edbbee471
43 changed files with 7164 additions and 83 deletions

90
src/main.rs Normal file
View File

@@ -0,0 +1,90 @@
mod api;
mod agent;
mod db;
mod llm;
mod ssh;
mod ws;
use std::sync::Arc;
use axum::Router;
use tower_http::cors::CorsLayer;
use tower_http::services::ServeDir;
pub struct AppState {
pub db: db::Database,
pub config: Config,
pub agent_mgr: Arc<agent::AgentManager>,
}
#[derive(Debug, Clone, serde::Deserialize)]
pub struct Config {
pub llm: LlmConfig,
pub ssh: SshConfig,
pub server: ServerConfig,
pub database: DatabaseConfig,
}
#[derive(Debug, Clone, serde::Deserialize)]
pub struct LlmConfig {
pub base_url: String,
pub api_key: String,
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,
pub port: u16,
}
#[derive(Debug, Clone, serde::Deserialize)]
pub struct DatabaseConfig {
pub path: String,
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
tracing_subscriber::fmt()
.with_env_filter("tori=debug,tower_http=debug")
.init();
let config_str = std::fs::read_to_string("config.yaml")
.expect("Failed to read config.yaml");
let config: Config = serde_yaml::from_str(&config_str)
.expect("Failed to parse config.yaml");
let database = db::Database::new(&config.database.path).await?;
database.migrate().await?;
let agent_mgr = agent::AgentManager::new(
database.pool.clone(),
config.llm.clone(),
config.ssh.clone(),
);
let state = Arc::new(AppState {
db: database,
config: config.clone(),
agent_mgr: agent_mgr.clone(),
});
let app = Router::new()
.nest("/api", api::router(state))
.nest("/ws", ws::router(agent_mgr))
.fallback_service(ServeDir::new("web/dist"))
.layer(CorsLayer::permissive());
let addr = format!("{}:{}", config.server.host, config.server.port);
tracing::info!("Tori server listening on {}", addr);
let listener = tokio::net::TcpListener::bind(&addr).await?;
axum::serve(listener, app).await?;
Ok(())
}