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()
26 .into_iter()
27 .find(|s| s.name == "sys.service.list")
28 .unwrap();
29 Self {
30 spec,
31 service_registry,
32 }
33 }
34}
35
36impl BuiltinTool for SysServiceListTool {
37 fn name(&self) -> &str {
38 "sys.service.list"
39 }
40 fn spec(&self) -> &BuiltinToolSpec {
41 &self.spec
42 }
43 fn execute(&self, _args: serde_json::Value) -> Result<serde_json::Value, ToolError> {
44 let services = self.service_registry.list();
45 let entries: Vec<serde_json::Value> = services
46 .iter()
47 .map(|(name, stype)| {
48 serde_json::json!({
49 "name": name,
50 "service_type": format!("{stype:?}"),
51 })
52 })
53 .collect();
54 Ok(serde_json::json!({"services": entries, "count": entries.len()}))
55 }
56}
57
58pub struct SysServiceHealthTool {
60 spec: BuiltinToolSpec,
61 service_registry: Arc<crate::service::ServiceRegistry>,
62}
63
64impl SysServiceHealthTool {
65 pub fn new(service_registry: Arc<crate::service::ServiceRegistry>) -> Self {
66 let spec = builtin_tool_catalog()
67 .into_iter()
68 .find(|s| s.name == "sys.service.health")
69 .unwrap();
70 Self {
71 spec,
72 service_registry,
73 }
74 }
75}
76
77impl BuiltinTool for SysServiceHealthTool {
78 fn name(&self) -> &str {
79 "sys.service.health"
80 }
81 fn spec(&self) -> &BuiltinToolSpec {
82 &self.spec
83 }
84 fn execute(&self, _args: serde_json::Value) -> Result<serde_json::Value, ToolError> {
85 let services = self.service_registry.list();
87 let entries: Vec<serde_json::Value> = services
88 .iter()
89 .map(|(name, _)| serde_json::json!({"name": name, "status": "registered"}))
90 .collect();
91 Ok(serde_json::json!({"health": entries, "count": entries.len()}))
92 }
93}
94
95#[cfg(feature = "exochain")]
101pub struct SysChainStatusTool {
102 spec: BuiltinToolSpec,
103 chain: Arc<crate::chain::ChainManager>,
104}
105
106#[cfg(feature = "exochain")]
107impl SysChainStatusTool {
108 pub fn new(chain: Arc<crate::chain::ChainManager>) -> Self {
109 let spec = builtin_tool_catalog()
110 .into_iter()
111 .find(|s| s.name == "sys.chain.status")
112 .unwrap();
113 Self { spec, chain }
114 }
115}
116
117#[cfg(feature = "exochain")]
118impl BuiltinTool for SysChainStatusTool {
119 fn name(&self) -> &str {
120 "sys.chain.status"
121 }
122 fn spec(&self) -> &BuiltinToolSpec {
123 &self.spec
124 }
125 fn execute(&self, _args: serde_json::Value) -> Result<serde_json::Value, ToolError> {
126 let status = self.chain.status();
127 Ok(serde_json::json!({
128 "chain_id": status.chain_id,
129 "sequence": status.sequence,
130 "event_count": status.event_count,
131 "checkpoint_count": status.checkpoint_count,
132 }))
133 }
134}
135
136#[cfg(feature = "exochain")]
138pub struct SysChainQueryTool {
139 spec: BuiltinToolSpec,
140 chain: Arc<crate::chain::ChainManager>,
141}
142
143#[cfg(feature = "exochain")]
144impl SysChainQueryTool {
145 pub fn new(chain: Arc<crate::chain::ChainManager>) -> Self {
146 let spec = builtin_tool_catalog()
147 .into_iter()
148 .find(|s| s.name == "sys.chain.query")
149 .unwrap();
150 Self { spec, chain }
151 }
152}
153
154#[cfg(feature = "exochain")]
155impl BuiltinTool for SysChainQueryTool {
156 fn name(&self) -> &str {
157 "sys.chain.query"
158 }
159 fn spec(&self) -> &BuiltinToolSpec {
160 &self.spec
161 }
162 fn execute(&self, args: serde_json::Value) -> Result<serde_json::Value, ToolError> {
163 let count = args.get("count").and_then(|v| v.as_u64()).unwrap_or(20) as usize;
164 let events = self.chain.tail(count);
165 let entries: Vec<serde_json::Value> = events
166 .iter()
167 .map(|e| {
168 serde_json::json!({
169 "sequence": e.sequence,
170 "source": e.source,
171 "kind": e.kind,
172 "timestamp": e.timestamp.to_rfc3339(),
173 })
174 })
175 .collect();
176 Ok(serde_json::json!({"events": entries, "count": entries.len()}))
177 }
178}
179
180#[cfg(feature = "exochain")]
186pub struct SysTreeReadTool {
187 spec: BuiltinToolSpec,
188 tree: Arc<crate::tree_manager::TreeManager>,
189}
190
191#[cfg(feature = "exochain")]
192impl SysTreeReadTool {
193 pub fn new(tree: Arc<crate::tree_manager::TreeManager>) -> Self {
194 let spec = builtin_tool_catalog()
195 .into_iter()
196 .find(|s| s.name == "sys.tree.read")
197 .unwrap();
198 Self { spec, tree }
199 }
200}
201
202#[cfg(feature = "exochain")]
203impl BuiltinTool for SysTreeReadTool {
204 fn name(&self) -> &str {
205 "sys.tree.read"
206 }
207 fn spec(&self) -> &BuiltinToolSpec {
208 &self.spec
209 }
210 fn execute(&self, _args: serde_json::Value) -> Result<serde_json::Value, ToolError> {
211 let stats = self.tree.stats();
212 Ok(serde_json::json!({
213 "node_count": stats.node_count,
214 "mutation_count": stats.mutation_count,
215 "root_hash": stats.root_hash,
216 }))
217 }
218}
219
220#[cfg(feature = "exochain")]
222pub struct SysTreeInspectTool {
223 spec: BuiltinToolSpec,
224 tree: Arc<crate::tree_manager::TreeManager>,
225}
226
227#[cfg(feature = "exochain")]
228impl SysTreeInspectTool {
229 pub fn new(tree: Arc<crate::tree_manager::TreeManager>) -> Self {
230 let spec = builtin_tool_catalog()
231 .into_iter()
232 .find(|s| s.name == "sys.tree.inspect")
233 .unwrap();
234 Self { spec, tree }
235 }
236}
237
238#[cfg(feature = "exochain")]
239impl BuiltinTool for SysTreeInspectTool {
240 fn name(&self) -> &str {
241 "sys.tree.inspect"
242 }
243 fn spec(&self) -> &BuiltinToolSpec {
244 &self.spec
245 }
246 fn execute(&self, args: serde_json::Value) -> Result<serde_json::Value, ToolError> {
247 let path = args
248 .get("path")
249 .and_then(|v| v.as_str())
250 .ok_or_else(|| ToolError::InvalidArgs("missing 'path'".into()))?;
251 let rid = exo_resource_tree::ResourceId::new(path);
252 let tree_lock = self
253 .tree
254 .tree()
255 .lock()
256 .map_err(|e| ToolError::ExecutionFailed(format!("tree lock: {e}")))?;
257 let node = tree_lock
258 .get(&rid)
259 .ok_or_else(|| ToolError::NotFound(format!("node not found: {path}")))?;
260 Ok(serde_json::json!({
261 "path": path,
262 "kind": format!("{:?}", node.kind),
263 "metadata": node.metadata,
264 "scoring": node.scoring.as_array(),
265 }))
266 }
267}
268
269pub struct SysEnvGetTool {
275 spec: BuiltinToolSpec,
276}
277
278impl SysEnvGetTool {
279 pub fn new() -> Self {
280 let spec = builtin_tool_catalog()
281 .into_iter()
282 .find(|s| s.name == "sys.env.get")
283 .unwrap();
284 Self { spec }
285 }
286}
287
288impl BuiltinTool for SysEnvGetTool {
289 fn name(&self) -> &str {
290 "sys.env.get"
291 }
292 fn spec(&self) -> &BuiltinToolSpec {
293 &self.spec
294 }
295 fn execute(&self, args: serde_json::Value) -> Result<serde_json::Value, ToolError> {
296 let name = args
297 .get("name")
298 .and_then(|v| v.as_str())
299 .ok_or_else(|| ToolError::InvalidArgs("missing 'name'".into()))?;
300 match std::env::var(name) {
301 Ok(val) => Ok(serde_json::json!({"name": name, "value": val})),
302 Err(_) => Ok(serde_json::json!({"name": name, "value": null})),
303 }
304 }
305}
306
307pub struct SysCronAddTool {
309 spec: BuiltinToolSpec,
310 cron: Arc<crate::cron::CronService>,
311}
312
313impl SysCronAddTool {
314 pub fn new(cron: Arc<crate::cron::CronService>) -> Self {
315 let spec = builtin_tool_catalog()
316 .into_iter()
317 .find(|s| s.name == "sys.cron.add")
318 .unwrap();
319 Self { spec, cron }
320 }
321}
322
323impl BuiltinTool for SysCronAddTool {
324 fn name(&self) -> &str {
325 "sys.cron.add"
326 }
327 fn spec(&self) -> &BuiltinToolSpec {
328 &self.spec
329 }
330 fn execute(&self, args: serde_json::Value) -> Result<serde_json::Value, ToolError> {
331 let name = args
332 .get("name")
333 .and_then(|v| v.as_str())
334 .ok_or_else(|| ToolError::InvalidArgs("missing 'name'".into()))?;
335 let interval_secs = args
336 .get("interval_secs")
337 .and_then(|v| v.as_u64())
338 .ok_or_else(|| ToolError::InvalidArgs("missing 'interval_secs'".into()))?;
339 let command = args
340 .get("command")
341 .and_then(|v| v.as_str())
342 .ok_or_else(|| ToolError::InvalidArgs("missing 'command'".into()))?;
343 let target_pid = args.get("target_pid").and_then(|v| v.as_u64());
344 let job = self
345 .cron
346 .add_job(
347 name.to_string(),
348 interval_secs,
349 command.to_string(),
350 target_pid,
351 )
352 .map_err(|e| ToolError::ExecutionFailed(e.to_string()))?;
353 Ok(serde_json::to_value(&job).unwrap_or_default())
354 }
355}
356
357pub struct SysCronListTool {
359 spec: BuiltinToolSpec,
360 cron: Arc<crate::cron::CronService>,
361}
362
363impl SysCronListTool {
364 pub fn new(cron: Arc<crate::cron::CronService>) -> Self {
365 let spec = builtin_tool_catalog()
366 .into_iter()
367 .find(|s| s.name == "sys.cron.list")
368 .unwrap();
369 Self { spec, cron }
370 }
371}
372
373impl BuiltinTool for SysCronListTool {
374 fn name(&self) -> &str {
375 "sys.cron.list"
376 }
377 fn spec(&self) -> &BuiltinToolSpec {
378 &self.spec
379 }
380 fn execute(&self, _args: serde_json::Value) -> Result<serde_json::Value, ToolError> {
381 let jobs = self.cron.list_jobs();
382 Ok(serde_json::to_value(&jobs).unwrap_or_default())
383 }
384}
385
386pub struct SysCronRemoveTool {
388 spec: BuiltinToolSpec,
389 cron: Arc<crate::cron::CronService>,
390}
391
392impl SysCronRemoveTool {
393 pub fn new(cron: Arc<crate::cron::CronService>) -> Self {
394 let spec = builtin_tool_catalog()
395 .into_iter()
396 .find(|s| s.name == "sys.cron.remove")
397 .unwrap();
398 Self { spec, cron }
399 }
400}
401
402impl BuiltinTool for SysCronRemoveTool {
403 fn name(&self) -> &str {
404 "sys.cron.remove"
405 }
406 fn spec(&self) -> &BuiltinToolSpec {
407 &self.spec
408 }
409 fn execute(&self, args: serde_json::Value) -> Result<serde_json::Value, ToolError> {
410 let id = args
411 .get("id")
412 .and_then(|v| v.as_str())
413 .ok_or_else(|| ToolError::InvalidArgs("missing 'id'".into()))?;
414 match self.cron.remove_job(id) {
415 Ok(Some(job)) => Ok(serde_json::json!({"removed": true, "job_id": job.id})),
416 Ok(None) => Err(ToolError::NotFound(format!("cron job: {id}"))),
417 Err(e) => Err(ToolError::PermissionDenied(format!("{e}"))),
418 }
419 }
420}
421
422#[derive(Debug, Clone, Serialize, Deserialize)]
431pub struct ShellCommand {
432 pub command: String,
434 pub args: Vec<String>,
436 pub sandbox_config: Option<SandboxConfig>,
438}
439
440#[derive(Debug, Clone, Serialize, Deserialize)]
442pub struct ShellResult {
443 pub exit_code: i32,
445 pub stdout: String,
447 pub stderr: String,
449 pub execution_time_ms: u64,
451}
452
453pub fn execute_shell(cmd: &ShellCommand) -> Result<ShellResult, ToolError> {
462 let start = std::time::Instant::now();
463
464 if let Some(ref sandbox) = cmd.sandbox_config
467 && sandbox.sudo_override
468 {
469 tracing::warn!(command = %cmd.command, "shell exec with sudo override");
470 }
471
472 let (exit_code, stdout, stderr) = match cmd.command.as_str() {
475 "echo" => {
476 let output = cmd.args.join(" ");
477 (0, output, String::new())
478 }
479 "true" => (0, String::new(), String::new()),
480 "false" => (1, String::new(), String::new()),
481 _ => {
482 (
485 127,
486 String::new(),
487 format!("command not found: {}", cmd.command),
488 )
489 }
490 };
491
492 let elapsed = start.elapsed();
493
494 Ok(ShellResult {
495 exit_code,
496 stdout,
497 stderr,
498 execution_time_ms: elapsed.as_millis() as u64,
499 })
500}
501
502pub struct ShellExecTool {
504 spec: BuiltinToolSpec,
505}
506
507impl ShellExecTool {
508 pub fn new() -> Self {
510 Self {
511 spec: BuiltinToolSpec {
512 name: "shell.exec".into(),
513 category: ToolCategory::System,
514 description: "Execute a shell command in the sandbox".into(),
515 parameters: serde_json::json!({
516 "type": "object",
517 "required": ["command"],
518 "properties": {
519 "command": {"type": "string", "description": "Command to execute"},
520 "args": {
521 "type": "array",
522 "items": {"type": "string"},
523 "description": "Command arguments"
524 }
525 }
526 }),
527 gate_action: "tool.shell.execute".into(),
528 effect: EffectVector {
529 risk: 0.7,
530 security: 0.4,
531 ..Default::default()
532 },
533 native: true,
534 },
535 }
536 }
537}
538
539impl BuiltinTool for ShellExecTool {
540 fn name(&self) -> &str {
541 "shell.exec"
542 }
543 fn spec(&self) -> &BuiltinToolSpec {
544 &self.spec
545 }
546 fn execute(&self, args: serde_json::Value) -> Result<serde_json::Value, ToolError> {
547 let command = args
548 .get("command")
549 .and_then(|v| v.as_str())
550 .ok_or_else(|| ToolError::InvalidArgs("missing 'command'".into()))?;
551 let cmd_args: Vec<String> = args
552 .get("args")
553 .and_then(|v| serde_json::from_value(v.clone()).ok())
554 .unwrap_or_default();
555
556 let cmd = ShellCommand {
557 command: command.to_string(),
558 args: cmd_args,
559 sandbox_config: None,
560 };
561
562 let result = execute_shell(&cmd)?;
563 Ok(serde_json::json!({
564 "exit_code": result.exit_code,
565 "stdout": result.stdout,
566 "stderr": result.stderr,
567 "execution_time_ms": result.execution_time_ms,
568 }))
569 }
570}
571
572#[derive(Debug, Clone, Serialize, Deserialize)]
581pub struct ShellPipeline {
582 pub name: String,
584 pub command: String,
586 pub content_hash: [u8; 32],
588 pub chain_seq: Option<u64>,
590}
591
592impl ShellPipeline {
593 pub fn new(name: impl Into<String>, command: impl Into<String>) -> Self {
595 let cmd = command.into();
596 let hash = compute_module_hash(cmd.as_bytes());
597 Self {
598 name: name.into(),
599 command: cmd,
600 content_hash: hash,
601 chain_seq: None,
602 }
603 }
604
605 #[cfg(feature = "exochain")]
607 pub fn anchor_to_chain(&mut self, chain: &crate::chain::ChainManager) {
608 let seq = chain.sequence();
609 let hash_hex: String = self
610 .content_hash
611 .iter()
612 .map(|b| format!("{b:02x}"))
613 .collect();
614 chain.append(
615 "shell",
616 "shell.pipeline.register",
617 Some(serde_json::json!({
618 "name": &self.name,
619 "command_hash": hash_hex,
620 "command_length": self.command.len(),
621 })),
622 );
623 self.chain_seq = Some(seq);
624 }
625
626 pub fn to_tool_spec(&self) -> BuiltinToolSpec {
628 BuiltinToolSpec {
629 name: format!("shell.{}", self.name),
630 category: ToolCategory::User,
631 description: format!("Shell pipeline: {}", self.name),
632 parameters: serde_json::json!({
633 "type": "object",
634 "properties": {
635 "args": {"type": "string", "description": "Additional arguments"}
636 }
637 }),
638 gate_action: "tool.shell.execute".into(),
639 effect: EffectVector {
640 risk: 0.6,
641 security: 0.3,
642 ..Default::default()
643 },
644 native: true,
645 }
646 }
647}