Files
zesdex/apps/infrastructure/src/tools/shell.rs
T
asepharyana a04651905f feat(token): add refresh token verification to TokenService
feat(bootstrap): create temporary settings and config files to prevent data loss

refactor(edit_log): switch from Vec to VecDeque for efficient memory management

fix(gateway): ensure store directories are created before starting the API server

refactor(bgbash): implement a global singleton for BashControl

feat(auth): enhance session authentication middleware to use SessionRepository

fix(edit_log_repo): update to use VecDeque for in-memory edit log storage

fix(memory_repo): add newline escaping for frontmatter fields

fix(session_lock_repo): improve error handling for lock file operations

fix(bash_tools): prevent path traversal in job_id argument

refactor(delete): enforce empty directory deletion in file system tools

fix(edit): optimize string replacement to only replace the first occurrence

fix(git_cred): improve credential management with piped input to git commands

feat(git_operator): add safety filter to block destructive git operations

fix(shell): register background jobs in Bash control

feat(spawn): add access tier specification for pipeline stages

refactor(hive_mind): run directives concurrently for improved performance

fix(auth): update refresh token verification in the refresh handler

fix(chat): optimize LLM client usage based on model matching

fix(conversations): enhance message deletion to target specific indices

feat(api): add JWT authentication middleware for all API routes

fix(state): implement refresh token verification in JwtTokenService

fix(daemon): improve usage tracking with saturating addition

fix(tui): handle compacted messages in the TUI state management
2026-07-20 12:26:10 +07:00

123 lines
4.3 KiB
Rust

//! Bash-shell execution tool with safety filters and optional timeout.
use crate::tools::{Tool, ToolCtx};
use anyhow::Result;
use serde_json::{json, Value};
use std::process::Command;
use std::time::Duration;
pub struct Bash;
impl Tool for Bash {
fn name(&self) -> &'static str {
"bash"
}
fn description(&self) -> &'static str {
"Execute a shell command via bash -c"
}
fn parameters(&self) -> Value {
json!({
"type": "object",
"properties": {
"command": {
"type": "string",
"description": "Shell command to execute"
},
"description": {
"type": "string",
"description": "Human-readable description of what the command does"
},
"timeout": {
"type": "integer",
"description": "Timeout in milliseconds (default 120000, max 600000)"
},
"run_in_background": {
"type": "boolean",
"description": "Run the command in the background"
}
},
"required": ["command"]
})
}
fn run(&self, _ctx: &ToolCtx, args: &Value) -> Result<String> {
let cmd = crate::tools::arg_str(args, "command")?;
let timeout_ms = args
.get("timeout")
.and_then(serde_json::Value::as_u64)
.unwrap_or(120_000)
.min(600_000);
// Safety filter: block destructive git operations
crate::tools::shell_filter::git::check_git_destructive(&cmd)
.map_err(|e| anyhow::anyhow!("blocked: {e}"))?;
let run_in_background = args
.get("run_in_background")
.and_then(serde_json::Value::as_bool)
.unwrap_or(false);
if run_in_background {
let job = crate::bgbash::job::spawn_bash_job(cmd);
crate::bgbash::control::bash_control().register(job.clone());
return Ok(format!("Background job: {}", job.id));
}
let mut child = Command::new("bash")
.arg("-c")
.arg(&cmd)
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.spawn()
.map_err(|e| anyhow::anyhow!("failed to spawn bash: {e}"))?;
let start = std::time::Instant::now();
let timeout = Duration::from_millis(timeout_ms);
loop {
match child.try_wait() {
Ok(Some(status)) => {
let elapsed = start.elapsed().as_secs_f64();
let output = child
.wait_with_output()
.map_err(|e| anyhow::anyhow!("failed to collect output: {e}"))?;
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
let combined = if stderr.is_empty() {
stdout
} else {
format!("{stdout}\n{stderr}")
};
let trimmed = combined.trim().to_string();
if status.success() {
return Ok(if trimmed.is_empty() {
format!("Command completed in {elapsed:.2}s (exit code 0)")
} else {
format!("{trimmed}\n\nExit code: 0 ({elapsed:.2}s)")
});
}
return Ok(format!(
"{}\n\nExit code: {} ({:.2}s)",
trimmed,
status.code().unwrap_or(-1),
elapsed
));
}
Ok(None) => {
if start.elapsed() > timeout {
let _ = child.kill();
let _ = child.wait();
anyhow::bail!("command timed out after {timeout_ms}ms");
}
std::thread::sleep(Duration::from_millis(10));
}
Err(e) => {
anyhow::bail!("failed to wait for command: {e}");
}
}
}
}
}