Files
g3/crates/g3-console/src/main.rs
Dhanji R. Prasanna da91459e09 Fix auto-continue bug: don't return early when tools executed but final_output not called
The bug was in the chunk.finished block inside stream_completion_with_tools.
When no tool was executed in the CURRENT iteration (!tool_executed), the code
would return early without checking if tools were executed in PREVIOUS iterations
(any_tool_executed) and final_output was never called.

This caused the agent to terminate prematurely after executing tools like
todo_read when the LLM responded with text instead of calling final_output.

The fix adds a check: if any_tool_executed && !final_output_called, we break
to let the outer loop's auto-continue logic prompt the LLM to continue.

Also fixed missing debug! import in g3-console/src/main.rs.
2025-12-22 16:45:17 +11:00

102 lines
3.0 KiB
Rust

use g3_console::api;
use g3_console::launch;
use g3_console::process;
use api::control::{kill_instance, launch_instance, restart_instance};
use api::instances::{get_file_content, get_instance, list_instances};
use api::logs::get_instance_logs;
use api::state::{browse_filesystem, get_state, save_state};
use axum::{
routing::{get, post},
Router,
};
use clap::Parser;
use process::{ProcessController, ProcessDetector};
use std::sync::Arc;
use tokio::sync::Mutex;
use tower_http::cors::CorsLayer;
use tower_http::services::ServeDir;
use tracing::{debug, Level};
use tracing_subscriber;
#[derive(Parser, Debug)]
#[command(name = "g3-console")]
#[command(about = "Web console for monitoring and managing g3 instances")]
struct Args {
/// Port to bind to
#[arg(long, default_value = "9090")]
port: u16,
/// Host to bind to
#[arg(long, default_value = "127.0.0.1")]
host: String,
/// Auto-open browser
#[arg(long)]
open: bool,
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Initialize tracing
tracing_subscriber::fmt().with_max_level(Level::INFO).init();
let args = Args::parse();
// Create shared state
let detector = Arc::new(Mutex::new(ProcessDetector::new()));
let controller = Arc::new(Mutex::new(ProcessController::new()));
// Build API routes with different state for different endpoints
let instance_routes = Router::new()
.route("/instances", get(list_instances))
.route("/instances/:id", get(get_instance))
.route("/instances/:id/logs", get(get_instance_logs))
.route("/instances/:id/file", get(get_file_content))
.with_state(detector.clone());
let control_routes = Router::new()
.route("/instances/:id/kill", post(kill_instance))
.route("/instances/:id/restart", post(restart_instance))
.route("/instances/launch", post(launch_instance))
.with_state(controller.clone());
let state_routes = Router::new()
.route("/state", get(get_state))
.route("/state", post(save_state))
.route("/browse", post(browse_filesystem))
.with_state(controller.clone());
// Combine routes
let api_routes = Router::new()
.merge(instance_routes)
.merge(control_routes)
.merge(state_routes);
// Serve static files from web directory
let web_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("web");
let static_service = ServeDir::new(web_dir);
// Build main app
let app = Router::new()
.nest("/api", api_routes)
.fallback_service(static_service)
.layer(CorsLayer::permissive());
let addr = format!("{}:{}", args.host, args.port);
debug!("Starting g3-console on http://{}", addr);
// Auto-open browser if requested
if args.open {
let url = format!("http://{}", addr);
debug!("Opening browser to {}", url);
let _ = open::that(&url);
}
// Start server
let listener = tokio::net::TcpListener::bind(&addr).await?;
axum::serve(listener, app).await?;
Ok(())
}