use std::sync::Arc;
use serde::{Deserialize, Serialize};
use super::catalog::builtin_tool_catalog;
use super::registry::BuiltinTool;
use super::runner::compute_module_hash;
use super::types::*;
use crate::governance::EffectVector;
pub struct SysServiceListTool {
spec: BuiltinToolSpec,
service_registry: Arc<crate::service::ServiceRegistry>,
}
impl SysServiceListTool {
pub fn new(service_registry: Arc<crate::service::ServiceRegistry>) -> Self {
let spec = builtin_tool_catalog().into_iter().find(|s| s.name == "sys.service.list").unwrap();
Self { spec, service_registry }
}
}
impl BuiltinTool for SysServiceListTool {
fn name(&self) -> &str { "sys.service.list" }
fn spec(&self) -> &BuiltinToolSpec { &self.spec }
fn execute(&self, _args: serde_json::Value) -> Result<serde_json::Value, ToolError> {
let services = self.service_registry.list();
let entries: Vec<serde_json::Value> = services.iter().map(|(name, stype)| {
serde_json::json!({
"name": name,
"service_type": format!("{stype:?}"),
})
}).collect();
Ok(serde_json::json!({"services": entries, "count": entries.len()}))
}
}
pub struct SysServiceHealthTool {
spec: BuiltinToolSpec,
service_registry: Arc<crate::service::ServiceRegistry>,
}
impl SysServiceHealthTool {
pub fn new(service_registry: Arc<crate::service::ServiceRegistry>) -> Self {
let spec = builtin_tool_catalog().into_iter().find(|s| s.name == "sys.service.health").unwrap();
Self { spec, service_registry }
}
}
impl BuiltinTool for SysServiceHealthTool {
fn name(&self) -> &str { "sys.service.health" }
fn spec(&self) -> &BuiltinToolSpec { &self.spec }
fn execute(&self, _args: serde_json::Value) -> Result<serde_json::Value, ToolError> {
let services = self.service_registry.list();
let entries: Vec<serde_json::Value> = services.iter().map(|(name, _)| {
serde_json::json!({"name": name, "status": "registered"})
}).collect();
Ok(serde_json::json!({"health": entries, "count": entries.len()}))
}
}
#[cfg(feature = "exochain")]
pub struct SysChainStatusTool {
spec: BuiltinToolSpec,
chain: Arc<crate::chain::ChainManager>,
}
#[cfg(feature = "exochain")]
impl SysChainStatusTool {
pub fn new(chain: Arc<crate::chain::ChainManager>) -> Self {
let spec = builtin_tool_catalog().into_iter().find(|s| s.name == "sys.chain.status").unwrap();
Self { spec, chain }
}
}
#[cfg(feature = "exochain")]
impl BuiltinTool for SysChainStatusTool {
fn name(&self) -> &str { "sys.chain.status" }
fn spec(&self) -> &BuiltinToolSpec { &self.spec }
fn execute(&self, _args: serde_json::Value) -> Result<serde_json::Value, ToolError> {
let status = self.chain.status();
Ok(serde_json::json!({
"chain_id": status.chain_id,
"sequence": status.sequence,
"event_count": status.event_count,
"checkpoint_count": status.checkpoint_count,
}))
}
}
#[cfg(feature = "exochain")]
pub struct SysChainQueryTool {
spec: BuiltinToolSpec,
chain: Arc<crate::chain::ChainManager>,
}
#[cfg(feature = "exochain")]
impl SysChainQueryTool {
pub fn new(chain: Arc<crate::chain::ChainManager>) -> Self {
let spec = builtin_tool_catalog().into_iter().find(|s| s.name == "sys.chain.query").unwrap();
Self { spec, chain }
}
}
#[cfg(feature = "exochain")]
impl BuiltinTool for SysChainQueryTool {
fn name(&self) -> &str { "sys.chain.query" }
fn spec(&self) -> &BuiltinToolSpec { &self.spec }
fn execute(&self, args: serde_json::Value) -> Result<serde_json::Value, ToolError> {
let count = args.get("count").and_then(|v| v.as_u64()).unwrap_or(20) as usize;
let events = self.chain.tail(count);
let entries: Vec<serde_json::Value> = events.iter().map(|e| {
serde_json::json!({
"sequence": e.sequence,
"source": e.source,
"kind": e.kind,
"timestamp": e.timestamp.to_rfc3339(),
})
}).collect();
Ok(serde_json::json!({"events": entries, "count": entries.len()}))
}
}
#[cfg(feature = "exochain")]
pub struct SysTreeReadTool {
spec: BuiltinToolSpec,
tree: Arc<crate::tree_manager::TreeManager>,
}
#[cfg(feature = "exochain")]
impl SysTreeReadTool {
pub fn new(tree: Arc<crate::tree_manager::TreeManager>) -> Self {
let spec = builtin_tool_catalog().into_iter().find(|s| s.name == "sys.tree.read").unwrap();
Self { spec, tree }
}
}
#[cfg(feature = "exochain")]
impl BuiltinTool for SysTreeReadTool {
fn name(&self) -> &str { "sys.tree.read" }
fn spec(&self) -> &BuiltinToolSpec { &self.spec }
fn execute(&self, _args: serde_json::Value) -> Result<serde_json::Value, ToolError> {
let stats = self.tree.stats();
Ok(serde_json::json!({
"node_count": stats.node_count,
"mutation_count": stats.mutation_count,
"root_hash": stats.root_hash,
}))
}
}
#[cfg(feature = "exochain")]
pub struct SysTreeInspectTool {
spec: BuiltinToolSpec,
tree: Arc<crate::tree_manager::TreeManager>,
}
#[cfg(feature = "exochain")]
impl SysTreeInspectTool {
pub fn new(tree: Arc<crate::tree_manager::TreeManager>) -> Self {
let spec = builtin_tool_catalog().into_iter().find(|s| s.name == "sys.tree.inspect").unwrap();
Self { spec, tree }
}
}
#[cfg(feature = "exochain")]
impl BuiltinTool for SysTreeInspectTool {
fn name(&self) -> &str { "sys.tree.inspect" }
fn spec(&self) -> &BuiltinToolSpec { &self.spec }
fn execute(&self, args: serde_json::Value) -> Result<serde_json::Value, ToolError> {
let path = args.get("path").and_then(|v| v.as_str())
.ok_or_else(|| ToolError::InvalidArgs("missing 'path'".into()))?;
let rid = exo_resource_tree::ResourceId::new(path);
let tree_lock = self.tree.tree().lock()
.map_err(|e| ToolError::ExecutionFailed(format!("tree lock: {e}")))?;
let node = tree_lock.get(&rid)
.ok_or_else(|| ToolError::NotFound(format!("node not found: {path}")))?;
Ok(serde_json::json!({
"path": path,
"kind": format!("{:?}", node.kind),
"metadata": node.metadata,
"scoring": node.scoring.as_array(),
}))
}
}
pub struct SysEnvGetTool {
spec: BuiltinToolSpec,
}
impl SysEnvGetTool {
pub fn new() -> Self {
let spec = builtin_tool_catalog().into_iter().find(|s| s.name == "sys.env.get").unwrap();
Self { spec }
}
}
impl BuiltinTool for SysEnvGetTool {
fn name(&self) -> &str { "sys.env.get" }
fn spec(&self) -> &BuiltinToolSpec { &self.spec }
fn execute(&self, args: serde_json::Value) -> Result<serde_json::Value, ToolError> {
let name = args.get("name").and_then(|v| v.as_str())
.ok_or_else(|| ToolError::InvalidArgs("missing 'name'".into()))?;
match std::env::var(name) {
Ok(val) => Ok(serde_json::json!({"name": name, "value": val})),
Err(_) => Ok(serde_json::json!({"name": name, "value": null})),
}
}
}
pub struct SysCronAddTool {
spec: BuiltinToolSpec,
cron: Arc<crate::cron::CronService>,
}
impl SysCronAddTool {
pub fn new(cron: Arc<crate::cron::CronService>) -> Self {
let spec = builtin_tool_catalog().into_iter().find(|s| s.name == "sys.cron.add").unwrap();
Self { spec, cron }
}
}
impl BuiltinTool for SysCronAddTool {
fn name(&self) -> &str { "sys.cron.add" }
fn spec(&self) -> &BuiltinToolSpec { &self.spec }
fn execute(&self, args: serde_json::Value) -> Result<serde_json::Value, ToolError> {
let name = args.get("name").and_then(|v| v.as_str())
.ok_or_else(|| ToolError::InvalidArgs("missing 'name'".into()))?;
let interval_secs = args.get("interval_secs").and_then(|v| v.as_u64())
.ok_or_else(|| ToolError::InvalidArgs("missing 'interval_secs'".into()))?;
let command = args.get("command").and_then(|v| v.as_str())
.ok_or_else(|| ToolError::InvalidArgs("missing 'command'".into()))?;
let target_pid = args.get("target_pid").and_then(|v| v.as_u64());
let job = self.cron.add_job(name.to_string(), interval_secs, command.to_string(), target_pid);
Ok(serde_json::to_value(&job).unwrap_or_default())
}
}
pub struct SysCronListTool {
spec: BuiltinToolSpec,
cron: Arc<crate::cron::CronService>,
}
impl SysCronListTool {
pub fn new(cron: Arc<crate::cron::CronService>) -> Self {
let spec = builtin_tool_catalog().into_iter().find(|s| s.name == "sys.cron.list").unwrap();
Self { spec, cron }
}
}
impl BuiltinTool for SysCronListTool {
fn name(&self) -> &str { "sys.cron.list" }
fn spec(&self) -> &BuiltinToolSpec { &self.spec }
fn execute(&self, _args: serde_json::Value) -> Result<serde_json::Value, ToolError> {
let jobs = self.cron.list_jobs();
Ok(serde_json::to_value(&jobs).unwrap_or_default())
}
}
pub struct SysCronRemoveTool {
spec: BuiltinToolSpec,
cron: Arc<crate::cron::CronService>,
}
impl SysCronRemoveTool {
pub fn new(cron: Arc<crate::cron::CronService>) -> Self {
let spec = builtin_tool_catalog().into_iter().find(|s| s.name == "sys.cron.remove").unwrap();
Self { spec, cron }
}
}
impl BuiltinTool for SysCronRemoveTool {
fn name(&self) -> &str { "sys.cron.remove" }
fn spec(&self) -> &BuiltinToolSpec { &self.spec }
fn execute(&self, args: serde_json::Value) -> Result<serde_json::Value, ToolError> {
let id = args.get("id").and_then(|v| v.as_str())
.ok_or_else(|| ToolError::InvalidArgs("missing 'id'".into()))?;
match self.cron.remove_job(id) {
Some(job) => Ok(serde_json::json!({"removed": true, "job_id": job.id})),
None => Err(ToolError::NotFound(format!("cron job: {id}"))),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ShellCommand {
pub command: String,
pub args: Vec<String>,
pub sandbox_config: Option<SandboxConfig>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ShellResult {
pub exit_code: i32,
pub stdout: String,
pub stderr: String,
pub execution_time_ms: u64,
}
pub fn execute_shell(cmd: &ShellCommand) -> Result<ShellResult, ToolError> {
let start = std::time::Instant::now();
if let Some(ref sandbox) = cmd.sandbox_config {
if sandbox.sudo_override {
tracing::warn!(command = %cmd.command, "shell exec with sudo override");
}
}
let (exit_code, stdout, stderr) = match cmd.command.as_str() {
"echo" => {
let output = cmd.args.join(" ");
(0, output, String::new())
}
"true" => (0, String::new(), String::new()),
"false" => (1, String::new(), String::new()),
_ => {
(127, String::new(), format!("command not found: {}", cmd.command))
}
};
let elapsed = start.elapsed();
Ok(ShellResult {
exit_code,
stdout,
stderr,
execution_time_ms: elapsed.as_millis() as u64,
})
}
pub struct ShellExecTool {
spec: BuiltinToolSpec,
}
impl ShellExecTool {
pub fn new() -> Self {
Self {
spec: BuiltinToolSpec {
name: "shell.exec".into(),
category: ToolCategory::System,
description: "Execute a shell command in the sandbox".into(),
parameters: serde_json::json!({
"type": "object",
"required": ["command"],
"properties": {
"command": {"type": "string", "description": "Command to execute"},
"args": {
"type": "array",
"items": {"type": "string"},
"description": "Command arguments"
}
}
}),
gate_action: "tool.shell.execute".into(),
effect: EffectVector {
risk: 0.7,
security: 0.4,
..Default::default()
},
native: true,
},
}
}
}
impl BuiltinTool for ShellExecTool {
fn name(&self) -> &str { "shell.exec" }
fn spec(&self) -> &BuiltinToolSpec { &self.spec }
fn execute(&self, args: serde_json::Value) -> Result<serde_json::Value, ToolError> {
let command = args.get("command").and_then(|v| v.as_str())
.ok_or_else(|| ToolError::InvalidArgs("missing 'command'".into()))?;
let cmd_args: Vec<String> = args.get("args")
.and_then(|v| serde_json::from_value(v.clone()).ok())
.unwrap_or_default();
let cmd = ShellCommand {
command: command.to_string(),
args: cmd_args,
sandbox_config: None,
};
let result = execute_shell(&cmd)?;
Ok(serde_json::json!({
"exit_code": result.exit_code,
"stdout": result.stdout,
"stderr": result.stderr,
"execution_time_ms": result.execution_time_ms,
}))
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ShellPipeline {
pub name: String,
pub command: String,
pub content_hash: [u8; 32],
pub chain_seq: Option<u64>,
}
impl ShellPipeline {
pub fn new(name: impl Into<String>, command: impl Into<String>) -> Self {
let cmd = command.into();
let hash = compute_module_hash(cmd.as_bytes());
Self {
name: name.into(),
command: cmd,
content_hash: hash,
chain_seq: None,
}
}
#[cfg(feature = "exochain")]
pub fn anchor_to_chain(&mut self, chain: &crate::chain::ChainManager) {
let seq = chain.sequence();
let hash_hex: String = self
.content_hash
.iter()
.map(|b| format!("{b:02x}"))
.collect();
chain.append(
"shell",
"shell.pipeline.register",
Some(serde_json::json!({
"name": &self.name,
"command_hash": hash_hex,
"command_length": self.command.len(),
})),
);
self.chain_seq = Some(seq);
}
pub fn to_tool_spec(&self) -> BuiltinToolSpec {
BuiltinToolSpec {
name: format!("shell.{}", self.name),
category: ToolCategory::User,
description: format!("Shell pipeline: {}", self.name),
parameters: serde_json::json!({
"type": "object",
"properties": {
"args": {"type": "string", "description": "Additional arguments"}
}
}),
gate_action: "tool.shell.execute".into(),
effect: EffectVector {
risk: 0.6,
security: 0.3,
..Default::default()
},
native: true,
}
}
}