Entelechy / plugins /manager.py
qa296
refactor: simplify architecture and rebrand to Entelechy
b8e5043
"""Plugin lifecycle manager."""
from __future__ import annotations
from pathlib import Path
from typing import TYPE_CHECKING
from loguru import logger
from plugins.registry import PluginRegistry, plugin_registry
if TYPE_CHECKING:
from plugins.base_plugin import BasePlugin
class PluginManager:
"""Manages plugin lifecycle: activation, deactivation, tool aggregation."""
def __init__(self, plugins_dir: Path, registry: PluginRegistry | None = None):
self.plugins_dir = Path(plugins_dir)
self.registry = registry or plugin_registry
self.active_plugins: dict[str, BasePlugin] = {}
async def discover_and_activate_all(self):
"""Discover all plugins and activate them."""
self.registry.discover_and_load(self.plugins_dir)
for name in self.registry.list_plugins():
await self.activate_plugin(name)
async def activate_plugin(self, plugin_name: str):
"""Activate a plugin by name."""
if plugin_name in self.active_plugins:
logger.debug(f"Plugin already active: {plugin_name}")
return
cls = self.registry.get_plugin_class(plugin_name)
if cls is None:
logger.warning(f"Plugin class not found: {plugin_name}")
return
try:
instance = cls(context={})
await instance.initialize()
self.active_plugins[plugin_name] = instance
logger.info(f"Activated plugin: {plugin_name}")
except Exception as e:
logger.error(f"Failed to activate plugin {plugin_name}: {e}")
async def deactivate_plugin(self, plugin_name: str):
"""Deactivate a plugin by name."""
if plugin_name not in self.active_plugins:
return
try:
await self.active_plugins[plugin_name].terminate()
except Exception as e:
logger.warning(f"Error terminating plugin {plugin_name}: {e}")
del self.active_plugins[plugin_name]
logger.info(f"Deactivated plugin: {plugin_name}")
def get_all_tools(self) -> list[dict]:
"""Aggregate tool definitions from all active plugins."""
tools = []
for plugin in self.active_plugins.values():
tools.extend(plugin.get_tools())
return tools
async def execute_plugin_tool(self, tool_name: str, args: dict) -> str | None:
"""Try to execute a tool from any active plugin.
Returns:
The tool result string, or None if no plugin handles this tool.
"""
for plugin in self.active_plugins.values():
for tool_def in plugin.get_tools():
if tool_def["name"] == tool_name:
return await plugin.execute_tool(tool_name, args)
return None
async def reload_plugins(self):
"""Deactivate all, re-discover, re-activate."""
names = list(self.active_plugins.keys())
for name in names:
await self.deactivate_plugin(name)
self.registry.plugin_classes.clear()
self.registry.metadata.clear()
await self.discover_and_activate_all()