2026-07-20 09:04:57 +07:00
|
|
|
//! Background bash process output and kill tools.
|
2026-07-20 15:53:20 +07:00
|
|
|
//!
|
|
|
|
|
//! These tools allow the agent to inspect the output of a background shell job
|
|
|
|
|
//! (`BashOutput`) and to terminate a running background job (`BashKill`).
|
2026-07-20 09:04:57 +07:00
|
|
|
|
|
|
|
|
use anyhow::Result;
|
|
|
|
|
use serde_json::{json, Value};
|
2026-07-20 15:53:20 +07:00
|
|
|
use tracing::{info, instrument, warn};
|
2026-07-20 09:04:57 +07:00
|
|
|
|
|
|
|
|
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.
|
2026-07-20 15:53:20 +07:00
|
|
|
/// Path traversal in `job_id` is explicitly rejected.
|
2026-07-20 09:04:57 +07:00
|
|
|
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"]
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-20 15:53:20 +07:00
|
|
|
#[instrument(skip(self, ctx, args))]
|
2026-07-20 09:04:57 +07:00
|
|
|
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)
|
2026-07-20 15:53:20 +07:00
|
|
|
.unwrap_or_else(|e| format!("Error reading output: {e}"));
|
2026-07-20 09:04:57 +07:00
|
|
|
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."
|
|
|
|
|
))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-20 15:53:20 +07:00
|
|
|
/// Kill a background bash job by ID.
|
|
|
|
|
///
|
|
|
|
|
/// Flow: parse job_id → look up in the global bash controller → cancel the job.
|
2026-07-20 09:04:57 +07:00
|
|
|
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"]
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-20 15:53:20 +07:00
|
|
|
#[instrument(skip(self, _ctx, args))]
|
2026-07-20 09:04:57 +07:00
|
|
|
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) {
|
2026-07-20 15:53:20 +07:00
|
|
|
info!(job = %job_id, "background job killed");
|
2026-07-20 12:26:08 +07:00
|
|
|
Ok(format!("Killed background job '{job_id}'"))
|
2026-07-20 10:55:09 +07:00
|
|
|
} else {
|
2026-07-20 15:53:20 +07:00
|
|
|
warn!(job = %job_id, "no active background job found");
|
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
|
|
|
}
|
|
|
|
|
}
|