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:
@@ -1,3 +1,4 @@
|
||||
pub mod background_process;
|
||||
pub mod code_search;
|
||||
pub mod error_handling;
|
||||
pub mod feedback_extraction;
|
||||
@@ -865,6 +866,7 @@ pub struct Agent<W: UiWriter> {
|
||||
requirements_sha: Option<String>,
|
||||
/// Working directory for tool execution (set by --codebase-fast-start)
|
||||
working_dir: Option<String>,
|
||||
background_process_manager: std::sync::Arc<background_process::BackgroundProcessManager>,
|
||||
}
|
||||
|
||||
impl<W: UiWriter> Agent<W> {
|
||||
@@ -1178,6 +1180,10 @@ impl<W: UiWriter> Agent<W> {
|
||||
tool_call_count: 0,
|
||||
requirements_sha: None,
|
||||
working_dir: None,
|
||||
background_process_manager: std::sync::Arc::new(
|
||||
background_process::BackgroundProcessManager::new(
|
||||
paths::get_logs_dir().join("background_processes")
|
||||
)),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2796,6 +2802,28 @@ impl<W: UiWriter> Agent<W> {
|
||||
"required": ["command"]
|
||||
}),
|
||||
},
|
||||
Tool {
|
||||
name: "background_process".to_string(),
|
||||
description: "Launch a long-running process in the background (e.g., game servers, dev servers). The process runs independently and logs are captured to a file. Use the regular 'shell' tool to read logs (cat/tail), check status (ps), or stop the process (kill). Returns the PID and log file path.".to_string(),
|
||||
input_schema: json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "A unique name for this process (e.g., 'game_server', 'my_app'). Used to identify the process and its log file."
|
||||
},
|
||||
"command": {
|
||||
"type": "string",
|
||||
"description": "The shell command to execute in the background"
|
||||
},
|
||||
"working_dir": {
|
||||
"type": "string",
|
||||
"description": "Optional working directory. Defaults to current directory if not specified."
|
||||
}
|
||||
},
|
||||
"required": ["name", "command"]
|
||||
}),
|
||||
},
|
||||
Tool {
|
||||
name: "read_file".to_string(),
|
||||
description: "Read the contents of a file. For image files (png, jpg, jpeg, gif, bmp, tiff, webp), automatically extracts text using OCR. For text files, optionally read a specific character range.".to_string(),
|
||||
@@ -4793,6 +4821,50 @@ impl<W: UiWriter> Agent<W> {
|
||||
Ok("❌ Missing command argument".to_string())
|
||||
}
|
||||
}
|
||||
"background_process" => {
|
||||
debug!("Processing background_process tool call");
|
||||
let name = tool_call.args.get("name")
|
||||
.and_then(|v| v.as_str());
|
||||
let name = match name {
|
||||
Some(n) => n,
|
||||
None => return Ok("❌ Missing 'name' argument".to_string()),
|
||||
};
|
||||
|
||||
let command = tool_call.args.get("command")
|
||||
.and_then(|v| v.as_str());
|
||||
let command = match command {
|
||||
Some(c) => c,
|
||||
None => return Ok("❌ Missing 'command' argument".to_string()),
|
||||
};
|
||||
|
||||
// Use provided working_dir, or fall back to agent's working_dir, or current dir
|
||||
let work_dir = tool_call.args.get("working_dir")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(|s| std::path::PathBuf::from(shellexpand::tilde(s).as_ref()))
|
||||
.or_else(|| working_dir.map(std::path::PathBuf::from))
|
||||
.unwrap_or_else(|| std::env::current_dir().unwrap_or_default());
|
||||
|
||||
match self.background_process_manager.start(name, command, &work_dir) {
|
||||
Ok(info) => {
|
||||
Ok(format!(
|
||||
"✅ Background process '{}' started\n\n\
|
||||
**PID:** {}\n\
|
||||
**Log file:** {}\n\
|
||||
**Working dir:** {}\n\n\
|
||||
To interact with this process, use the shell tool:\n\
|
||||
- View logs: `tail -100 {}`\n\
|
||||
- Follow logs: `tail -f {}` (blocks until Ctrl+C)\n\
|
||||
- Check status: `ps -p {}`\n\
|
||||
- Stop process: `kill {}`",
|
||||
info.name, info.pid,
|
||||
info.log_file.display(), info.working_dir.display(),
|
||||
info.log_file.display(), info.log_file.display(),
|
||||
info.pid, info.pid
|
||||
))
|
||||
}
|
||||
Err(e) => Ok(format!("❌ Failed to start background process: {}", e)),
|
||||
}
|
||||
}
|
||||
"read_file" => {
|
||||
debug!("Processing read_file tool call");
|
||||
if let Some(file_path) = tool_call.args.get("file_path") {
|
||||
|
||||
Reference in New Issue
Block a user