2026-07-20 09:04:57 +07:00
|
|
|
//! Background bash process output and kill tools.
|
|
|
|
|
|
|
|
|
|
use anyhow::Result;
|
|
|
|
|
use serde_json::{json, Value};
|
|
|
|
|
use tracing::info;
|
|
|
|
|
|
|
|
|
|
use crate::tools::{arg_str, Tool, ToolCtx};
|
|
|
|
|
|
|
|
|
|
/// Get the output of a background bash job by ID.
|
|
|
|
|
///
|
|
|
|
|
/// Flow: look up `{session_dir}/bash-outputs/{job_id}` → read content back.
|
|
|
|
|
pub struct BashOutput;
|
|
|
|
|
|
|
|
|
|
impl Tool for BashOutput {
|
|
|
|
|
fn name(&self) -> &'static str {
|
|
|
|
|
"bash_output"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn description(&self) -> &'static str {
|
|
|
|
|
"Get the output of a background bash job by ID"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn parameters(&self) -> Value {
|
|
|
|
|
json!({
|
|
|
|
|
"type": "object",
|
|
|
|
|
"properties": {
|
|
|
|
|
"job_id": {
|
|
|
|
|
"type": "string",
|
|
|
|
|
"description": "Background job ID"
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
"required": ["job_id"]
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn run(&self, ctx: &ToolCtx, args: &Value) -> Result<String> {
|
|
|
|
|
let job_id = arg_str(args, "job_id")?;
|
|
|
|
|
info!("Getting output for job: {job_id}");
|
|
|
|
|
|
2026-07-20 12:26:08 +07:00
|
|
|
// Prevent path traversal
|
|
|
|
|
if job_id.contains('/') || job_id.contains('\\') || job_id.contains("..") {
|
|
|
|
|
anyhow::bail!("invalid job_id '{job_id}': must not contain path separators");
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-20 09:04:57 +07:00
|
|
|
// Read from the session's bash output directory
|
|
|
|
|
let output_dir = ctx.session_dir.join("bash-outputs");
|
|
|
|
|
let output_file = output_dir.join(&job_id);
|
|
|
|
|
|
|
|
|
|
if output_file.exists() {
|
|
|
|
|
let content = std::fs::read_to_string(&output_file)
|
|
|
|
|
.unwrap_or_else(|_| "Error reading output".to_string());
|
|
|
|
|
Ok(format!("Output for job '{job_id}':\n{content}"))
|
|
|
|
|
} else {
|
|
|
|
|
Ok(format!(
|
|
|
|
|
"No output found for job '{job_id}'. The job may still be running."
|
|
|
|
|
))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub struct BashKill;
|
|
|
|
|
|
|
|
|
|
impl Tool for BashKill {
|
|
|
|
|
fn name(&self) -> &'static str {
|
|
|
|
|
"bash_kill"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn description(&self) -> &'static str {
|
|
|
|
|
"Kill a background bash job by ID"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn parameters(&self) -> Value {
|
|
|
|
|
json!({
|
|
|
|
|
"type": "object",
|
|
|
|
|
"properties": {
|
|
|
|
|
"job_id": {
|
|
|
|
|
"type": "string",
|
|
|
|
|
"description": "Background job ID to kill"
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
"required": ["job_id"]
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn run(&self, _ctx: &ToolCtx, args: &Value) -> Result<String> {
|
2026-07-20 10:55:09 +07:00
|
|
|
let job_id = crate::tools::arg_str(args, "job_id")?;
|
|
|
|
|
info!("bash_kill called for job: {job_id}");
|
2026-07-20 12:26:08 +07:00
|
|
|
|
|
|
|
|
if crate::bgbash::control::bash_control().cancel(&job_id) {
|
|
|
|
|
Ok(format!("Killed background job '{job_id}'"))
|
2026-07-20 10:55:09 +07:00
|
|
|
} else {
|
2026-07-20 12:26:08 +07:00
|
|
|
anyhow::bail!("no active background job found with ID '{job_id}'")
|
2026-07-20 10:55:09 +07:00
|
|
|
}
|
2026-07-20 09:04:57 +07:00
|
|
|
}
|
|
|
|
|
}
|