disyn_runtime/
executor.rs1use async_trait::async_trait;
2use disyn_core::Result;
3use disyn_core::ports::ActionExecutor;
4use disyn_core::types::{ApprovedPlan, ExecutionReport, ResourceUsage, StepResult};
5
6pub struct ShellExecutor;
7
8impl ShellExecutor {
9 pub fn new() -> Self {
10 Self
11 }
12}
13
14impl Default for ShellExecutor {
15 fn default() -> Self {
16 Self::new()
17 }
18}
19
20#[async_trait]
21impl ActionExecutor for ShellExecutor {
22 async fn execute(&self, plan: &ApprovedPlan) -> Result<ExecutionReport> {
23 let mut results = Vec::new();
24 for (i, step) in plan.steps.iter().enumerate() {
25 let output = tokio::process::Command::new("sh")
26 .arg("-c")
27 .arg(&step.action)
28 .output()
29 .await
30 .map_err(|e| disyn_core::Error::Execution(e.to_string()))?;
31 results.push(StepResult {
32 idempotency_key: step.idempotency_key,
33 step_index: i,
34 success: output.status.success(),
35 output: serde_json::json!({
36 "stdout": String::from_utf8_lossy(&output.stdout),
37 "stderr": String::from_utf8_lossy(&output.stderr),
38 }),
39 error: if output.status.success() {
40 None
41 } else {
42 Some(format!("exit code: {}", output.status.code().unwrap_or(-1)))
43 },
44 });
45 }
46 Ok(ExecutionReport {
47 results,
48 total_cost: ResourceUsage {
49 total_tokens: 0,
50 symbolic_tokens: 0,
51 neural_tokens: 0,
52 wall_time_ms: 0,
53 },
54 })
55 }
56}