feat(tui): implement agent turn engine for background processing and enhance input handling
This commit is contained in:
@@ -4,13 +4,14 @@
|
||||
//! schema for each one. Standalone CLI tool invoked as `cargo run --bin migrate`.
|
||||
|
||||
use std::path::Path;
|
||||
use tracing;
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
let store = zesdex_domain::core::Store::new();
|
||||
let sessions_dir = store.base_dir.join("sessions");
|
||||
|
||||
if !sessions_dir.exists() {
|
||||
eprintln!("No sessions directory found, nothing to migrate");
|
||||
tracing::info!("No sessions directory found, nothing to migrate");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -27,16 +28,16 @@ fn main() -> anyhow::Result<()> {
|
||||
match migrate_session_msglog(&path) {
|
||||
Ok(_) => {
|
||||
migrated += 1;
|
||||
eprintln!("Migrated session: {:?}", path.file_name());
|
||||
tracing::info!("Migrated session: {:?}", path.file_name());
|
||||
}
|
||||
Err(e) => {
|
||||
failed += 1;
|
||||
eprintln!("Failed to migrate session {:?}: {e}", path.file_name());
|
||||
tracing::error!("Failed to migrate session {:?}: {e}", path.file_name());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
eprintln!("Migration complete: {migrated} succeeded, {failed} failed");
|
||||
tracing::info!("Migration complete: {migrated} succeeded, {failed} failed");
|
||||
if failed > 0 {
|
||||
anyhow::bail!("{failed} session(s) failed to migrate");
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
//! configuration files plus a seed session for development/testing.
|
||||
//! Invoked as `cargo run --bin seed`.
|
||||
|
||||
use tracing;
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
let store = zesdex_domain::core::Store::new();
|
||||
store.ensure_dirs()?;
|
||||
@@ -18,9 +20,9 @@ fn main() -> anyhow::Result<()> {
|
||||
let f = std::fs::File::open(&tmp)?;
|
||||
f.sync_all()?;
|
||||
std::fs::rename(&tmp, settings_path)?;
|
||||
println!("Default settings created");
|
||||
tracing::info!("Default settings created");
|
||||
} else {
|
||||
println!("Settings already exist, skipping");
|
||||
tracing::info!("Settings already exist, skipping");
|
||||
}
|
||||
|
||||
// Create default app config if not present
|
||||
@@ -33,15 +35,15 @@ fn main() -> anyhow::Result<()> {
|
||||
let f = std::fs::File::open(&tmp)?;
|
||||
f.sync_all()?;
|
||||
std::fs::rename(&tmp, config_path)?;
|
||||
println!("Default app_config created");
|
||||
tracing::info!("Default app_config created");
|
||||
} else {
|
||||
println!("App config already exists, skipping");
|
||||
tracing::info!("App config already exists, skipping");
|
||||
}
|
||||
|
||||
// Create data directories
|
||||
std::fs::create_dir_all(&store.memory_dir)?;
|
||||
std::fs::create_dir_all(&store.session_images_dir)?;
|
||||
println!("All store directories verified");
|
||||
tracing::info!("All store directories verified");
|
||||
|
||||
// Create a seed session
|
||||
let session_id = uuid::Uuid::new_v4().to_string();
|
||||
@@ -53,7 +55,7 @@ fn main() -> anyhow::Result<()> {
|
||||
use zesdex_domain::SessionRepository;
|
||||
let repo = zesdex_infrastructure::persistence::iam::session_repo::FileSystemSessionRepository::new();
|
||||
repo.save_session(&store.base_dir, &session)?;
|
||||
println!("Seed session created: id={session_id}");
|
||||
tracing::info!("Seed session created: id={session_id}");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+69
-58
@@ -4,39 +4,59 @@
|
||||
//! to the requested interface: TUI (default), daemon (background IPC),
|
||||
//! API server (REST), WebSocket server, gRPC server, or Web frontend.
|
||||
//!
|
||||
//! # CLI flags
|
||||
//!
|
||||
//! | Flag | Description |
|
||||
//! |------|-------------|
|
||||
//! | `--daemon` | Run as background daemon with IPC socket |
|
||||
//! | `--attach <id>` | Attach TUI client to a running daemon |
|
||||
//! | `--api` | Run REST API server |
|
||||
//! | `--api-port <port>` | REST API port (default 8080) |
|
||||
//! | `--ws` | Run WebSocket server |
|
||||
//! | `--ws-port <port>` | WebSocket port (default 8081) |
|
||||
//! | `--grpc` | Run gRPC server |
|
||||
//! | `--grpc-port <port>` | gRPC port (default 50051) |
|
||||
//! | `--web` | Serve web frontend |
|
||||
//! | `--version` | Print version and exit |
|
||||
//! CLI flags are parsed via clap; run with `--help` for details.
|
||||
|
||||
use std::sync::Mutex;
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
let is_daemon = args.iter().any(|a| a == "--daemon");
|
||||
let is_api = args.iter().any(|a| a == "--api");
|
||||
let is_ws = args.iter().any(|a| a == "--ws");
|
||||
let is_grpc = args.iter().any(|a| a == "--grpc");
|
||||
let is_web = args.iter().any(|a| a == "--web");
|
||||
let attach_session = args
|
||||
.iter()
|
||||
.position(|a| a == "--attach")
|
||||
.and_then(|i| args.get(i + 1).cloned());
|
||||
use clap::Parser;
|
||||
|
||||
if args.iter().any(|a| a == "--version") {
|
||||
println!("Zesdex version {}", env!("CARGO_PKG_VERSION"));
|
||||
return Ok(());
|
||||
}
|
||||
/// Zesdex — autonomous AI coding agent.
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(name = "zesdex", version, about = "Autonomous AI coding agent with TUI")]
|
||||
struct Cli {
|
||||
/// Run as background daemon with IPC socket
|
||||
#[arg(long)]
|
||||
daemon: bool,
|
||||
|
||||
/// Attach TUI client to a running daemon session
|
||||
#[arg(long)]
|
||||
attach: Option<String>,
|
||||
|
||||
/// Run REST API server
|
||||
#[arg(long)]
|
||||
api: bool,
|
||||
|
||||
/// REST API port
|
||||
#[arg(long, default_value_t = 8080)]
|
||||
api_port: u16,
|
||||
|
||||
/// Run WebSocket server
|
||||
#[arg(long)]
|
||||
ws: bool,
|
||||
|
||||
/// WebSocket port
|
||||
#[arg(long, default_value_t = 8081)]
|
||||
ws_port: u16,
|
||||
|
||||
/// Run gRPC server
|
||||
#[arg(long)]
|
||||
grpc: bool,
|
||||
|
||||
/// gRPC port
|
||||
#[arg(long, default_value_t = 50051)]
|
||||
grpc_port: u16,
|
||||
|
||||
/// Serve web frontend
|
||||
#[arg(long)]
|
||||
web: bool,
|
||||
|
||||
/// Web frontend port
|
||||
#[arg(long, default_value_t = 3000)]
|
||||
web_port: u16,
|
||||
}
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
let cli = Cli::parse();
|
||||
|
||||
// ── Setup logging ────────────────────────────────────────────────────
|
||||
let log_dir = dirs::data_dir()
|
||||
@@ -67,11 +87,11 @@ fn main() -> anyhow::Result<()> {
|
||||
|
||||
// ── Dispatch to interface ────────────────────────────────────────────
|
||||
// Validate mutually exclusive flags
|
||||
let mode_count = [is_daemon, is_api, is_ws, is_grpc, is_web]
|
||||
let mode_count = [cli.daemon, cli.api, cli.ws, cli.grpc, cli.web]
|
||||
.iter()
|
||||
.filter(|&&b| b)
|
||||
.count()
|
||||
+ if attach_session.is_some() { 1 } else { 0 };
|
||||
+ if cli.attach.is_some() { 1 } else { 0 };
|
||||
|
||||
if mode_count > 1 {
|
||||
anyhow::bail!(
|
||||
@@ -79,24 +99,24 @@ fn main() -> anyhow::Result<()> {
|
||||
);
|
||||
}
|
||||
|
||||
if is_daemon {
|
||||
if cli.daemon {
|
||||
tracing::info!("starting in daemon mode");
|
||||
zesdex_daemon::server::run_daemon()?;
|
||||
} else if let Some(session_id) = attach_session {
|
||||
} else if let Some(session_id) = cli.attach {
|
||||
tracing::info!("starting in attach mode for session {session_id}");
|
||||
zesdex_daemon::client::run_attach(&session_id)?;
|
||||
} else if is_api {
|
||||
} else if cli.api {
|
||||
tracing::info!("starting in API server mode");
|
||||
run_api_server(&args)?;
|
||||
} else if is_ws {
|
||||
run_api_server(cli.api_port)?;
|
||||
} else if cli.ws {
|
||||
tracing::info!("starting in WebSocket server mode");
|
||||
run_ws_server()?;
|
||||
} else if is_grpc {
|
||||
run_ws_server(cli.ws_port)?;
|
||||
} else if cli.grpc {
|
||||
tracing::info!("starting in gRPC server mode");
|
||||
run_grpc_server()?;
|
||||
} else if is_web {
|
||||
run_grpc_server(cli.grpc_port)?;
|
||||
} else if cli.web {
|
||||
tracing::info!("starting in web server mode");
|
||||
run_web_server()?;
|
||||
run_web_server(cli.web_port)?;
|
||||
} else {
|
||||
// Default: run TUI single-process mode
|
||||
tracing::info!("starting in TUI single-process mode");
|
||||
@@ -108,19 +128,11 @@ fn main() -> anyhow::Result<()> {
|
||||
|
||||
/// Run the TUI in single-process mode (TUI + agent in one process).
|
||||
fn run_tui_single_process() -> anyhow::Result<()> {
|
||||
// Import and run the TUI's single-process entry point
|
||||
zesdex_tui::run_single_process()
|
||||
}
|
||||
|
||||
/// Run the REST API server.
|
||||
fn run_api_server(args: &[String]) -> anyhow::Result<()> {
|
||||
let port = args
|
||||
.iter()
|
||||
.position(|a| a == "--api-port")
|
||||
.and_then(|i| args.get(i + 1))
|
||||
.and_then(|s| s.parse::<u16>().ok())
|
||||
.unwrap_or(8080);
|
||||
|
||||
fn run_api_server(port: u16) -> anyhow::Result<()> {
|
||||
let rt = tokio::runtime::Runtime::new()?;
|
||||
rt.block_on(async {
|
||||
let store = zesdex_domain::core::Store::new();
|
||||
@@ -133,8 +145,7 @@ fn run_api_server(args: &[String]) -> anyhow::Result<()> {
|
||||
);
|
||||
let app = zesdex_api::build_router(state);
|
||||
let addr = std::net::SocketAddr::from(([0, 0, 0, 0], port));
|
||||
tracing::info!("REST API server listening on {addr}");
|
||||
println!("REST API server listening on http://{addr}/api/v1/health");
|
||||
tracing::info!("REST API server listening on http://{addr}/api/v1/health");
|
||||
let listener = tokio::net::TcpListener::bind(addr).await?;
|
||||
axum::serve(listener, app).await?;
|
||||
Ok::<_, anyhow::Error>(())
|
||||
@@ -143,22 +154,22 @@ fn run_api_server(args: &[String]) -> anyhow::Result<()> {
|
||||
}
|
||||
|
||||
/// Run the WebSocket server.
|
||||
fn run_ws_server() -> anyhow::Result<()> {
|
||||
fn run_ws_server(port: u16) -> anyhow::Result<()> {
|
||||
let rt = tokio::runtime::Runtime::new()?;
|
||||
rt.block_on(async { zesdex_ws::run_server(8081).await })?;
|
||||
rt.block_on(async { zesdex_ws::run_server(port).await })?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Run the gRPC server.
|
||||
fn run_grpc_server() -> anyhow::Result<()> {
|
||||
fn run_grpc_server(port: u16) -> anyhow::Result<()> {
|
||||
let rt = tokio::runtime::Runtime::new()?;
|
||||
rt.block_on(async { zesdex_grpc::run_server(50051).await })?;
|
||||
rt.block_on(async { zesdex_grpc::run_server(port).await })?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Serve the web frontend.
|
||||
fn run_web_server() -> anyhow::Result<()> {
|
||||
fn run_web_server(port: u16) -> anyhow::Result<()> {
|
||||
let rt = tokio::runtime::Runtime::new()?;
|
||||
rt.block_on(async { zesdex_web::run_server(3000, None).await })?;
|
||||
rt.block_on(async { zesdex_web::run_server(port, None).await })?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user