feat: add background_process tool for launching long-running processes

Adds a new tool that allows launching processes (like game servers) in the
background while g3 continues to operate. The process runs independently
with stdout/stderr captured to a log file.

Features:
- Named process tracking for easy reference
- Automatic log capture to logs/background_processes/
- Returns PID and log file path for use with shell tool
- Automatic cleanup on agent shutdown via Drop trait

Usage: Use shell tool to interact with the process:
- Read logs: tail -100 <logfile>
- Check status: ps -p <pid>
- Stop process: kill <pid>

Files:
- New: crates/g3-core/src/background_process.rs
- New: crates/g3-core/tests/background_process_demo_test.rs
- Modified: crates/g3-core/src/lib.rs (tool definition + handler)
- Modified: crates/g3-core/src/prompts.rs (documentation)
This commit is contained in:
Dhanji R. Prasanna
2025-12-25 18:23:10 +11:00
parent 9ff5ba6098
commit d9c58576a1
4 changed files with 425 additions and 0 deletions

View File

@@ -0,0 +1,68 @@
use g3_core::background_process::BackgroundProcessManager;
use std::path::PathBuf;
use std::thread;
use std::time::Duration;
use std::fs;
#[test]
fn demo_background_process_with_script() {
// Create temp directories
let test_dir = std::env::temp_dir().join("g3_bg_demo");
let _ = fs::remove_dir_all(&test_dir);
fs::create_dir_all(&test_dir).unwrap();
// Create a test script
let script_path = test_dir.join("test.sh");
fs::write(&script_path, r#"#!/bin/bash
echo "Starting..."
for i in 1 2 3; do
echo "Tick $i"
sleep 0.5
done
echo "Done!"
"#).unwrap();
// Make executable
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
fs::set_permissions(&script_path, fs::Permissions::from_mode(0o755)).unwrap();
}
let log_dir = test_dir.join("logs");
let manager = BackgroundProcessManager::new(log_dir);
println!("\n=== Background Process Demo ===");
match manager.start("demo", "./test.sh", &test_dir) {
Ok(info) => {
println!("✅ Started '{}' with PID {}", info.name, info.pid);
println!(" Log file: {:?}", info.log_file);
// Wait for script to produce some output
thread::sleep(Duration::from_millis(800));
// Read logs
let logs = fs::read_to_string(&info.log_file).unwrap_or_default();
println!("\n📜 Logs so far:\n{}", logs);
// Should still be running
assert!(manager.is_running("demo"), "Process should still be running");
println!("🔍 Process is running: true");
// Wait for completion
thread::sleep(Duration::from_secs(2));
// Read final logs
let final_logs = fs::read_to_string(&info.log_file).unwrap_or_default();
println!("\n📜 Final logs:\n{}", final_logs);
assert!(final_logs.contains("Done!"), "Should have completed");
}
Err(e) => panic!("Failed to start: {}", e),
}
// Cleanup
let _ = fs::remove_dir_all(&test_dir);
println!("\n✅ Demo complete!");
}