rsclaw 0.0.1-alpha.1

rsclaw: High-performance AI agent (BETA). Optimized for M4 Max and 2GB VPS. 100% compatible with openclaw
Documentation
use super::registry::HookRegistry;
use super::types::{Hook, HookAction, HookContext, HookResult};
use crate::skill::ShellRunner;
use anyhow::Result;
use std::sync::Arc;
use std::time::Instant;

/// Hook execution engine.
pub struct HookEngine {
    registry: Arc<HookRegistry>,
    skills_dir: std::path::PathBuf,
}

impl HookEngine {
    /// Create a new hook engine.
    pub fn new(registry: Arc<HookRegistry>, skills_dir: std::path::PathBuf) -> Self {
        Self {
            registry,
            skills_dir,
        }
    }

    /// Execute hooks for an event.
    pub async fn execute(&self, context: &HookContext) -> Result<Vec<HookResult>> {
        let hooks = self.registry.get_hooks_for_event(&context.event).await;
        let mut results = Vec::new();

        for hook in hooks {
            let result = self.execute_hook(&hook, context).await;
            results.push(result);
        }

        Ok(results)
    }

    /// Execute a single hook.
    async fn execute_hook(&self, hook: &Hook, context: &HookContext) -> HookResult {
        let start = Instant::now();

        let result = match &hook.action {
            HookAction::Log(message) => {
                tracing::info!("Hook '{}' [{}]: {}", hook.name, context.event, message);
                Ok(())
            }
            HookAction::Command(cmd) => {
                self.execute_command(cmd, context).await
            }
            HookAction::Skill(skill_name) => {
                self.execute_skill(skill_name, context).await
            }
            HookAction::Agent(agent_name) => {
                self.execute_agent(agent_name, context).await
            }
            HookAction::None => Ok(()),
        };

        let duration = start.elapsed();

        match result {
            Ok(_) => HookResult {
                hook_id: hook.id.clone(),
                success: true,
                error: None,
                duration_ms: duration.as_millis() as u64,
            },
            Err(e) => HookResult {
                hook_id: hook.id.clone(),
                success: false,
                error: Some(Arc::from(e.to_string().as_str())),
                duration_ms: duration.as_millis() as u64,
            },
        }
    }

    /// Execute a command hook.
    async fn execute_command(&self, cmd: &str, _context: &HookContext) -> Result<()> {
        let output = tokio::process::Command::new("sh")
            .arg("-c")
            .arg(cmd)
            .output()
            .await?;

        if !output.status.success() {
            anyhow::bail!(
                "Command failed: {}",
                String::from_utf8_lossy(&output.stderr)
            );
        }

        Ok(())
    }

    /// Execute a skill hook.
    async fn execute_skill(&self, skill_name: &str, context: &HookContext) -> Result<()> {
        let mut loader = crate::skill::SkillLoader::new(self.skills_dir.clone());
        loader.load_all()?;

        let skill = loader.get(skill_name)
            .ok_or_else(|| anyhow::anyhow!("Skill '{}' not found", skill_name))?;

        let runner = ShellRunner::default();
        let args = vec![serde_json::to_string(&context.data)?];
        let result = runner.run(&skill.path, &args).await?;

        if !result.success {
            anyhow::bail!("Skill execution failed: {}", result.stderr);
        }

        Ok(())
    }

    /// Execute an agent hook.
    async fn execute_agent(&self, agent_name: &str, context: &HookContext) -> Result<()> {
        tracing::info!(
            "Agent hook triggered for '{}': {}",
            agent_name,
            context.event
        );
        Ok(())
    }
}