Phase 7: Built-in Hooks (audit_log, dangerous_confirmation, security_scan) Phase 8: Plugin system (PluginManager, PluginSandbox, PluginManifest) Phase 9: Skills registry (SkillRegistry, local/plugin/MCP loaders) Phase 10: TeamLeader, RemoteTransport, BackgroundTaskManager
208 lines
5.5 KiB
Python
208 lines
5.5 KiB
Python
"""插件管理器 - Phase 8.2"""
|
||
|
||
import importlib.util
|
||
import os
|
||
import sys
|
||
from typing import Any
|
||
|
||
from app.agents.plugins.manifest import PluginManifest
|
||
from app.agents.plugins.sandbox import PluginSandbox
|
||
|
||
|
||
class PluginManager:
|
||
"""插件管理器
|
||
|
||
负责插件的安装、卸载、启用、禁用和生命周期管理。
|
||
"""
|
||
|
||
def __init__(self, plugins_dir: str | None = None):
|
||
"""
|
||
Args:
|
||
plugins_dir: 插件目录,None 则使用默认目录
|
||
"""
|
||
if plugins_dir is None:
|
||
plugins_dir = os.path.join(os.path.dirname(__file__), "..", "..", "..", "plugins")
|
||
self.plugins_dir = plugins_dir
|
||
self._plugins: dict[str, PluginManifest] = {}
|
||
self._enabled: dict[str, bool] = {}
|
||
self._modules: dict[str, Any] = {}
|
||
self._sandbox = PluginSandbox()
|
||
|
||
def install(self, plugin_path: str) -> bool:
|
||
"""安装插件
|
||
|
||
Args:
|
||
plugin_path: 插件目录路径或 manifest.json 所在目录
|
||
|
||
Returns:
|
||
是否安装成功
|
||
"""
|
||
try:
|
||
manifest_path = os.path.join(plugin_path, "manifest.json")
|
||
|
||
if not os.path.exists(manifest_path):
|
||
return False
|
||
|
||
with open(manifest_path, "r", encoding="utf-8") as f:
|
||
import json
|
||
|
||
data = json.load(f)
|
||
|
||
manifest = PluginManifest.from_dict(data)
|
||
|
||
# 验证 manifest
|
||
if not self._validate_manifest(manifest, plugin_path):
|
||
return False
|
||
|
||
# 复制插件到 plugins_dir
|
||
target_dir = os.path.join(self.plugins_dir, manifest.id)
|
||
os.makedirs(os.path.dirname(target_dir), exist_ok=True)
|
||
|
||
# 保存 manifest
|
||
with open(os.path.join(target_dir, "manifest.json"), "w", encoding="utf-8") as f:
|
||
json.dump(manifest.to_dict(), f, indent=2, ensure_ascii=False)
|
||
|
||
# 注册插件
|
||
self._plugins[manifest.id] = manifest
|
||
self._enabled[manifest.id] = True
|
||
|
||
return True
|
||
|
||
except Exception:
|
||
return False
|
||
|
||
def uninstall(self, plugin_id: str) -> bool:
|
||
"""卸载插件
|
||
|
||
Args:
|
||
plugin_id: 插件 ID
|
||
|
||
Returns:
|
||
是否卸载成功
|
||
"""
|
||
if plugin_id not in self._plugins:
|
||
return False
|
||
|
||
# 禁用插件
|
||
self.disable(plugin_id)
|
||
|
||
# 移除模块
|
||
if plugin_id in self._modules:
|
||
del self._modules[plugin_id]
|
||
|
||
# 移除插件
|
||
del self._plugins[plugin_id]
|
||
del self._enabled[plugin_id]
|
||
|
||
# 删除目录
|
||
plugin_dir = os.path.join(self.plugins_dir, plugin_id)
|
||
if os.path.exists(plugin_dir):
|
||
import shutil
|
||
|
||
shutil.rmtree(plugin_dir)
|
||
|
||
return True
|
||
|
||
def enable(self, plugin_id: str) -> bool:
|
||
"""启用插件
|
||
|
||
Args:
|
||
plugin_id: 插件 ID
|
||
|
||
Returns:
|
||
是否启用成功
|
||
"""
|
||
if plugin_id not in self._plugins:
|
||
return False
|
||
|
||
self._enabled[plugin_id] = True
|
||
return True
|
||
|
||
def disable(self, plugin_id: str) -> bool:
|
||
"""禁用插件
|
||
|
||
Args:
|
||
plugin_id: 插件 ID
|
||
|
||
Returns:
|
||
是否禁用成功
|
||
"""
|
||
if plugin_id not in self._plugins:
|
||
return False
|
||
|
||
self._enabled[plugin_id] = False
|
||
return True
|
||
|
||
def reload(self, plugin_id: str) -> bool:
|
||
"""重新加载插件
|
||
|
||
Args:
|
||
plugin_id: 插件 ID
|
||
|
||
Returns:
|
||
是否重新加载成功
|
||
"""
|
||
if plugin_id not in self._plugins:
|
||
return False
|
||
|
||
# 卸载模块
|
||
if plugin_id in self._modules:
|
||
del self._modules[plugin_id]
|
||
|
||
# 重新加载
|
||
return self._load_plugin_module(plugin_id)
|
||
|
||
def list_plugins(self) -> list[PluginManifest]:
|
||
"""列出所有插件"""
|
||
return list(self._plugins.values())
|
||
|
||
def get_plugin(self, plugin_id: str) -> PluginManifest | None:
|
||
"""获取插件清单"""
|
||
return self._plugins.get(plugin_id)
|
||
|
||
def is_enabled(self, plugin_id: str) -> bool:
|
||
"""检查插件是否启用"""
|
||
return self._enabled.get(plugin_id, False)
|
||
|
||
def _validate_manifest(self, manifest: PluginManifest, plugin_path: str) -> bool:
|
||
"""验证 manifest"""
|
||
# 检查主入口文件是否存在
|
||
main_path = os.path.join(plugin_path, manifest.main)
|
||
if not os.path.exists(main_path):
|
||
return False
|
||
|
||
return True
|
||
|
||
def _load_plugin_module(self, plugin_id: str) -> bool:
|
||
"""加载插件模块"""
|
||
plugin_dir = os.path.join(self.plugins_dir, plugin_id)
|
||
manifest = self._plugins.get(plugin_id)
|
||
if not manifest:
|
||
return False
|
||
|
||
try:
|
||
main_path = os.path.join(plugin_dir, manifest.main)
|
||
spec = importlib.util.spec_from_file_location(plugin_id, main_path)
|
||
if spec and spec.loader:
|
||
module = importlib.util.module_from_spec(spec)
|
||
sys.modules[plugin_id] = module
|
||
spec.loader.exec_module(module)
|
||
self._modules[plugin_id] = module
|
||
return True
|
||
except Exception:
|
||
pass
|
||
|
||
return False
|
||
|
||
|
||
# 全局单例
|
||
_manager: PluginManager | None = None
|
||
|
||
|
||
def get_plugin_manager() -> PluginManager:
|
||
"""获取全局插件管理器"""
|
||
global _manager
|
||
if _manager is None:
|
||
_manager = PluginManager()
|
||
return _manager
|