An AI agent built to do Ralph loops - plan mode for planning and ralph mode for implementing.
at new-directions 105 lines 3.6 kB view raw
1use crate::daemon::DaemonConfig; 2use crate::daemon::api::AppState; 3use crate::daemon::api::{agents, graph, projects, search}; 4use crate::daemon::ws; 5use axum::routing::{delete, get, post}; 6use axum::{Json, Router}; 7use tokio_util::sync::CancellationToken; 8use tower_http::cors::{Any, CorsLayer}; 9 10/// Create the axum Router with all routes and middleware 11pub fn create_router(state: AppState) -> Router { 12 let cors = CorsLayer::new() 13 .allow_origin(Any) 14 .allow_methods(Any) 15 .allow_headers(Any); 16 17 Router::new() 18 // Health 19 .route("/api/health", get(health_check)) 20 // Projects 21 .route( 22 "/api/projects", 23 get(projects::list_projects).post(projects::create_project), 24 ) 25 .route( 26 "/api/projects/{id}", 27 get(projects::get_project).delete(projects::delete_project), 28 ) 29 // Goals 30 .route( 31 "/api/projects/{id}/goals", 32 get(graph::list_goals).post(graph::create_goal), 33 ) 34 // Nodes 35 .route( 36 "/api/nodes/{id}", 37 get(graph::get_node).patch(graph::update_node), 38 ) 39 .route("/api/nodes/{id}/children", post(graph::create_child)) 40 // Edges 41 .route("/api/edges", post(graph::create_edge)) 42 .route("/api/edges/{id}", delete(graph::delete_edge)) 43 // Goal tree 44 .route("/api/goals/{id}/tree", get(graph::get_goal_tree)) 45 // Tasks 46 .route("/api/goals/{id}/tasks", get(graph::list_tasks)) 47 .route("/api/goals/{id}/tasks/ready", get(graph::list_ready_tasks)) 48 .route("/api/goals/{id}/tasks/next", get(graph::next_task)) 49 // Decisions 50 .route("/api/projects/{id}/decisions", get(graph::list_decisions)) 51 .route( 52 "/api/projects/{id}/decisions/history", 53 get(graph::decisions_history), 54 ) 55 .route( 56 "/api/projects/{id}/decisions/export", 57 post(graph::export_decisions), 58 ) 59 // Search 60 .route("/api/projects/{id}/search", post(search::search_nodes)) 61 // Sessions 62 .route("/api/goals/{id}/sessions", get(graph::list_sessions)) 63 .route("/api/sessions/{id}", get(graph::get_session)) 64 // Agents 65 .route("/api/goals/{id}/agents", get(agents::list_agents)) 66 // Graph import/export 67 .route( 68 "/api/projects/{id}/graph/export", 69 get(graph::export_all_goals), 70 ) 71 .route("/api/goals/{id}/export", get(graph::export_goal_toml)) 72 .route("/api/projects/{id}/graph/import", post(graph::import_graph)) 73 .route("/api/projects/{id}/graph/diff", post(graph::diff_graph)) 74 // WebSocket 75 .route("/ws", get(ws::ws_handler)) 76 // Static file serving (fallback for non-API routes) 77 .fallback(crate::daemon::static_files::static_handler) 78 .layer(cors) 79 .with_state(state) 80} 81 82async fn health_check() -> Json<serde_json::Value> { 83 Json(serde_json::json!({ "status": "ok" })) 84} 85 86/// Start the axum server, blocking until the shutdown token is cancelled 87pub async fn start_server( 88 config: &DaemonConfig, 89 state: AppState, 90 shutdown: CancellationToken, 91) -> anyhow::Result<()> { 92 let router = create_router(state); 93 let addr = config.socket_addr()?; 94 95 let listener = tokio::net::TcpListener::bind(addr).await?; 96 tracing::info!("Daemon listening on {}", addr); 97 98 axum::serve(listener, router) 99 .with_graceful_shutdown(async move { 100 shutdown.cancelled().await; 101 }) 102 .await?; 103 104 Ok(()) 105}