g3 console init

This commit is contained in:
Dhanji Prasanna
2025-11-07 09:25:17 +11:00
parent aaf918828f
commit cb43fcdecf
24 changed files with 2860 additions and 498 deletions

View File

@@ -1,5 +1,5 @@
use crate::models::*;
use crate::process::{ProcessController, ProcessDetector};
use crate::process::ProcessController;
use axum::{extract::State, http::StatusCode, Json};
use std::sync::Arc;
use tokio::sync::Mutex;
@@ -82,7 +82,14 @@ pub async fn launch_instance(
// Validate binary path if provided
if let Some(ref binary_path) = request.g3_binary_path {
let path = std::path::Path::new(binary_path);
// Expand relative paths and resolve to absolute
let path = if binary_path.starts_with("./") || binary_path.starts_with("../") {
std::env::current_dir()
.map(|cwd| cwd.join(binary_path))
.unwrap_or_else(|_| std::path::PathBuf::from(binary_path))
} else {
std::path::PathBuf::from(binary_path)
};
// Check if file exists
if !path.exists() {

View File

@@ -1,7 +1,8 @@
use crate::logs::{LogParser, StatsAggregator};
use crate::models::*;
use crate::process::ProcessDetector;
use axum::{extract::State, http::StatusCode, Json};
use axum::{extract::{Query, State}, http::StatusCode, Json};
use serde::Deserialize;
use std::sync::Arc;
use tokio::sync::Mutex;
use tracing::{debug, error, warn};
@@ -187,3 +188,34 @@ fn read_file_snippet(workspace: &std::path::Path, filename: &str) -> Option<Stri
.join("\n")
})
}
#[derive(Deserialize)]
pub struct FileQuery {
name: String,
}
pub async fn get_file_content(
axum::extract::Path(id): axum::extract::Path<String>,
Query(query): Query<FileQuery>,
State(detector): State<AppState>,
) -> Result<Json<serde_json::Value>, StatusCode> {
let mut detector = detector.lock().await;
// Find the instance
let instances = detector.detect_instances().map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
let instance = instances.iter().find(|i| i.id == id).ok_or(StatusCode::NOT_FOUND)?;
// Read the full file
let file_path = instance.workspace.join(&query.name);
if !file_path.exists() {
return Err(StatusCode::NOT_FOUND);
}
let content = std::fs::read_to_string(&file_path)
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
Ok(Json(serde_json::json!({
"name": query.name,
"content": content,
})))
}

View File

@@ -1,5 +1,4 @@
use crate::logs::LogParser;
use crate::models::*;
use crate::process::ProcessDetector;
use axum::{extract::State, http::StatusCode, Json};
use std::sync::Arc;

View File

@@ -2,8 +2,3 @@ pub mod instances;
pub mod control;
pub mod logs;
pub mod state;
pub use instances::*;
pub use control::*;
pub use logs::*;
pub use state::*;

View File

@@ -44,7 +44,7 @@ pub struct BrowseResponse {
pub struct FileEntry {
pub name: String,
pub path: String,
pub is_directory: bool,
pub is_dir: bool,
pub is_executable: bool,
}
@@ -76,7 +76,7 @@ pub async fn browse_filesystem(
entries.push(FileEntry {
name: entry.file_name().to_string_lossy().to_string(),
path: entry.path().to_string_lossy().to_string(),
is_directory: metadata.is_dir(),
is_dir: metadata.is_dir(),
is_executable: metadata.permissions().mode() & 0o111 != 0,
});
}
@@ -84,7 +84,7 @@ pub async fn browse_filesystem(
}
entries.sort_by(|a, b| {
match (a.is_directory, b.is_directory) {
match (a.is_dir, b.is_dir) {
(true, false) => std::cmp::Ordering::Less,
(false, true) => std::cmp::Ordering::Greater,
_ => a.name.cmp(&b.name),

View File

@@ -57,10 +57,10 @@ impl ConsoleState {
}
fn config_path() -> PathBuf {
// Use explicit ~/.config/g3/console-state.json path as per requirements
// Use explicit ~/.config/g3/console.json path as per requirements
let home = dirs::home_dir().unwrap_or_else(|| PathBuf::from("."));
home.join(".config")
.join("g3")
.join("console-state.json")
.join("console.json")
}
}

View File

@@ -4,8 +4,8 @@ mod models;
mod process;
mod launch;
use api::control::{kill_instance, launch_instance, restart_instance, ControllerState};
use api::instances::{get_instance, list_instances, AppState};
use api::control::{kill_instance, launch_instance, restart_instance};
use api::instances::{get_instance, get_file_content, list_instances};
use api::logs::get_instance_logs;
use api::state::{get_state, save_state, browse_filesystem};
use axum::{
@@ -56,6 +56,7 @@ async fn main() -> anyhow::Result<()> {
.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()

View File

@@ -5,7 +5,7 @@ use std::collections::HashMap;
use std::sync::Mutex;
use std::path::PathBuf;
use sysinfo::{Pid, Signal, System, Process};
use tracing::{debug, error, info};
use tracing::{debug, info};
use crate::models::LaunchParams;
pub struct ProcessController {
@@ -120,8 +120,8 @@ impl ProcessController {
// We need to scan for it by matching workspace and recent start time
info!("Scanning for newly launched g3 process in workspace: {}", workspace);
// Wait a moment for the process to fully start
std::thread::sleep(std::time::Duration::from_millis(500));
// Wait even longer for the process to fully start and appear in process list
std::thread::sleep(std::time::Duration::from_millis(2500));
// Refresh and scan for the process
self.system.refresh_processes();
@@ -149,11 +149,11 @@ impl ProcessController {
});
if has_workspace {
// Check if it's recent (started within last 5 seconds)
// Check if it's recent (started within last 10 seconds)
let now = std::time::SystemTime::now();
let start_time = std::time::UNIX_EPOCH + std::time::Duration::from_secs(process.start_time());
if let Ok(duration) = now.duration_since(start_time) {
if duration.as_secs() < 5 {
if duration.as_secs() < 10 {
found_pid = Some(pid.as_u32());
break;
}
@@ -161,7 +161,42 @@ impl ProcessController {
}
}
let pid = found_pid.unwrap_or(intermediate_pid);
let pid = if let Some(found) = found_pid {
found
} else {
// If we couldn't find it, try one more refresh after a longer delay
info!("Process not found on first scan, trying again...");
std::thread::sleep(std::time::Duration::from_millis(2000));
self.system.refresh_processes();
// Try the scan again with full logic
let mut retry_found = None;
for (pid, process) in self.system.processes() {
let cmd = process.cmd();
let cmd_str = cmd.join(" ");
let is_g3 = process.name().contains("g3") || cmd_str.contains("g3");
if !is_g3 {
continue;
}
let has_workspace = cmd.iter().any(|arg| {
if let Ok(path) = PathBuf::from(arg).canonicalize() {
if let Ok(ws) = workspace_path.canonicalize() {
return path == ws;
}
}
false
});
if has_workspace {
retry_found = Some(pid.as_u32());
break;
}
}
retry_found.unwrap_or(intermediate_pid)
};
info!("Launched g3 process with PID {}", pid);

View File

@@ -1,9 +1,8 @@
use crate::models::{ExecutionMethod, Instance, InstanceStatus, InstanceType};
use anyhow::{Context, Result};
use anyhow::Result;
use chrono::{DateTime, Utc};
use std::path::PathBuf;
use std::process::Command;
use sysinfo::{System, Process, Pid};
use sysinfo::{System, Pid, Process};
use tracing::{debug, warn};
pub struct ProcessDetector {
@@ -46,14 +45,26 @@ impl ProcessDetector {
) -> Option<Instance> {
let cmd_str = cmd.join(" ");
// Check if this is a g3 binary
let is_g3_binary = cmd.get(0).map(|s| s.ends_with("g3")).unwrap_or(false);
// Check if this is a g3 binary (more comprehensive check)
let is_g3_binary = cmd.get(0).map(|s| {
s.ends_with("g3") || s.ends_with("/g3") || s.contains("/target/release/g3") || s.contains("/target/debug/g3")
}).unwrap_or(false);
// Check if this is cargo run with g3
let is_cargo_run = cmd.get(0).map(|s| s.contains("cargo")).unwrap_or(false)
&& cmd.iter().any(|s| s == "run" || s.contains("g3"));
if !is_g3_binary && !is_cargo_run {
let is_cargo_run = cmd.get(0).map(|s| s.contains("cargo")).unwrap_or(false) && cmd.iter().any(|s| s == "run");
// Also check if any part of the command line contains g3-related patterns
let has_g3_pattern = cmd_str.contains("g3 ")
|| cmd_str.contains("/g3 ")
|| cmd_str.contains("g3-")
|| cmd_str.ends_with("g3")
|| cmd_str.contains("--workspace") // g3-specific flag
|| cmd_str.contains("--autonomous"); // g3-specific flag
// Accept if it's a g3 binary, cargo run with g3 patterns, or has g3-specific flags
let is_g3_process = is_g3_binary || (is_cargo_run && has_g3_pattern) || has_g3_pattern;
if !is_g3_process {
return None;
}
@@ -100,7 +111,7 @@ impl ProcessDetector {
})
}
fn extract_workspace(&self, pid: Pid, process: &Process, cmd: &[String]) -> Option<PathBuf> {
fn extract_workspace(&self, pid: Pid, _process: &Process, cmd: &[String]) -> Option<PathBuf> {
// Look for --workspace flag
for i in 0..cmd.len() {
if cmd[i] == "--workspace" && i + 1 < cmd.len() {