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;
pub struct HookEngine {
registry: Arc<HookRegistry>,
skills_dir: std::path::PathBuf,
}
impl HookEngine {
pub fn new(registry: Arc<HookRegistry>, skills_dir: std::path::PathBuf) -> Self {
Self {
registry,
skills_dir,
}
}
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)
}
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,
},
}
}
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(())
}
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(())
}
async fn execute_agent(&self, agent_name: &str, context: &HookContext) -> Result<()> {
tracing::info!(
"Agent hook triggered for '{}': {}",
agent_name,
context.event
);
Ok(())
}
}