diff --git a/src/plugin_system/base/base_plugin.py b/src/plugin_system/base/base_plugin.py index 662af3a5e..ada78e634 100644 --- a/src/plugin_system/base/base_plugin.py +++ b/src/plugin_system/base/base_plugin.py @@ -135,11 +135,6 @@ class BasePlugin(PluginBase): components = self.get_plugin_components() - # 检查依赖 - if not self._check_dependencies(): - logger.error(f"{self.log_prefix} 依赖检查失败,跳过注册") - return False - # 注册所有组件 registered_components = [] for component_info, component_class in components: diff --git a/src/plugin_system/base/component_types.py b/src/plugin_system/base/component_types.py index d92383788..87e771bfe 100644 --- a/src/plugin_system/base/component_types.py +++ b/src/plugin_system/base/component_types.py @@ -166,7 +166,7 @@ class ComponentInfo: @dataclass class ActionInfo(ComponentInfo): """动作组件信息 - + 注意:激活类型相关字段已废弃,推荐使用 Action 类的 go_activate() 方法来自定义激活逻辑。 这些字段将继续保留以提供向后兼容性,BaseAction.go_activate() 的默认实现会使用这些字段。 """ @@ -341,7 +341,7 @@ class PluginInfo: is_built_in: bool = False # 是否为内置插件 components: list[ComponentInfo] = field(default_factory=list) # 包含的组件列表 dependencies: list[str] = field(default_factory=list) # 依赖的其他插件 - python_dependencies: list[PythonDependency] = field(default_factory=list) # Python包依赖 + python_dependencies: list[str | PythonDependency] = field(default_factory=list) # Python包依赖 config_file: str = "" # 配置文件路径 metadata: dict[str, Any] = field(default_factory=dict) # 额外元数据 # 新增:manifest相关信息 diff --git a/src/plugin_system/base/plugin_base.py b/src/plugin_system/base/plugin_base.py index 0d5af65e3..b2799b860 100644 --- a/src/plugin_system/base/plugin_base.py +++ b/src/plugin_system/base/plugin_base.py @@ -12,7 +12,6 @@ from src.config.config import CONFIG_DIR from src.plugin_system.base.component_types import ( PermissionNodeField, PluginInfo, - PythonDependency, ) from src.plugin_system.base.config_types import ConfigField from src.plugin_system.base.plugin_metadata import PluginMetadata @@ -30,8 +29,6 @@ class PluginBase(ABC): plugin_name: str config_file_name: str enable_plugin: bool = True - dependencies: list[str] = [] - python_dependencies: list[str | PythonDependency] = [] config_schema: dict[str, dict[str, ConfigField] | str] = {} @@ -64,12 +61,6 @@ class PluginBase(ABC): self.plugin_description = self.plugin_meta.description self.plugin_author = self.plugin_meta.author - # 标准化Python依赖为PythonDependency对象 - normalized_python_deps = self._normalize_python_dependencies(self.python_dependencies) - - # 检查Python依赖 - self._check_python_dependencies(normalized_python_deps) - # 创建插件信息对象 self.plugin_info = PluginInfo( name=self.plugin_name, @@ -80,8 +71,8 @@ class PluginBase(ABC): enabled=self._is_enabled, is_built_in=False, config_file=self.config_file_name or "", - dependencies=self.dependencies.copy(), - python_dependencies=normalized_python_deps, + dependencies=self.plugin_meta.dependencies.copy(), + python_dependencies=self.plugin_meta.python_dependencies.copy(), ) logger.debug(f"{self.log_prefix} 插件基类初始化完成") @@ -367,20 +358,6 @@ class PluginBase(ABC): self._is_enabled = self.config["plugin"]["enabled"] logger.info(f"{self.log_prefix} 从配置更新插件启用状态: {self._is_enabled}") - def _check_dependencies(self) -> bool: - """检查插件依赖""" - from src.plugin_system.core.component_registry import component_registry - - if not self.dependencies: - return True - - for dep in self.dependencies: - if not component_registry.get_plugin_info(dep): - logger.error(f"{self.log_prefix} 缺少依赖插件: {dep}") - return False - - return True - def get_config(self, key: str, default: Any = None) -> Any: """获取插件配置值,支持嵌套键访问 @@ -403,61 +380,6 @@ class PluginBase(ABC): return current - def _normalize_python_dependencies(self, dependencies: Any) -> list[PythonDependency]: - """将依赖列表标准化为PythonDependency对象""" - from packaging.requirements import Requirement - - normalized = [] - for dep in dependencies: - if isinstance(dep, str): - try: - # 尝试解析为requirement格式 (如 "package>=1.0.0") - req = Requirement(dep) - version_spec = str(req.specifier) if req.specifier else "" - - normalized.append( - PythonDependency( - package_name=req.name, - version=version_spec, - install_name=dep, # 保持原始的安装名称 - ) - ) - except Exception: - # 如果解析失败,作为简单包名处理 - normalized.append(PythonDependency(package_name=dep, install_name=dep)) - elif isinstance(dep, PythonDependency): - normalized.append(dep) - else: - logger.warning(f"{self.log_prefix} 未知的依赖格式: {dep}") - - return normalized - - def _check_python_dependencies(self, dependencies: list[PythonDependency]) -> bool: - """检查Python依赖并尝试自动安装""" - if not dependencies: - logger.info(f"{self.log_prefix} 无Python依赖需要检查") - return True - - try: - # 延迟导入以避免循环依赖 - from src.plugin_system.utils.dependency_manager import get_dependency_manager - - dependency_manager = get_dependency_manager() - success, errors = dependency_manager.check_and_install_dependencies(dependencies, self.plugin_name) - - if success: - logger.info(f"{self.log_prefix} Python依赖检查通过") - return True - else: - logger.error(f"{self.log_prefix} Python依赖检查失败:") - for error in errors: - logger.error(f"{self.log_prefix} - {error}") - return False - - except Exception as e: - logger.error(f"{self.log_prefix} Python依赖检查时发生异常: {e}", exc_info=True) - return False - @abstractmethod def register_plugin(self) -> bool: """ diff --git a/src/plugin_system/base/plugin_metadata.py b/src/plugin_system/base/plugin_metadata.py index 8871fcf14..be25e04d7 100644 --- a/src/plugin_system/base/plugin_metadata.py +++ b/src/plugin_system/base/plugin_metadata.py @@ -1,6 +1,8 @@ from dataclasses import dataclass, field from typing import Any +from src.plugin_system.base.component_types import PythonDependency + @dataclass class PluginMetadata: @@ -23,5 +25,9 @@ class PluginMetadata: keywords: list[str] = field(default_factory=list) # 关键词 categories: list[str] = field(default_factory=list) # 分类 + # 依赖关系 + dependencies: list[str] = field(default_factory=list) # 插件依赖 + python_dependencies: list[str | PythonDependency] = field(default_factory=list) # Python包依赖 + # 扩展字段 extra: dict[str, Any] = field(default_factory=dict) # 其他任意信息 diff --git a/src/plugin_system/core/plugin_manager.py b/src/plugin_system/core/plugin_manager.py index ba4829340..7fb1ecd4a 100644 --- a/src/plugin_system/core/plugin_manager.py +++ b/src/plugin_system/core/plugin_manager.py @@ -323,6 +323,33 @@ class PluginManager: init_module = module_from_spec(init_spec) init_spec.loader.exec_module(init_module) + # --- 在这里进行依赖检查 --- + if hasattr(init_module, "__plugin_meta__"): + metadata = getattr(init_module, "__plugin_meta__") + from src.plugin_system.utils.dependency_manager import get_dependency_manager + + dependency_manager = get_dependency_manager() + + # 1. 检查Python依赖 + if metadata.python_dependencies: + success, errors = dependency_manager.check_and_install_dependencies( + metadata.python_dependencies, metadata.name + ) + if not success: + error_msg = f"Python依赖检查失败: {', '.join(errors)}" + self.failed_plugins[plugin_name] = error_msg + logger.error(f"❌ 插件加载失败: {plugin_name} - {error_msg}") + return None # 依赖检查失败,不加载该模块 + + # 2. 检查插件依赖 + if not self._check_plugin_dependencies(metadata): + error_msg = f"插件依赖检查失败: 请确保依赖 {metadata.dependencies} 已正确安装并加载。" + self.failed_plugins[plugin_name] = error_msg + logger.error(f"❌ 插件加载失败: {plugin_name} - {error_msg}") + return None # 插件依赖检查失败 + + # --- 依赖检查逻辑结束 --- + # 然后加载 plugin.py spec = spec_from_file_location(module_name, plugin_file) if spec is None or spec.loader is None: @@ -335,7 +362,8 @@ class PluginManager: # 将 __plugin_meta__ 从 init_module 附加到主模块 if init_module and hasattr(init_module, "__plugin_meta__"): - setattr(module, "__plugin_meta__", getattr(init_module, "__plugin_meta__")) + metadata = getattr(init_module, "__plugin_meta__") + setattr(module, "__plugin_meta__", metadata) logger.debug(f"插件模块加载成功: {plugin_file} -> {plugin_name} ({plugin_dir})") return module @@ -346,6 +374,20 @@ class PluginManager: self.failed_plugins[plugin_name if "plugin_name" in locals() else module_name] = error_msg return None + def _check_plugin_dependencies(self, plugin_meta: PluginMetadata) -> bool: + """检查插件的插件依赖""" + dependencies = plugin_meta.dependencies + if not dependencies: + return True + + for dep_name in dependencies: + # 检查依赖的插件类是否已注册 + if dep_name not in self.plugin_classes: + logger.error(f"插件 '{plugin_meta.name}' 缺少依赖: 插件 '{dep_name}' 未找到或加载失败。") + return False + logger.debug(f"插件 '{plugin_meta.name}' 的所有依赖都已找到。") + return True + # == 显示统计与插件信息 == def _show_stats(self, total_registered: int, total_failed_registration: int): diff --git a/src/plugins/built_in/social_toolkit_plugin/__init__.py b/src/plugins/built_in/social_toolkit_plugin/__init__.py index 9f48d7182..b02ed2426 100644 --- a/src/plugins/built_in/social_toolkit_plugin/__init__.py +++ b/src/plugins/built_in/social_toolkit_plugin/__init__.py @@ -1,5 +1,6 @@ from src.plugin_system.base.plugin_metadata import PluginMetadata +# 定义插件元数据 __plugin_meta__ = PluginMetadata( name="MoFox-Bot工具箱", description="一个集合多种实用功能的插件,旨在提升聊天体验和效率。", @@ -11,4 +12,6 @@ __plugin_meta__ = PluginMetadata( keywords=["emoji", "reaction", "like", "表情", "回应", "点赞"], categories=["Chat", "Integration"], extra={"is_built_in": "true", "plugin_type": "functional"}, + dependencies=[], + python_dependencies=["httpx", "Pillow"], ) diff --git a/src/plugins/built_in/tts_voice_plugin/__init__.py b/src/plugins/built_in/tts_voice_plugin/__init__.py index 8eaac0ab7..463dcc244 100644 --- a/src/plugins/built_in/tts_voice_plugin/__init__.py +++ b/src/plugins/built_in/tts_voice_plugin/__init__.py @@ -13,5 +13,6 @@ __plugin_meta__ = PluginMetadata( extra={ "is_built_in": False, "plugin_type": "tools", - } + }, + python_dependencies = ["aiohttp", "soundfile", "pedalboard"] ) diff --git a/src/plugins/built_in/tts_voice_plugin/plugin.py b/src/plugins/built_in/tts_voice_plugin/plugin.py index 3d032d7d8..5f2dcb7ad 100644 --- a/src/plugins/built_in/tts_voice_plugin/plugin.py +++ b/src/plugins/built_in/tts_voice_plugin/plugin.py @@ -30,7 +30,6 @@ class TTSVoicePlugin(BasePlugin): plugin_author = "Kilo Code & 靚仔" config_file_name = "config.toml" dependencies = [] - python_dependencies = ["aiohttp", "soundfile", "pedalboard"] permission_nodes: list[PermissionNodeField] = [ PermissionNodeField(node_name="command.use", description="是否可以使用 /tts 命令"), diff --git a/src/plugins/built_in/web_search_tool/__init__.py b/src/plugins/built_in/web_search_tool/__init__.py index 1ebf0bec1..458e2586b 100644 --- a/src/plugins/built_in/web_search_tool/__init__.py +++ b/src/plugins/built_in/web_search_tool/__init__.py @@ -1,3 +1,4 @@ +from src.plugin_system.base.component_types import PythonDependency from src.plugin_system.base.plugin_metadata import PluginMetadata __plugin_meta__ = PluginMetadata( @@ -13,4 +14,26 @@ __plugin_meta__ = PluginMetadata( extra={ "is_built_in": True, }, + # Python包依赖列表 + python_dependencies = [ # noqa: RUF012 + PythonDependency(package_name="asyncddgs", description="异步DuckDuckGo搜索库", optional=False), + PythonDependency( + package_name="exa_py", + description="Exa搜索API客户端库", + optional=True, # 如果没有API密钥,这个是可选的 + ), + PythonDependency( + package_name="tavily", + install_name="tavily-python", # 安装时使用这个名称 + description="Tavily搜索API客户端库", + optional=True, # 如果没有API密钥,这个是可选的 + ), + PythonDependency( + package_name="httpx", + version=">=0.20.0", + install_name="httpx[socks]", # 安装时使用这个名称(包含可选依赖) + description="支持SOCKS代理的HTTP客户端库", + optional=False, + ), + ] ) diff --git a/src/plugins/built_in/web_search_tool/plugin.py b/src/plugins/built_in/web_search_tool/plugin.py index 93f302081..44e0082e0 100644 --- a/src/plugins/built_in/web_search_tool/plugin.py +++ b/src/plugins/built_in/web_search_tool/plugin.py @@ -74,29 +74,6 @@ class WEBSEARCHPLUGIN(BasePlugin): except Exception as e: logger.error(f"❌ 搜索引擎初始化失败: {e}", exc_info=True) - - # Python包依赖列表 - python_dependencies: list[PythonDependency] = [ # noqa: RUF012 - PythonDependency(package_name="asyncddgs", description="异步DuckDuckGo搜索库", optional=False), - PythonDependency( - package_name="exa_py", - description="Exa搜索API客户端库", - optional=True, # 如果没有API密钥,这个是可选的 - ), - PythonDependency( - package_name="tavily", - install_name="tavily-python", # 安装时使用这个名称 - description="Tavily搜索API客户端库", - optional=True, # 如果没有API密钥,这个是可选的 - ), - PythonDependency( - package_name="httpx", - version=">=0.20.0", - install_name="httpx[socks]", # 安装时使用这个名称(包含可选依赖) - description="支持SOCKS代理的HTTP客户端库", - optional=False, - ), - ] config_file_name: str = "config.toml" # 配置文件名 # 配置节描述