1use std::sync::Arc;
4
5use serde::{Deserialize, Serialize};
6
7use super::catalog::builtin_tool_catalog;
8use super::registry::BuiltinTool;
9use super::runner::compute_module_hash;
10use super::types::*;
11use crate::governance::EffectVector;
12
13pub struct SysServiceListTool {
19 spec: BuiltinToolSpec,
20 service_registry: Arc<crate::service::ServiceRegistry>,
21}
22
23impl SysServiceListTool {
24 pub fn new(service_registry: Arc<crate::service::ServiceRegistry>) -> Self {
25 let spec = builtin_tool_catalog().into_iter().find(|s| s.name == "sys.service.list").unwrap();
26 Self { spec, service_registry }
27 }
28}
29
30impl BuiltinTool for SysServiceListTool {
31 fn name(&self) -> &str { "sys.service.list" }
32 fn spec(&self) -> &BuiltinToolSpec { &self.spec }
33 fn execute(&self, _args: serde_json::Value) -> Result<serde_json::Value, ToolError> {
34 let services = self.service_registry.list();
35 let entries: Vec<serde_json::Value> = services.iter().map(|(name, stype)| {
36 serde_json::json!({
37 "name": name,
38 "service_type": format!("{stype:?}"),
39 })
40 }).collect();
41 Ok(serde_json::json!({"services": entries, "count": entries.len()}))
42 }
43}
44
45pub struct SysServiceHealthTool {
47 spec: BuiltinToolSpec,
48 service_registry: Arc<crate::service::ServiceRegistry>,
49}
50
51impl SysServiceHealthTool {
52 pub fn new(service_registry: Arc<crate::service::ServiceRegistry>) -> Self {
53 let spec = builtin_tool_catalog().into_iter().find(|s| s.name == "sys.service.health").unwrap();
54 Self { spec, service_registry }
55 }
56}
57
58impl BuiltinTool for SysServiceHealthTool {
59 fn name(&self) -> &str { "sys.service.health" }
60 fn spec(&self) -> &BuiltinToolSpec { &self.spec }
61 fn execute(&self, _args: serde_json::Value) -> Result<serde_json::Value, ToolError> {
62 let services = self.service_registry.list();
64 let entries: Vec<serde_json::Value> = services.iter().map(|(name, _)| {
65 serde_json::json!({"name": name, "status": "registered"})
66 }).collect();
67 Ok(serde_json::json!({"health": entries, "count": entries.len()}))
68 }
69}
70
71#[cfg(feature = "exochain")]
77pub struct SysChainStatusTool {
78 spec: BuiltinToolSpec,
79 chain: Arc<crate::chain::ChainManager>,
80}
81
82#[cfg(feature = "exochain")]
83impl SysChainStatusTool {
84 pub fn new(chain: Arc<crate::chain::ChainManager>) -> Self {
85 let spec = builtin_tool_catalog().into_iter().find(|s| s.name == "sys.chain.status").unwrap();
86 Self { spec, chain }
87 }
88}
89
90#[cfg(feature = "exochain")]
91impl BuiltinTool for SysChainStatusTool {
92 fn name(&self) -> &str { "sys.chain.status" }
93 fn spec(&self) -> &BuiltinToolSpec { &self.spec }
94 fn execute(&self, _args: serde_json::Value) -> Result<serde_json::Value, ToolError> {
95 let status = self.chain.status();
96 Ok(serde_json::json!({
97 "chain_id": status.chain_id,
98 "sequence": status.sequence,
99 "event_count": status.event_count,
100 "checkpoint_count": status.checkpoint_count,
101 }))
102 }
103}
104
105#[cfg(feature = "exochain")]
107pub struct SysChainQueryTool {
108 spec: BuiltinToolSpec,
109 chain: Arc<crate::chain::ChainManager>,
110}
111
112#[cfg(feature = "exochain")]
113impl SysChainQueryTool {
114 pub fn new(chain: Arc<crate::chain::ChainManager>) -> Self {
115 let spec = builtin_tool_catalog().into_iter().find(|s| s.name == "sys.chain.query").unwrap();
116 Self { spec, chain }
117 }
118}
119
120#[cfg(feature = "exochain")]
121impl BuiltinTool for SysChainQueryTool {
122 fn name(&self) -> &str { "sys.chain.query" }
123 fn spec(&self) -> &BuiltinToolSpec { &self.spec }
124 fn execute(&self, args: serde_json::Value) -> Result<serde_json::Value, ToolError> {
125 let count = args.get("count").and_then(|v| v.as_u64()).unwrap_or(20) as usize;
126 let events = self.chain.tail(count);
127 let entries: Vec<serde_json::Value> = events.iter().map(|e| {
128 serde_json::json!({
129 "sequence": e.sequence,
130 "source": e.source,
131 "kind": e.kind,
132 "timestamp": e.timestamp.to_rfc3339(),
133 })
134 }).collect();
135 Ok(serde_json::json!({"events": entries, "count": entries.len()}))
136 }
137}
138
139#[cfg(feature = "exochain")]
145pub struct SysTreeReadTool {
146 spec: BuiltinToolSpec,
147 tree: Arc<crate::tree_manager::TreeManager>,
148}
149
150#[cfg(feature = "exochain")]
151impl SysTreeReadTool {
152 pub fn new(tree: Arc<crate::tree_manager::TreeManager>) -> Self {
153 let spec = builtin_tool_catalog().into_iter().find(|s| s.name == "sys.tree.read").unwrap();
154 Self { spec, tree }
155 }
156}
157
158#[cfg(feature = "exochain")]
159impl BuiltinTool for SysTreeReadTool {
160 fn name(&self) -> &str { "sys.tree.read" }
161 fn spec(&self) -> &BuiltinToolSpec { &self.spec }
162 fn execute(&self, _args: serde_json::Value) -> Result<serde_json::Value, ToolError> {
163 let stats = self.tree.stats();
164 Ok(serde_json::json!({
165 "node_count": stats.node_count,
166 "mutation_count": stats.mutation_count,
167 "root_hash": stats.root_hash,
168 }))
169 }
170}
171
172#[cfg(feature = "exochain")]
174pub struct SysTreeInspectTool {
175 spec: BuiltinToolSpec,
176 tree: Arc<crate::tree_manager::TreeManager>,
177}
178
179#[cfg(feature = "exochain")]
180impl SysTreeInspectTool {
181 pub fn new(tree: Arc<crate::tree_manager::TreeManager>) -> Self {
182 let spec = builtin_tool_catalog().into_iter().find(|s| s.name == "sys.tree.inspect").unwrap();
183 Self { spec, tree }
184 }
185}
186
187#[cfg(feature = "exochain")]
188impl BuiltinTool for SysTreeInspectTool {
189 fn name(&self) -> &str { "sys.tree.inspect" }
190 fn spec(&self) -> &BuiltinToolSpec { &self.spec }
191 fn execute(&self, args: serde_json::Value) -> Result<serde_json::Value, ToolError> {
192 let path = args.get("path").and_then(|v| v.as_str())
193 .ok_or_else(|| ToolError::InvalidArgs("missing 'path'".into()))?;
194 let rid = exo_resource_tree::ResourceId::new(path);
195 let tree_lock = self.tree.tree().lock()
196 .map_err(|e| ToolError::ExecutionFailed(format!("tree lock: {e}")))?;
197 let node = tree_lock.get(&rid)
198 .ok_or_else(|| ToolError::NotFound(format!("node not found: {path}")))?;
199 Ok(serde_json::json!({
200 "path": path,
201 "kind": format!("{:?}", node.kind),
202 "metadata": node.metadata,
203 "scoring": node.scoring.as_array(),
204 }))
205 }
206}
207
208pub struct SysEnvGetTool {
214 spec: BuiltinToolSpec,
215}
216
217impl SysEnvGetTool {
218 pub fn new() -> Self {
219 let spec = builtin_tool_catalog().into_iter().find(|s| s.name == "sys.env.get").unwrap();
220 Self { spec }
221 }
222}
223
224impl BuiltinTool for SysEnvGetTool {
225 fn name(&self) -> &str { "sys.env.get" }
226 fn spec(&self) -> &BuiltinToolSpec { &self.spec }
227 fn execute(&self, args: serde_json::Value) -> Result<serde_json::Value, ToolError> {
228 let name = args.get("name").and_then(|v| v.as_str())
229 .ok_or_else(|| ToolError::InvalidArgs("missing 'name'".into()))?;
230 match std::env::var(name) {
231 Ok(val) => Ok(serde_json::json!({"name": name, "value": val})),
232 Err(_) => Ok(serde_json::json!({"name": name, "value": null})),
233 }
234 }
235}
236
237pub struct SysCronAddTool {
239 spec: BuiltinToolSpec,
240 cron: Arc<crate::cron::CronService>,
241}
242
243impl SysCronAddTool {
244 pub fn new(cron: Arc<crate::cron::CronService>) -> Self {
245 let spec = builtin_tool_catalog().into_iter().find(|s| s.name == "sys.cron.add").unwrap();
246 Self { spec, cron }
247 }
248}
249
250impl BuiltinTool for SysCronAddTool {
251 fn name(&self) -> &str { "sys.cron.add" }
252 fn spec(&self) -> &BuiltinToolSpec { &self.spec }
253 fn execute(&self, args: serde_json::Value) -> Result<serde_json::Value, ToolError> {
254 let name = args.get("name").and_then(|v| v.as_str())
255 .ok_or_else(|| ToolError::InvalidArgs("missing 'name'".into()))?;
256 let interval_secs = args.get("interval_secs").and_then(|v| v.as_u64())
257 .ok_or_else(|| ToolError::InvalidArgs("missing 'interval_secs'".into()))?;
258 let command = args.get("command").and_then(|v| v.as_str())
259 .ok_or_else(|| ToolError::InvalidArgs("missing 'command'".into()))?;
260 let target_pid = args.get("target_pid").and_then(|v| v.as_u64());
261 let job = self.cron.add_job(name.to_string(), interval_secs, command.to_string(), target_pid);
262 Ok(serde_json::to_value(&job).unwrap_or_default())
263 }
264}
265
266pub struct SysCronListTool {
268 spec: BuiltinToolSpec,
269 cron: Arc<crate::cron::CronService>,
270}
271
272impl SysCronListTool {
273 pub fn new(cron: Arc<crate::cron::CronService>) -> Self {
274 let spec = builtin_tool_catalog().into_iter().find(|s| s.name == "sys.cron.list").unwrap();
275 Self { spec, cron }
276 }
277}
278
279impl BuiltinTool for SysCronListTool {
280 fn name(&self) -> &str { "sys.cron.list" }
281 fn spec(&self) -> &BuiltinToolSpec { &self.spec }
282 fn execute(&self, _args: serde_json::Value) -> Result<serde_json::Value, ToolError> {
283 let jobs = self.cron.list_jobs();
284 Ok(serde_json::to_value(&jobs).unwrap_or_default())
285 }
286}
287
288pub struct SysCronRemoveTool {
290 spec: BuiltinToolSpec,
291 cron: Arc<crate::cron::CronService>,
292}
293
294impl SysCronRemoveTool {
295 pub fn new(cron: Arc<crate::cron::CronService>) -> Self {
296 let spec = builtin_tool_catalog().into_iter().find(|s| s.name == "sys.cron.remove").unwrap();
297 Self { spec, cron }
298 }
299}
300
301impl BuiltinTool for SysCronRemoveTool {
302 fn name(&self) -> &str { "sys.cron.remove" }
303 fn spec(&self) -> &BuiltinToolSpec { &self.spec }
304 fn execute(&self, args: serde_json::Value) -> Result<serde_json::Value, ToolError> {
305 let id = args.get("id").and_then(|v| v.as_str())
306 .ok_or_else(|| ToolError::InvalidArgs("missing 'id'".into()))?;
307 match self.cron.remove_job(id) {
308 Some(job) => Ok(serde_json::json!({"removed": true, "job_id": job.id})),
309 None => Err(ToolError::NotFound(format!("cron job: {id}"))),
310 }
311 }
312}
313
314#[derive(Debug, Clone, Serialize, Deserialize)]
323pub struct ShellCommand {
324 pub command: String,
326 pub args: Vec<String>,
328 pub sandbox_config: Option<SandboxConfig>,
330}
331
332#[derive(Debug, Clone, Serialize, Deserialize)]
334pub struct ShellResult {
335 pub exit_code: i32,
337 pub stdout: String,
339 pub stderr: String,
341 pub execution_time_ms: u64,
343}
344
345pub fn execute_shell(cmd: &ShellCommand) -> Result<ShellResult, ToolError> {
354 let start = std::time::Instant::now();
355
356 if let Some(ref sandbox) = cmd.sandbox_config {
359 if sandbox.sudo_override {
360 tracing::warn!(command = %cmd.command, "shell exec with sudo override");
361 }
362 }
363
364 let (exit_code, stdout, stderr) = match cmd.command.as_str() {
367 "echo" => {
368 let output = cmd.args.join(" ");
369 (0, output, String::new())
370 }
371 "true" => (0, String::new(), String::new()),
372 "false" => (1, String::new(), String::new()),
373 _ => {
374 (127, String::new(), format!("command not found: {}", cmd.command))
377 }
378 };
379
380 let elapsed = start.elapsed();
381
382 Ok(ShellResult {
383 exit_code,
384 stdout,
385 stderr,
386 execution_time_ms: elapsed.as_millis() as u64,
387 })
388}
389
390pub struct ShellExecTool {
392 spec: BuiltinToolSpec,
393}
394
395impl ShellExecTool {
396 pub fn new() -> Self {
398 Self {
399 spec: BuiltinToolSpec {
400 name: "shell.exec".into(),
401 category: ToolCategory::System,
402 description: "Execute a shell command in the sandbox".into(),
403 parameters: serde_json::json!({
404 "type": "object",
405 "required": ["command"],
406 "properties": {
407 "command": {"type": "string", "description": "Command to execute"},
408 "args": {
409 "type": "array",
410 "items": {"type": "string"},
411 "description": "Command arguments"
412 }
413 }
414 }),
415 gate_action: "tool.shell.execute".into(),
416 effect: EffectVector {
417 risk: 0.7,
418 security: 0.4,
419 ..Default::default()
420 },
421 native: true,
422 },
423 }
424 }
425}
426
427impl BuiltinTool for ShellExecTool {
428 fn name(&self) -> &str { "shell.exec" }
429 fn spec(&self) -> &BuiltinToolSpec { &self.spec }
430 fn execute(&self, args: serde_json::Value) -> Result<serde_json::Value, ToolError> {
431 let command = args.get("command").and_then(|v| v.as_str())
432 .ok_or_else(|| ToolError::InvalidArgs("missing 'command'".into()))?;
433 let cmd_args: Vec<String> = args.get("args")
434 .and_then(|v| serde_json::from_value(v.clone()).ok())
435 .unwrap_or_default();
436
437 let cmd = ShellCommand {
438 command: command.to_string(),
439 args: cmd_args,
440 sandbox_config: None,
441 };
442
443 let result = execute_shell(&cmd)?;
444 Ok(serde_json::json!({
445 "exit_code": result.exit_code,
446 "stdout": result.stdout,
447 "stderr": result.stderr,
448 "execution_time_ms": result.execution_time_ms,
449 }))
450 }
451}
452
453#[derive(Debug, Clone, Serialize, Deserialize)]
462pub struct ShellPipeline {
463 pub name: String,
465 pub command: String,
467 pub content_hash: [u8; 32],
469 pub chain_seq: Option<u64>,
471}
472
473impl ShellPipeline {
474 pub fn new(name: impl Into<String>, command: impl Into<String>) -> Self {
476 let cmd = command.into();
477 let hash = compute_module_hash(cmd.as_bytes());
478 Self {
479 name: name.into(),
480 command: cmd,
481 content_hash: hash,
482 chain_seq: None,
483 }
484 }
485
486 #[cfg(feature = "exochain")]
488 pub fn anchor_to_chain(&mut self, chain: &crate::chain::ChainManager) {
489 let seq = chain.sequence();
490 let hash_hex: String = self
491 .content_hash
492 .iter()
493 .map(|b| format!("{b:02x}"))
494 .collect();
495 chain.append(
496 "shell",
497 "shell.pipeline.register",
498 Some(serde_json::json!({
499 "name": &self.name,
500 "command_hash": hash_hex,
501 "command_length": self.command.len(),
502 })),
503 );
504 self.chain_seq = Some(seq);
505 }
506
507 pub fn to_tool_spec(&self) -> BuiltinToolSpec {
509 BuiltinToolSpec {
510 name: format!("shell.{}", self.name),
511 category: ToolCategory::User,
512 description: format!("Shell pipeline: {}", self.name),
513 parameters: serde_json::json!({
514 "type": "object",
515 "properties": {
516 "args": {"type": "string", "description": "Additional arguments"}
517 }
518 }),
519 gate_action: "tool.shell.execute".into(),
520 effect: EffectVector {
521 risk: 0.6,
522 security: 0.3,
523 ..Default::default()
524 },
525 native: true,
526 }
527 }
528}