Skip to main content

walrus_daemon/hook/skill/
handler.rs

1//! Walrus skill handler — initial load from disk.
2
3use crate::hook::skill::{SkillRegistry, loader};
4use anyhow::Result;
5use schemars::JsonSchema;
6use serde::Deserialize;
7use std::path::PathBuf;
8use tokio::sync::Mutex;
9
10#[derive(Deserialize, JsonSchema)]
11pub(crate) struct SearchSkillInput {
12    /// Keyword to match skill names and descriptions
13    pub query: String,
14}
15
16#[derive(Deserialize, JsonSchema)]
17pub(crate) struct LoadSkillInput {
18    /// Skill name
19    pub name: String,
20}
21
22/// Skill registry owner.
23///
24/// Implements [`Hook`] — `on_build_agent` enriches the system prompt with
25/// matching skills based on agent tags. Tools and dispatch are no-ops
26/// (skills inject behavior via prompt, not via tools).
27pub struct SkillHandler {
28    /// The skill registry (Mutex for interior-mutability from `dispatch_load_skill`).
29    pub registry: Mutex<SkillRegistry>,
30    /// Base directory from which skills are loaded.
31    pub skills_dir: PathBuf,
32}
33
34impl SkillHandler {
35    /// Load skills from the given directory. Tolerates a missing directory
36    /// by creating an empty registry.
37    pub fn load(skills_dir: PathBuf) -> Result<Self> {
38        let registry = if skills_dir.exists() {
39            match loader::load_skills_dir(&skills_dir) {
40                Ok(r) => {
41                    tracing::info!("loaded {} skill(s)", r.len());
42                    r
43                }
44                Err(e) => {
45                    tracing::warn!("could not load skills from {}: {e}", skills_dir.display());
46                    SkillRegistry::new()
47                }
48            }
49        } else {
50            SkillRegistry::new()
51        };
52        Ok(Self {
53            registry: Mutex::new(registry),
54            skills_dir,
55        })
56    }
57}