aidaemon 0.11.13

A personal AI agent that runs as a background daemon, accessible via Telegram, Slack, or Discord, with tool use, MCP integration, and persistent memory
Documentation
use std::path::{Path, PathBuf};
use std::sync::Arc;

use tracing::info;

use crate::config::AppConfig;
use crate::state::SqliteStateStore;
use crate::tools::ApprovalBroker;
use crate::tools::{ManageSkillsTool, SkillResourcesTool, UseSkillTool};
use crate::traits::store_prelude::*;
use crate::traits::Tool;

pub async fn register_skills_tools(
    config: &AppConfig,
    config_path: &Path,
    http_profiles: crate::oauth::SharedHttpProfiles,
    state: Arc<SqliteStateStore>,
    tools: &mut Vec<Arc<dyn Tool>>,
    approval_tx: ApprovalBroker,
) -> anyhow::Result<Option<PathBuf>> {
    if !config.skills.enabled {
        info!("Skills system disabled");
        return Ok(None);
    }

    let dir = config_path
        .parent()
        .unwrap_or_else(|| std::path::Path::new("."))
        .join(&config.skills.dir);
    std::fs::create_dir_all(&dir).ok();

    if state
        .get_setting("skill_migration_v1_done")
        .await?
        .is_none()
    {
        match state.get_dynamic_skills().await {
            Ok(dynamic_skills) => {
                let existing = crate::skills::load_skills(&dir);
                let existing_names: Vec<&str> = existing.iter().map(|s| s.name.as_str()).collect();
                let mut migrated = 0;
                for ds in &dynamic_skills {
                    if existing_names.iter().any(|n| *n == ds.name) {
                        info!(name = %ds.name, "Skipping migration — skill already exists on disk");
                        continue;
                    }
                    let triggers: Vec<String> =
                        serde_json::from_str(&ds.triggers_json).unwrap_or_default();
                    let skill = crate::skills::Skill {
                        name: ds.name.clone(),
                        description: ds.description.clone(),
                        triggers,
                        body: ds.body.clone(),
                        origin: Some(
                            crate::skills::infer_skill_origin(None, Some(ds.source.as_str()))
                                .to_string(),
                        ),
                        source: Some(ds.source.clone()),
                        source_url: ds.source_url.clone(),
                        dir_path: None,
                        resources: vec![],
                    };
                    match crate::skills::write_skill_to_file(&dir, &skill) {
                        Ok(path) => {
                            info!(name = %ds.name, path = %path.display(), "Migrated dynamic skill to filesystem");
                            migrated += 1;
                        }
                        Err(e) => {
                            tracing::warn!(name = %ds.name, error = %e, "Failed to migrate dynamic skill");
                        }
                    }
                }
                if migrated > 0 {
                    info!(count = migrated, "Dynamic skills migrated to filesystem");
                }
            }
            Err(e) => {
                tracing::warn!("Failed to load dynamic skills for migration: {}", e);
            }
        }
        state.set_setting("skill_migration_v1_done", "true").await?;
    }

    let startup_skills = crate::skills::load_skills(&dir);
    info!(
        count = startup_skills.len(),
        dir = %dir.display(),
        "Filesystem skills loaded"
    );

    let fs_resolver = Arc::new(crate::skills::FileSystemResolver::new());
    for skill in &startup_skills {
        if let Some(ref dir_path) = skill.dir_path {
            fs_resolver.register(&skill.name, dir_path.clone()).await;
        }
    }

    if config.tools.is_enabled("use_skill") {
        tools.push(Arc::new(UseSkillTool::new(dir.clone())));
        info!("use_skill tool enabled");
    }

    if config.tools.is_enabled("skill_resources") {
        tools.push(Arc::new(SkillResourcesTool::new(
            dir.clone(),
            fs_resolver as Arc<dyn crate::skills::ResourceResolver>,
        )));
        info!("skill_resources tool enabled");
    }

    if config.tools.is_enabled("manage_skills") {
        let manage_skills = ManageSkillsTool::new(dir.clone(), state, approval_tx)
            .with_http_profiles(http_profiles)
            .with_registries(config.skills.registries.clone());
        tools.push(Arc::new(manage_skills));
        info!("manage_skills tool enabled");
    }

    Ok(Some(dir))
}