Skip to main content

disyn_runtime/
executor.rs

1use 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}