增加了卸载和重载插件功能

This commit is contained in:
UnCLAS-Prommer
2025-07-22 18:52:11 +08:00
parent 22c7f667e9
commit 76025032a9
9 changed files with 258 additions and 271 deletions

View File

@@ -45,6 +45,7 @@
10. 修正了`main.py`中的错误输出。 10. 修正了`main.py`中的错误输出。
11. 修正了`command`所编译的`Pattern`注册时的错误输出。 11. 修正了`command`所编译的`Pattern`注册时的错误输出。
12. `events_manager`有了task相关逻辑了。 12. `events_manager`有了task相关逻辑了。
13. 现在有了插件卸载和重载功能了,也就是热插拔。
### TODO ### TODO
把这个看起来就很别扭的config获取方式改一下 把这个看起来就很别扭的config获取方式改一下
@@ -64,4 +65,7 @@ else:
plugin_path = Path(plugin_file) plugin_path = Path(plugin_file)
module_name = ".".join(plugin_path.parent.parts) module_name = ".".join(plugin_path.parent.parts)
``` ```
这两个区别很大的。 这两个区别很大的。
### 执笔BGM
塞壬唱片!

View File

@@ -1,4 +1,6 @@
from typing import Dict, List, Optional, Type import traceback
from typing import Dict, Optional, Type
from src.plugin_system.base.base_action import BaseAction from src.plugin_system.base.base_action import BaseAction
from src.chat.message_receive.chat_stream import ChatStream from src.chat.message_receive.chat_stream import ChatStream
from src.common.logger import get_logger from src.common.logger import get_logger
@@ -27,24 +29,12 @@ class ActionManager:
# 当前正在使用的动作集合,默认加载默认动作 # 当前正在使用的动作集合,默认加载默认动作
self._using_actions: Dict[str, ActionInfo] = {} self._using_actions: Dict[str, ActionInfo] = {}
# 加载插件动作 # 初始化管理器注册表
self._load_plugin_actions() self._load_plugin_system_actions()
# 初始化时将默认动作加载到使用中的动作 # 初始化时将默认动作加载到使用中的动作
self._using_actions = component_registry.get_default_actions() self._using_actions = component_registry.get_default_actions()
def _load_plugin_actions(self) -> None:
"""
加载所有插件系统中的动作
"""
try:
# 从新插件系统获取Action组件
self._load_plugin_system_actions()
logger.debug("从插件系统加载Action组件成功")
except Exception as e:
logger.error(f"加载插件动作失败: {e}")
def _load_plugin_system_actions(self) -> None: def _load_plugin_system_actions(self) -> None:
"""从插件系统的component_registry加载Action组件""" """从插件系统的component_registry加载Action组件"""
try: try:
@@ -58,18 +48,16 @@ class ActionManager:
self._registered_actions[action_name] = action_info self._registered_actions[action_name] = action_info
logger.debug( logger.debug(f"从插件系统加载Action组件: {action_name} (插件: {action_info.plugin_name})")
f"从插件系统加载Action组件: {action_name} (插件: {getattr(action_info, 'plugin_name', 'unknown')})"
)
logger.info(f"加载了 {len(action_components)} 个Action动作") logger.info(f"加载了 {len(action_components)} 个Action动作")
logger.debug("从插件系统加载Action组件成功")
except Exception as e: except Exception as e:
logger.error(f"从插件系统加载Action组件失败: {e}") logger.error(f"从插件系统加载Action组件失败: {e}")
import traceback
logger.error(traceback.format_exc()) logger.error(traceback.format_exc())
# === 执行Action方法 ===
def create_action( def create_action(
self, self,
action_name: str, action_name: str,
@@ -147,28 +135,37 @@ class ActionManager:
"""获取当前正在使用的动作集合""" """获取当前正在使用的动作集合"""
return self._using_actions.copy() return self._using_actions.copy()
def add_action_to_using(self, action_name: str) -> bool: # === 增删Action方法 ===
""" def add_action(self, action_name: str) -> bool:
添加已注册的动作到当前使用的动作集 """增加一个Action到管理器
Args: Parameters:
action_name: 动作名称 action_name: 动作名称
Returns: Returns:
bool: 添加是否成功 bool: 添加是否成功
""" """
if action_name not in self._registered_actions: if action_name in self._registered_actions:
return True
component_info: ActionInfo = component_registry.get_component_info(action_name, ComponentType.ACTION) # type: ignore
if not component_info:
logger.warning(f"添加失败: 动作 {action_name} 未注册") logger.warning(f"添加失败: 动作 {action_name} 未注册")
return False return False
self._registered_actions[action_name] = component_info
if action_name in self._using_actions:
logger.info(f"动作 {action_name} 已经在使用中")
return True
self._using_actions[action_name] = self._registered_actions[action_name]
logger.info(f"添加动作 {action_name} 到使用集")
return True return True
def remove_action(self, action_name: str) -> bool:
"""从注册集移除指定动作
Parameters:
action_name: 动作名称
Returns:
bool: 移除是否成功
"""
if action_name not in self._registered_actions:
return False
del self._registered_actions[action_name]
return True
# === Modify相关方法 ===
def remove_action_from_using(self, action_name: str) -> bool: def remove_action_from_using(self, action_name: str) -> bool:
""" """
从当前使用的动作集中移除指定动作 从当前使用的动作集中移除指定动作
@@ -187,79 +184,52 @@ class ActionManager:
logger.debug(f"已从使用集中移除动作 {action_name}") logger.debug(f"已从使用集中移除动作 {action_name}")
return True return True
# def add_action(self, action_name: str, description: str, parameters: Dict = None, require: List = None) -> bool:
# """
# 添加新的动作到注册集
# Args:
# action_name: 动作名称
# description: 动作描述
# parameters: 动作参数定义,默认为空字典
# require: 动作依赖项,默认为空列表
# Returns:
# bool: 添加是否成功
# """
# if action_name in self._registered_actions:
# return False
# if parameters is None:
# parameters = {}
# if require is None:
# require = []
# action_info = {"description": description, "parameters": parameters, "require": require}
# self._registered_actions[action_name] = action_info
# return True
def remove_action(self, action_name: str) -> bool:
"""从注册集移除指定动作"""
if action_name not in self._registered_actions:
return False
del self._registered_actions[action_name]
# 如果在使用集中也存在,一并移除
if action_name in self._using_actions:
del self._using_actions[action_name]
return True
def temporarily_remove_actions(self, actions_to_remove: List[str]) -> None:
"""临时移除使用集中的指定动作"""
for name in actions_to_remove:
self._using_actions.pop(name, None)
def restore_actions(self) -> None: def restore_actions(self) -> None:
"""恢复到默认动作集""" """恢复到默认动作集"""
actions_to_restore = list(self._using_actions.keys()) actions_to_restore = list(self._using_actions.keys())
self._using_actions = component_registry.get_default_actions() self._using_actions = component_registry.get_default_actions()
logger.debug(f"恢复动作集: 从 {actions_to_restore} 恢复到默认动作集 {list(self._using_actions.keys())}") logger.debug(f"恢复动作集: 从 {actions_to_restore} 恢复到默认动作集 {list(self._using_actions.keys())}")
def add_system_action_if_needed(self, action_name: str) -> bool: # def add_action_to_using(self, action_name: str) -> bool:
"""
根据需要添加系统动作到使用集
Args: # """
action_name: 动作名称 # 添加已注册的动作到当前使用的动作集
Returns: # Args:
bool: 是否成功添加 # action_name: 动作名称
"""
if action_name in self._registered_actions and action_name not in self._using_actions:
self._using_actions[action_name] = self._registered_actions[action_name]
logger.info(f"临时添加系统动作到使用集: {action_name}")
return True
return False
def get_action(self, action_name: str) -> Optional[Type[BaseAction]]: # Returns:
""" # bool: 添加是否成功
获取指定动作的处理器类 # """
# if action_name not in self._registered_actions:
# logger.warning(f"添加失败: 动作 {action_name} 未注册")
# return False
Args: # if action_name in self._using_actions:
action_name: 动作名称 # logger.info(f"动作 {action_name} 已经在使用中")
# return True
Returns: # self._using_actions[action_name] = self._registered_actions[action_name]
Optional[Type[BaseAction]]: 动作处理器类如果不存在则返回None # logger.info(f"添加动作 {action_name} 到使用集")
""" # return True
from src.plugin_system.core.component_registry import component_registry
return component_registry.get_component_class(action_name, ComponentType.ACTION) # type: ignore # def temporarily_remove_actions(self, actions_to_remove: List[str]) -> None:
# """临时移除使用集中的指定动作"""
# for name in actions_to_remove:
# self._using_actions.pop(name, None)
# def add_system_action_if_needed(self, action_name: str) -> bool:
# """
# 根据需要添加系统动作到使用集
# Args:
# action_name: 动作名称
# Returns:
# bool: 是否成功添加
# """
# if action_name in self._registered_actions and action_name not in self._using_actions:
# self._using_actions[action_name] = self._registered_actions[action_name]
# logger.info(f"临时添加系统动作到使用集: {action_name}")
# return True
# return False

View File

@@ -47,7 +47,6 @@ class ActionModifier:
async def modify_actions( async def modify_actions(
self, self,
history_loop=None,
message_content: str = "", message_content: str = "",
): # sourcery skip: use-named-expression ): # sourcery skip: use-named-expression
""" """
@@ -318,7 +317,7 @@ class ActionModifier:
action_name: str, action_name: str,
action_info: ActionInfo, action_info: ActionInfo,
chat_content: str = "", chat_content: str = "",
) -> bool: ) -> bool: # sourcery skip: move-assign-in-block, use-named-expression
""" """
使用LLM判定是否应该激活某个action 使用LLM判定是否应该激活某个action

View File

@@ -113,7 +113,6 @@ class ActionPlanner:
try: try:
is_group_chat = True is_group_chat = True
is_group_chat, chat_target_info = get_chat_type_and_target_info(self.chat_id) is_group_chat, chat_target_info = get_chat_type_and_target_info(self.chat_id)
logger.debug(f"{self.log_prefix}获取到聊天信息 - 群聊: {is_group_chat}, 目标信息: {chat_target_info}") logger.debug(f"{self.log_prefix}获取到聊天信息 - 群聊: {is_group_chat}, 目标信息: {chat_target_info}")
@@ -234,10 +233,13 @@ class ActionPlanner:
"is_parallel": is_parallel, "is_parallel": is_parallel,
} }
return { return (
"action_result": action_result, {
"action_prompt": prompt, "action_result": action_result,
}, target_message "action_prompt": prompt,
},
target_message,
)
async def build_planner_prompt( async def build_planner_prompt(
self, self,

View File

@@ -619,9 +619,7 @@ def get_chat_type_and_target_info(chat_id: str) -> Tuple[bool, Optional[Dict]]:
chat_target_info = None chat_target_info = None
try: try:
chat_stream = get_chat_manager().get_stream(chat_id) if chat_stream := get_chat_manager().get_stream(chat_id):
if chat_stream:
if chat_stream.group_info: if chat_stream.group_info:
is_group_chat = True is_group_chat = True
chat_target_info = None # Explicitly None for group chat chat_target_info = None # Explicitly None for group chat
@@ -660,8 +658,6 @@ def get_chat_type_and_target_info(chat_id: str) -> Tuple[bool, Optional[Dict]]:
chat_target_info = target_info chat_target_info = target_info
else: else:
logger.warning(f"无法获取 chat_stream for {chat_id} in utils") logger.warning(f"无法获取 chat_stream for {chat_id} in utils")
# Keep defaults: is_group_chat=False, chat_target_info=None
except Exception as e: except Exception as e:
logger.error(f"获取聊天类型和目标信息时出错 for {chat_id}: {e}", exc_info=True) logger.error(f"获取聊天类型和目标信息时出错 for {chat_id}: {e}", exc_info=True)
# Keep defaults on error # Keep defaults on error

View File

@@ -28,7 +28,6 @@ def register_plugin(cls):
if "." in plugin_name: if "." in plugin_name:
logger.error(f"插件名称 '{plugin_name}' 包含非法字符 '.',请使用下划线替代") logger.error(f"插件名称 '{plugin_name}' 包含非法字符 '.',请使用下划线替代")
raise ValueError(f"插件名称 '{plugin_name}' 包含非法字符 '.',请使用下划线替代") raise ValueError(f"插件名称 '{plugin_name}' 包含非法字符 '.',请使用下划线替代")
plugin_manager.plugin_classes[plugin_name] = cls
splitted_name = cls.__module__.split(".") splitted_name = cls.__module__.split(".")
root_path = Path(__file__) root_path = Path(__file__)
@@ -40,6 +39,7 @@ def register_plugin(cls):
logger.error(f"注册 {plugin_name} 无法找到项目根目录") logger.error(f"注册 {plugin_name} 无法找到项目根目录")
return cls return cls
plugin_manager.plugin_classes[plugin_name] = cls
plugin_manager.plugin_paths[plugin_name] = str(Path(root_path, *splitted_name).resolve()) plugin_manager.plugin_paths[plugin_name] = str(Path(root_path, *splitted_name).resolve())
logger.debug(f"插件类已注册: {plugin_name}, 路径: {plugin_manager.plugin_paths[plugin_name]}") logger.debug(f"插件类已注册: {plugin_name}, 路径: {plugin_manager.plugin_paths[plugin_name]}")

View File

@@ -27,7 +27,7 @@ class ComponentRegistry:
def __init__(self): def __init__(self):
# 组件注册表 # 组件注册表
self._components: Dict[str, ComponentInfo] = {} # 命名空间式组件名 -> 组件信息 self._components: Dict[str, ComponentInfo] = {} # 命名空间式组件名 -> 组件信息
# 类型 -> 命名空间式名称 -> 组件信息 # 类型 -> 组件原名称 -> 组件信息
self._components_by_type: Dict[ComponentType, Dict[str, ComponentInfo]] = {types: {} for types in ComponentType} self._components_by_type: Dict[ComponentType, Dict[str, ComponentInfo]] = {types: {} for types in ComponentType}
# 命名空间式组件名 -> 组件类 # 命名空间式组件名 -> 组件类
self._components_classes: Dict[str, Type[Union[BaseCommand, BaseAction, BaseEventHandler]]] = {} self._components_classes: Dict[str, Type[Union[BaseCommand, BaseAction, BaseEventHandler]]] = {}
@@ -160,7 +160,9 @@ class ComponentRegistry:
if pattern not in self._command_patterns: if pattern not in self._command_patterns:
self._command_patterns[pattern] = command_name self._command_patterns[pattern] = command_name
else: else:
logger.warning(f"'{command_name}' 对应的命令模式与 '{self._command_patterns[pattern]}' 重复,忽略此命令") logger.warning(
f"'{command_name}' 对应的命令模式与 '{self._command_patterns[pattern]}' 重复,忽略此命令"
)
return True return True
@@ -176,6 +178,10 @@ class ComponentRegistry:
self._event_handler_registry[handler_name] = handler_class self._event_handler_registry[handler_name] = handler_class
if not handler_info.enabled:
logger.warning(f"EventHandler组件 {handler_name} 未启用")
return True # 未启用,但是也是注册成功
from .events_manager import events_manager # 延迟导入防止循环导入问题 from .events_manager import events_manager # 延迟导入防止循环导入问题
if events_manager.register_event_subscriber(handler_info, handler_class): if events_manager.register_event_subscriber(handler_info, handler_class):
@@ -185,6 +191,33 @@ class ComponentRegistry:
logger.error(f"注册事件处理器 {handler_name} 失败") logger.error(f"注册事件处理器 {handler_name} 失败")
return False return False
# === 组件移除相关 ===
async def remove_component(self, component_name: str, component_type: ComponentType):
target_component_class = self.get_component_class(component_name, component_type)
if not target_component_class:
logger.warning(f"组件 {component_name} 未注册,无法移除")
return
match component_type:
case ComponentType.ACTION:
self._action_registry.pop(component_name, None)
self._default_actions.pop(component_name, None)
case ComponentType.COMMAND:
self._command_registry.pop(component_name, None)
keys_to_remove = [k for k, v in self._command_patterns.items() if v == component_name]
for key in keys_to_remove:
self._command_patterns.pop(key, None)
case ComponentType.EVENT_HANDLER:
from .events_manager import events_manager # 延迟导入防止循环导入问题
self._event_handler_registry.pop(component_name, None)
self._enabled_event_handlers.pop(component_name, None)
await events_manager.unregister_event_subscriber(component_name)
self._components.pop(component_name, None)
self._components_by_type[component_type].pop(component_name, None)
self._components_classes.pop(component_name, None)
logger.info(f"组件 {component_name} 已移除")
# === 组件查询方法 === # === 组件查询方法 ===
def get_component_info( def get_component_info(
self, component_name: str, component_type: Optional[ComponentType] = None self, component_name: str, component_type: Optional[ComponentType] = None
@@ -287,7 +320,7 @@ class ComponentRegistry:
# === Action特定查询方法 === # === Action特定查询方法 ===
def get_action_registry(self) -> Dict[str, Type[BaseAction]]: def get_action_registry(self) -> Dict[str, Type[BaseAction]]:
"""获取Action注册表(用于兼容现有系统)""" """获取Action注册表"""
return self._action_registry.copy() return self._action_registry.copy()
def get_registered_action_info(self, action_name: str) -> Optional[ActionInfo]: def get_registered_action_info(self, action_name: str) -> Optional[ActionInfo]:

View File

@@ -28,18 +28,16 @@ class EventsManager:
bool: 是否注册成功 bool: 是否注册成功
""" """
handler_name = handler_info.name handler_name = handler_info.name
plugin_name = getattr(handler_info, "plugin_name", "unknown")
namespace_name = f"{plugin_name}.{handler_name}" if handler_name in self._handler_mapping:
if namespace_name in self._handler_mapping: logger.warning(f"事件处理器 {handler_name} 已存在,跳过注册")
logger.warning(f"事件处理器 {namespace_name} 已存在,跳过注册")
return False return False
if not issubclass(handler_class, BaseEventHandler): if not issubclass(handler_class, BaseEventHandler):
logger.error(f"{handler_class.__name__} 不是 BaseEventHandler 的子类") logger.error(f"{handler_class.__name__} 不是 BaseEventHandler 的子类")
return False return False
self._handler_mapping[namespace_name] = handler_class self._handler_mapping[handler_name] = handler_class
return self._insert_event_handler(handler_class, handler_info) return self._insert_event_handler(handler_class, handler_info)
async def handle_mai_events( async def handle_mai_events(
@@ -71,7 +69,7 @@ class EventsManager:
try: try:
handler_task = asyncio.create_task(handler.execute(transformed_message)) handler_task = asyncio.create_task(handler.execute(transformed_message))
handler_task.add_done_callback(self._task_done_callback) handler_task.add_done_callback(self._task_done_callback)
handler_task.set_name(f"EventHandler-{handler.handler_name}-{event_type.name}") handler_task.set_name(f"{handler.plugin_name}-{handler.handler_name}")
self._handler_tasks[handler.handler_name].append(handler_task) self._handler_tasks[handler.handler_name].append(handler_task)
except Exception as e: except Exception as e:
logger.error(f"创建事件处理器任务 {handler.handler_name} 时发生异常: {e}") logger.error(f"创建事件处理器任务 {handler.handler_name} 时发生异常: {e}")
@@ -91,7 +89,7 @@ class EventsManager:
return True return True
def _remove_event_handler(self, handler_class: Type[BaseEventHandler]) -> bool: def _remove_event_handler_instance(self, handler_class: Type[BaseEventHandler]) -> bool:
"""从事件类型列表中移除事件处理器""" """从事件类型列表中移除事件处理器"""
display_handler_name = handler_class.handler_name or handler_class.__name__ display_handler_name = handler_class.handler_name or handler_class.__name__
if handler_class.event_type == EventType.UNKNOWN: if handler_class.event_type == EventType.UNKNOWN:
@@ -190,5 +188,20 @@ class EventsManager:
finally: finally:
del self._handler_tasks[handler_name] del self._handler_tasks[handler_name]
async def unregister_event_subscriber(self, handler_name: str) -> bool:
"""取消注册事件处理器"""
if handler_name not in self._handler_mapping:
logger.warning(f"事件处理器 {handler_name} 不存在,无法取消注册")
return False
await self.cancel_handler_tasks(handler_name)
handler_class = self._handler_mapping.pop(handler_name)
if not self._remove_event_handler_instance(handler_class):
return False
logger.info(f"事件处理器 {handler_name} 已成功取消注册")
return True
events_manager = EventsManager() events_manager = EventsManager()

View File

@@ -1,5 +1,4 @@
import os import os
import inspect
import traceback import traceback
from typing import Dict, List, Optional, Tuple, Type, Any from typing import Dict, List, Optional, Tuple, Type, Any
@@ -8,11 +7,11 @@ from pathlib import Path
from src.common.logger import get_logger from src.common.logger import get_logger
from src.plugin_system.core.component_registry import component_registry
from src.plugin_system.core.dependency_manager import dependency_manager
from src.plugin_system.base.plugin_base import PluginBase from src.plugin_system.base.plugin_base import PluginBase
from src.plugin_system.base.component_types import ComponentType, PluginInfo, PythonDependency from src.plugin_system.base.component_types import ComponentType, PluginInfo, PythonDependency
from src.plugin_system.utils.manifest_utils import VersionComparator from src.plugin_system.utils.manifest_utils import VersionComparator
from .component_registry import component_registry
from .dependency_manager import dependency_manager
logger = get_logger("plugin_manager") logger = get_logger("plugin_manager")
@@ -36,19 +35,7 @@ class PluginManager:
self._ensure_plugin_directories() self._ensure_plugin_directories()
logger.info("插件管理器初始化完成") logger.info("插件管理器初始化完成")
def _ensure_plugin_directories(self) -> None: # === 插件目录管理 ===
"""确保所有插件根目录存在,如果不存在则创建"""
default_directories = ["src/plugins/built_in", "plugins"]
for directory in default_directories:
if not os.path.exists(directory):
os.makedirs(directory, exist_ok=True)
logger.info(f"创建插件根目录: {directory}")
if directory not in self.plugin_directories:
self.plugin_directories.append(directory)
logger.debug(f"已添加插件根目录: {directory}")
else:
logger.warning(f"根目录不可重复加载: {directory}")
def add_plugin_directory(self, directory: str) -> bool: def add_plugin_directory(self, directory: str) -> bool:
"""添加插件目录""" """添加插件目录"""
@@ -63,6 +50,8 @@ class PluginManager:
logger.warning(f"插件目录不存在: {directory}") logger.warning(f"插件目录不存在: {directory}")
return False return False
# === 插件加载管理 ===
def load_all_plugins(self) -> Tuple[int, int]: def load_all_plugins(self) -> Tuple[int, int]:
"""加载所有插件 """加载所有插件
@@ -86,7 +75,7 @@ class PluginManager:
total_failed_registration = 0 total_failed_registration = 0
for plugin_name in self.plugin_classes.keys(): for plugin_name in self.plugin_classes.keys():
load_status, count = self.load_registered_plugin_classes(plugin_name) load_status, count = self._load_registered_plugin_classes(plugin_name)
if load_status: if load_status:
total_registered += 1 total_registered += 1
else: else:
@@ -96,90 +85,32 @@ class PluginManager:
return total_registered, total_failed_registration return total_registered, total_failed_registration
def load_registered_plugin_classes(self, plugin_name: str) -> Tuple[bool, int]: async def remove_registered_plugin(self, plugin_name: str) -> None:
# sourcery skip: extract-duplicate-method, extract-method
""" """
加载已经注册的插件类 禁用插件模块
""" """
plugin_class = self.plugin_classes.get(plugin_name) if not plugin_name:
if not plugin_class: raise ValueError("插件名称不能为空")
logger.error(f"插件 {plugin_name} 的插件类未注册或不存在") if plugin_name not in self.loaded_plugins:
return False, 1 logger.warning(f"插件 {plugin_name} 未加载")
try: return
# 使用记录的插件目录路径 plugin_instance = self.loaded_plugins[plugin_name]
plugin_dir = self.plugin_paths.get(plugin_name) plugin_info = plugin_instance.plugin_info
for component in plugin_info.components:
await component_registry.remove_component(component.name, component.component_type)
del self.loaded_plugins[plugin_name]
# 如果没有记录,直接返回失败 async def reload_registered_plugin_module(self, plugin_name: str) -> None:
if not plugin_dir:
return False, 1
plugin_instance = plugin_class(plugin_dir=plugin_dir) # 实例化插件可能因为缺少manifest而失败
if not plugin_instance:
logger.error(f"插件 {plugin_name} 实例化失败")
return False, 1
# 检查插件是否启用
if not plugin_instance.enable_plugin:
logger.info(f"插件 {plugin_name} 已禁用,跳过加载")
return False, 0
# 检查版本兼容性
is_compatible, compatibility_error = self._check_plugin_version_compatibility(
plugin_name, plugin_instance.manifest_data
)
if not is_compatible:
self.failed_plugins[plugin_name] = compatibility_error
logger.error(f"❌ 插件加载失败: {plugin_name} - {compatibility_error}")
return False, 1
if plugin_instance.register_plugin():
self.loaded_plugins[plugin_name] = plugin_instance
self._show_plugin_components(plugin_name)
return True, 1
else:
self.failed_plugins[plugin_name] = "插件注册失败"
logger.error(f"❌ 插件注册失败: {plugin_name}")
return False, 1
except FileNotFoundError as e:
# manifest文件缺失
error_msg = f"缺少manifest文件: {str(e)}"
self.failed_plugins[plugin_name] = error_msg
logger.error(f"❌ 插件加载失败: {plugin_name} - {error_msg}")
return False, 1
except ValueError as e:
# manifest文件格式错误或验证失败
traceback.print_exc()
error_msg = f"manifest验证失败: {str(e)}"
self.failed_plugins[plugin_name] = error_msg
logger.error(f"❌ 插件加载失败: {plugin_name} - {error_msg}")
return False, 1
except Exception as e:
# 其他错误
error_msg = f"未知错误: {str(e)}"
self.failed_plugins[plugin_name] = error_msg
logger.error(f"❌ 插件加载失败: {plugin_name} - {error_msg}")
logger.debug("详细错误信息: ", exc_info=True)
return False, 1
def unload_registered_plugin_module(self, plugin_name: str) -> None:
"""
卸载插件模块
"""
pass
def reload_registered_plugin_module(self, plugin_name: str) -> None:
""" """
重载插件模块 重载插件模块
""" """
self.unload_registered_plugin_module(plugin_name) await self.remove_registered_plugin(plugin_name)
self.load_registered_plugin_classes(plugin_name) self._load_registered_plugin_classes(plugin_name)
def rescan_plugin_directory(self) -> None: def rescan_plugin_directory(self) -> None:
""" """
重新扫描插件根目录 重新扫描插件根目录
""" """
# --------------------------------------- NEED REFACTORING ---------------------------------------
for directory in self.plugin_directories: for directory in self.plugin_directories:
if os.path.exists(directory): if os.path.exists(directory):
logger.debug(f"重新扫描插件根目录: {directory}") logger.debug(f"重新扫描插件根目录: {directory}")
@@ -195,30 +126,6 @@ class PluginManager:
"""获取所有启用的插件信息""" """获取所有启用的插件信息"""
return list(component_registry.get_enabled_plugins().values()) return list(component_registry.get_enabled_plugins().values())
# def enable_plugin(self, plugin_name: str) -> bool:
# # -------------------------------- NEED REFACTORING --------------------------------
# """启用插件"""
# if plugin_info := component_registry.get_plugin_info(plugin_name):
# plugin_info.enabled = True
# # 启用插件的所有组件
# for component in plugin_info.components:
# component_registry.enable_component(component.name)
# logger.debug(f"已启用插件: {plugin_name}")
# return True
# return False
# def disable_plugin(self, plugin_name: str) -> bool:
# # -------------------------------- NEED REFACTORING --------------------------------
# """禁用插件"""
# if plugin_info := component_registry.get_plugin_info(plugin_name):
# plugin_info.enabled = False
# # 禁用插件的所有组件
# for component in plugin_info.components:
# component_registry.disable_component(component.name)
# logger.debug(f"已禁用插件: {plugin_name}")
# return True
# return False
def get_plugin_instance(self, plugin_name: str) -> Optional["PluginBase"]: def get_plugin_instance(self, plugin_name: str) -> Optional["PluginBase"]:
"""获取插件实例 """获取插件实例
@@ -230,25 +137,6 @@ class PluginManager:
""" """
return self.loaded_plugins.get(plugin_name) return self.loaded_plugins.get(plugin_name)
def get_plugin_stats(self) -> Dict[str, Any]:
"""获取插件统计信息"""
all_plugins = component_registry.get_all_plugins()
enabled_plugins = component_registry.get_enabled_plugins()
action_components = component_registry.get_components_by_type(ComponentType.ACTION)
command_components = component_registry.get_components_by_type(ComponentType.COMMAND)
return {
"total_plugins": len(all_plugins),
"enabled_plugins": len(enabled_plugins),
"failed_plugins": len(self.failed_plugins),
"total_components": len(action_components) + len(command_components),
"action_components": len(action_components),
"command_components": len(command_components),
"loaded_plugin_files": len(self.loaded_plugins),
"failed_plugin_details": self.failed_plugins.copy(),
}
def check_all_dependencies(self, auto_install: bool = False) -> Dict[str, Any]: def check_all_dependencies(self, auto_install: bool = False) -> Dict[str, Any]:
"""检查所有插件的Python依赖包 """检查所有插件的Python依赖包
@@ -347,6 +235,24 @@ class PluginManager:
return dependency_manager.generate_requirements_file(all_dependencies, output_path) return dependency_manager.generate_requirements_file(all_dependencies, output_path)
# === 私有方法 ===
# == 目录管理 ==
def _ensure_plugin_directories(self) -> None:
"""确保所有插件根目录存在,如果不存在则创建"""
default_directories = ["src/plugins/built_in", "plugins"]
for directory in default_directories:
if not os.path.exists(directory):
os.makedirs(directory, exist_ok=True)
logger.info(f"创建插件根目录: {directory}")
if directory not in self.plugin_directories:
self.plugin_directories.append(directory)
logger.debug(f"已添加插件根目录: {directory}")
else:
logger.warning(f"根目录不可重复加载: {directory}")
# == 插件加载 ==
def _load_plugin_modules_from_directory(self, directory: str) -> tuple[int, int]: def _load_plugin_modules_from_directory(self, directory: str) -> tuple[int, int]:
"""从指定目录加载插件模块""" """从指定目录加载插件模块"""
loaded_count = 0 loaded_count = 0
@@ -372,18 +278,6 @@ class PluginManager:
return loaded_count, failed_count return loaded_count, failed_count
def _find_plugin_directory(self, plugin_class: Type[PluginBase]) -> Optional[str]:
"""查找插件类对应的目录路径"""
try:
# module = getmodule(plugin_class)
# if module and hasattr(module, "__file__") and module.__file__:
# return os.path.dirname(module.__file__)
file_path = inspect.getfile(plugin_class)
return os.path.dirname(file_path)
except Exception as e:
logger.debug(f"通过inspect获取插件目录失败: {e}")
return None
def _load_plugin_module_file(self, plugin_file: str) -> bool: def _load_plugin_module_file(self, plugin_file: str) -> bool:
# sourcery skip: extract-method # sourcery skip: extract-method
"""加载单个插件模块文件 """加载单个插件模块文件
@@ -416,6 +310,74 @@ class PluginManager:
self.failed_plugins[module_name] = error_msg self.failed_plugins[module_name] = error_msg
return False return False
def _load_registered_plugin_classes(self, plugin_name: str) -> Tuple[bool, int]:
# sourcery skip: extract-duplicate-method, extract-method
"""
加载已经注册的插件类
"""
plugin_class = self.plugin_classes.get(plugin_name)
if not plugin_class:
logger.error(f"插件 {plugin_name} 的插件类未注册或不存在")
return False, 1
try:
# 使用记录的插件目录路径
plugin_dir = self.plugin_paths.get(plugin_name)
# 如果没有记录,直接返回失败
if not plugin_dir:
return False, 1
plugin_instance = plugin_class(plugin_dir=plugin_dir) # 实例化插件可能因为缺少manifest而失败
if not plugin_instance:
logger.error(f"插件 {plugin_name} 实例化失败")
return False, 1
# 检查插件是否启用
if not plugin_instance.enable_plugin:
logger.info(f"插件 {plugin_name} 已禁用,跳过加载")
return False, 0
# 检查版本兼容性
is_compatible, compatibility_error = self._check_plugin_version_compatibility(
plugin_name, plugin_instance.manifest_data
)
if not is_compatible:
self.failed_plugins[plugin_name] = compatibility_error
logger.error(f"❌ 插件加载失败: {plugin_name} - {compatibility_error}")
return False, 1
if plugin_instance.register_plugin():
self.loaded_plugins[plugin_name] = plugin_instance
self._show_plugin_components(plugin_name)
return True, 1
else:
self.failed_plugins[plugin_name] = "插件注册失败"
logger.error(f"❌ 插件注册失败: {plugin_name}")
return False, 1
except FileNotFoundError as e:
# manifest文件缺失
error_msg = f"缺少manifest文件: {str(e)}"
self.failed_plugins[plugin_name] = error_msg
logger.error(f"❌ 插件加载失败: {plugin_name} - {error_msg}")
return False, 1
except ValueError as e:
# manifest文件格式错误或验证失败
traceback.print_exc()
error_msg = f"manifest验证失败: {str(e)}"
self.failed_plugins[plugin_name] = error_msg
logger.error(f"❌ 插件加载失败: {plugin_name} - {error_msg}")
return False, 1
except Exception as e:
# 其他错误
error_msg = f"未知错误: {str(e)}"
self.failed_plugins[plugin_name] = error_msg
logger.error(f"❌ 插件加载失败: {plugin_name} - {error_msg}")
logger.debug("详细错误信息: ", exc_info=True)
return False, 1
# == 兼容性检查 ==
def _check_plugin_version_compatibility(self, plugin_name: str, manifest_data: Dict[str, Any]) -> Tuple[bool, str]: def _check_plugin_version_compatibility(self, plugin_name: str, manifest_data: Dict[str, Any]) -> Tuple[bool, str]:
"""检查插件版本兼容性 """检查插件版本兼容性
@@ -451,6 +413,8 @@ class PluginManager:
logger.warning(f"插件 {plugin_name} 版本兼容性检查失败: {e}") logger.warning(f"插件 {plugin_name} 版本兼容性检查失败: {e}")
return False, f"插件 {plugin_name} 版本兼容性检查失败: {e}" # 检查失败时默认不允许加载 return False, f"插件 {plugin_name} 版本兼容性检查失败: {e}" # 检查失败时默认不允许加载
# == 显示统计与插件信息 ==
def _show_stats(self, total_registered: int, total_failed_registration: int): def _show_stats(self, total_registered: int, total_failed_registration: int):
# sourcery skip: low-code-quality # sourcery skip: low-code-quality
# 获取组件统计信息 # 获取组件统计信息
@@ -493,9 +457,15 @@ class PluginManager:
# 组件列表 # 组件列表
if plugin_info.components: if plugin_info.components:
action_components = [c for c in plugin_info.components if c.component_type == ComponentType.ACTION] action_components = [
command_components = [c for c in plugin_info.components if c.component_type == ComponentType.COMMAND] c for c in plugin_info.components if c.component_type == ComponentType.ACTION
event_handler_components = [c for c in plugin_info.components if c.component_type == ComponentType.EVENT_HANDLER] ]
command_components = [
c for c in plugin_info.components if c.component_type == ComponentType.COMMAND
]
event_handler_components = [
c for c in plugin_info.components if c.component_type == ComponentType.EVENT_HANDLER
]
if action_components: if action_components:
action_names = [c.name for c in action_components] action_names = [c.name for c in action_components]
@@ -504,7 +474,7 @@ class PluginManager:
if command_components: if command_components:
command_names = [c.name for c in command_components] command_names = [c.name for c in command_components]
logger.info(f" ⚡ Command组件: {', '.join(command_names)}") logger.info(f" ⚡ Command组件: {', '.join(command_names)}")
if event_handler_components: if event_handler_components:
event_handler_names = [c.name for c in event_handler_components] event_handler_names = [c.name for c in event_handler_components]
logger.info(f" 📢 EventHandler组件: {', '.join(event_handler_names)}") logger.info(f" 📢 EventHandler组件: {', '.join(event_handler_names)}")