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::manifest::{PluginManifest, SlotDefinition};
use super::shell_bridge::ShellBridge;
use anyhow::Result;
use serde_json::Value;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::sync::Arc;

/// Loaded plugin with its manifest and path.
#[derive(Debug)]
pub struct Plugin {
    pub name: Arc<str>,
    pub path: PathBuf,
    pub manifest: PluginManifest,
    pub enabled: bool,
}

/// Plugin slot for registering hooks.
#[derive(Debug)]
pub struct PluginSlot {
    pub name: Arc<str>,
    pub slot_type: Arc<str>,
    pub plugin_name: Arc<str>,
}

/// Plugin manager for loading and executing plugins.
pub struct PluginManager {
    plugins_dir: PathBuf,
    plugins: HashMap<Arc<str>, Plugin>,
    slots: Vec<PluginSlot>,
}

impl PluginManager {
    /// Create a new plugin manager.
    pub fn new(plugins_dir: PathBuf) -> Self {
        Self {
            plugins_dir,
            plugins: HashMap::new(),
            slots: Vec::new(),
        }
    }

    /// Get the plugins directory.
    pub fn plugins_dir(&self) -> &Path {
        &self.plugins_dir
    }

    /// List all loaded plugins.
    pub fn list(&self) -> Vec<&Plugin> {
        self.plugins.values().collect()
    }

    /// Get a plugin by name.
    pub fn get(&self, name: &str) -> Option<&Plugin> {
        self.plugins.get(name)
    }

    /// Load a plugin from directory.
    pub fn load(&mut self, plugin_name: &str) -> Result<()> {
        let plugin_dir = self.plugins_dir.join(plugin_name);

        if !plugin_dir.exists() {
            anyhow::bail!("Plugin directory not found: {:?}", plugin_dir);
        }

        let manifest_path = plugin_dir.join("openclaw.plugin.json");
        if !manifest_path.exists() {
            anyhow::bail!("Plugin manifest not found: {:?}", manifest_path);
        }

        let manifest_content = std::fs::read_to_string(&manifest_path)?;
        let manifest: PluginManifest = serde_json::from_str(&manifest_content)?;

        for slot in &manifest.slots {
            self.slots.push(PluginSlot {
                name: slot.name.clone(),
                slot_type: slot.slot_type.clone(),
                plugin_name: manifest.name.clone(),
            });
        }

        let plugin = Plugin {
            name: manifest.name.clone(),
            path: plugin_dir,
            manifest,
            enabled: true,
        };

        self.plugins.insert(plugin.name.clone(), plugin);
        Ok(())
    }

    /// Execute a plugin method.
    pub async fn execute(&self, plugin_name: &str, method: &str, params: Option<Value>) -> Result<Value> {
        let plugin = self.plugins.get(plugin_name)
            .ok_or_else(|| anyhow::anyhow!("Plugin '{}' not found", plugin_name))?;

        if !plugin.enabled {
            anyhow::bail!("Plugin '{}' is disabled", plugin_name);
        }

        let mut bridge = ShellBridge::new(Some(plugin.manifest.runtime.clone()));
        bridge.start(&plugin.path, &plugin.manifest.main).await?;

        let result = bridge.call(method, params).await;
        bridge.stop().await?;

        result
    }

    /// List all registered slots.
    pub fn list_slots(&self) -> &[PluginSlot] {
        &self.slots
    }
}