Spaces:
Running on Zero
Running on Zero
| """Plugin manager — loads, registers, and manages plugins.""" | |
| import json | |
| import logging | |
| from typing import Optional | |
| from pathlib import Path | |
| from config import config | |
| from database.repositories import PluginRepo | |
| logger = logging.getLogger("synapse.plugins") | |
| class PluginAPI: | |
| """API surface exposed to plugins.""" | |
| def __init__(self): | |
| self.tools: dict = {} | |
| self.hooks: dict = {} | |
| def register_tool(self, name: str, description: str, handler, parameters: dict = None): | |
| self.tools[name] = { | |
| "name": name, | |
| "description": description, | |
| "handler": handler, | |
| "parameters": parameters or {}, | |
| } | |
| logger.info(f"Plugin registered tool: {name}") | |
| def register_hook(self, event: str, handler): | |
| if event not in self.hooks: | |
| self.hooks[event] = [] | |
| self.hooks[event].append(handler) | |
| logger.info(f"Plugin registered hook: {event}") | |
| async def trigger_hook(self, event: str, **kwargs): | |
| results = [] | |
| for handler in self.hooks.get(event, []): | |
| try: | |
| if hasattr(handler, '__call__'): | |
| result = handler(**kwargs) | |
| results.append(result) | |
| except Exception as e: | |
| logger.error(f"Hook {event} error: {e}") | |
| return results | |
| class PluginManager: | |
| """Manages plugin lifecycle.""" | |
| def __init__(self): | |
| self.api = PluginAPI() | |
| self._loaded_plugins: dict[str, object] = {} | |
| async def load_enabled_plugins(self): | |
| plugins = await PluginRepo.get_enabled() | |
| for plugin in plugins: | |
| await self._load_plugin(plugin) | |
| async def _load_plugin(self, plugin_data: dict): | |
| plugin_id = plugin_data["id"] | |
| name = plugin_data["name"] | |
| try: | |
| plugin_dir = Path(f"plugins/builtin/{name}") | |
| if plugin_dir.exists(): | |
| import importlib.util | |
| spec = importlib.util.spec_from_file_location( | |
| f"plugins.builtin.{name}", plugin_dir / "main.py" | |
| ) | |
| if spec and spec.loader: | |
| module = importlib.util.module_from_spec(spec) | |
| spec.loader.exec_module(module) | |
| if hasattr(module, "register"): | |
| module.register(self.api) | |
| self._loaded_plugins[plugin_id] = module | |
| logger.info(f"Loaded plugin: {name}") | |
| except Exception as e: | |
| logger.error(f"Failed to load plugin {name}: {e}") | |
| async def install_plugin(self, name: str, description: str = "", | |
| author: str = "") -> dict: | |
| from database.repositories import PluginRepo | |
| plugin_id = PluginRepo.new_id() | |
| from database import engine as db_engine | |
| await db_engine.execute( | |
| "INSERT INTO plugins (id, name, description, author) VALUES (?, ?, ?, ?)", | |
| (plugin_id, name, description, author), | |
| ) | |
| return {"id": plugin_id, "name": name} | |
| async def toggle_plugin(self, plugin_id: str) -> dict: | |
| await PluginRepo.toggle(plugin_id) | |
| plugin = await PluginRepo.get_by_id(plugin_id) | |
| return plugin | |
| async def get_all_plugins(self) -> list[dict]: | |
| from database import engine as db_engine | |
| return await db_engine.fetch_all("SELECT * FROM plugins ORDER BY name") | |
| def get_registered_tools(self) -> list[dict]: | |
| return [ | |
| {"name": t["name"], "description": t["description"], "parameters": t["parameters"]} | |
| for t in self.api.tools.values() | |
| ] | |
| plugin_manager = PluginManager() | |