From 76025032a91a19596fe5ed897eff80b02f963c7d Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Tue, 22 Jul 2025 18:52:11 +0800 Subject: [PATCH 1/4] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BA=86=E5=8D=B8?= =?UTF-8?q?=E8=BD=BD=E5=92=8C=E9=87=8D=E8=BD=BD=E6=8F=92=E4=BB=B6=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- changes.md | 6 +- src/chat/planner_actions/action_manager.py | 166 +++++------ src/chat/planner_actions/action_modifier.py | 3 +- src/chat/planner_actions/planner.py | 12 +- src/chat/utils/utils.py | 6 +- src/plugin_system/apis/plugin_register_api.py | 2 +- src/plugin_system/core/component_registry.py | 39 ++- src/plugin_system/core/events_manager.py | 27 +- src/plugin_system/core/plugin_manager.py | 268 ++++++++---------- 9 files changed, 258 insertions(+), 271 deletions(-) diff --git a/changes.md b/changes.md index 407537d28..86b2f9b28 100644 --- a/changes.md +++ b/changes.md @@ -45,6 +45,7 @@ 10. 修正了`main.py`中的错误输出。 11. 修正了`command`所编译的`Pattern`注册时的错误输出。 12. `events_manager`有了task相关逻辑了。 +13. 现在有了插件卸载和重载功能了,也就是热插拔。 ### TODO 把这个看起来就很别扭的config获取方式改一下 @@ -64,4 +65,7 @@ else: plugin_path = Path(plugin_file) module_name = ".".join(plugin_path.parent.parts) ``` -这两个区别很大的。 \ No newline at end of file +这两个区别很大的。 + +### 执笔BGM +塞壬唱片! \ No newline at end of file diff --git a/src/chat/planner_actions/action_manager.py b/src/chat/planner_actions/action_manager.py index a2f4c37bd..0c6e17400 100644 --- a/src/chat/planner_actions/action_manager.py +++ b/src/chat/planner_actions/action_manager.py @@ -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.chat.message_receive.chat_stream import ChatStream from src.common.logger import get_logger @@ -27,24 +29,12 @@ class ActionManager: # 当前正在使用的动作集合,默认加载默认动作 self._using_actions: Dict[str, ActionInfo] = {} - # 加载插件动作 - self._load_plugin_actions() + # 初始化管理器注册表 + self._load_plugin_system_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: """从插件系统的component_registry加载Action组件""" try: @@ -58,18 +48,16 @@ class ActionManager: self._registered_actions[action_name] = action_info - logger.debug( - f"从插件系统加载Action组件: {action_name} (插件: {getattr(action_info, 'plugin_name', 'unknown')})" - ) + logger.debug(f"从插件系统加载Action组件: {action_name} (插件: {action_info.plugin_name})") logger.info(f"加载了 {len(action_components)} 个Action动作") - + logger.debug("从插件系统加载Action组件成功") except Exception as e: logger.error(f"从插件系统加载Action组件失败: {e}") - import traceback - logger.error(traceback.format_exc()) + # === 执行Action方法 === + def create_action( self, action_name: str, @@ -147,28 +135,37 @@ class ActionManager: """获取当前正在使用的动作集合""" 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: 动作名称 - Returns: 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} 未注册") return False - - 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} 到使用集") + self._registered_actions[action_name] = component_info 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: """ 从当前使用的动作集中移除指定动作 @@ -187,79 +184,52 @@ class ActionManager: logger.debug(f"已从使用集中移除动作 {action_name}") 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: """恢复到默认动作集""" actions_to_restore = list(self._using_actions.keys()) self._using_actions = component_registry.get_default_actions() 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: - 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 + # Args: + # action_name: 动作名称 - 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: - action_name: 动作名称 + # if action_name in self._using_actions: + # logger.info(f"动作 {action_name} 已经在使用中") + # return True - Returns: - Optional[Type[BaseAction]]: 动作处理器类,如果不存在则返回None - """ - from src.plugin_system.core.component_registry import component_registry + # self._using_actions[action_name] = self._registered_actions[action_name] + # logger.info(f"添加动作 {action_name} 到使用集") + # return True - 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 diff --git a/src/chat/planner_actions/action_modifier.py b/src/chat/planner_actions/action_modifier.py index 93be49842..bdc4a2f3a 100644 --- a/src/chat/planner_actions/action_modifier.py +++ b/src/chat/planner_actions/action_modifier.py @@ -47,7 +47,6 @@ class ActionModifier: async def modify_actions( self, - history_loop=None, message_content: str = "", ): # sourcery skip: use-named-expression """ @@ -318,7 +317,7 @@ class ActionModifier: action_name: str, action_info: ActionInfo, chat_content: str = "", - ) -> bool: + ) -> bool: # sourcery skip: move-assign-in-block, use-named-expression """ 使用LLM判定是否应该激活某个action diff --git a/src/chat/planner_actions/planner.py b/src/chat/planner_actions/planner.py index 61fc2f4d6..09d0a5edc 100644 --- a/src/chat/planner_actions/planner.py +++ b/src/chat/planner_actions/planner.py @@ -113,7 +113,6 @@ class ActionPlanner: try: is_group_chat = True - 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}") @@ -234,10 +233,13 @@ class ActionPlanner: "is_parallel": is_parallel, } - return { - "action_result": action_result, - "action_prompt": prompt, - }, target_message + return ( + { + "action_result": action_result, + "action_prompt": prompt, + }, + target_message, + ) async def build_planner_prompt( self, diff --git a/src/chat/utils/utils.py b/src/chat/utils/utils.py index 071f1886c..e7d2caddb 100644 --- a/src/chat/utils/utils.py +++ b/src/chat/utils/utils.py @@ -619,9 +619,7 @@ def get_chat_type_and_target_info(chat_id: str) -> Tuple[bool, Optional[Dict]]: chat_target_info = None try: - chat_stream = get_chat_manager().get_stream(chat_id) - - if chat_stream: + if chat_stream := get_chat_manager().get_stream(chat_id): if chat_stream.group_info: is_group_chat = True 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 else: logger.warning(f"无法获取 chat_stream for {chat_id} in utils") - # Keep defaults: is_group_chat=False, chat_target_info=None - except Exception as e: logger.error(f"获取聊天类型和目标信息时出错 for {chat_id}: {e}", exc_info=True) # Keep defaults on error diff --git a/src/plugin_system/apis/plugin_register_api.py b/src/plugin_system/apis/plugin_register_api.py index 879c09b32..e4ba2ee48 100644 --- a/src/plugin_system/apis/plugin_register_api.py +++ b/src/plugin_system/apis/plugin_register_api.py @@ -28,7 +28,6 @@ def register_plugin(cls): if "." in plugin_name: logger.error(f"插件名称 '{plugin_name}' 包含非法字符 '.',请使用下划线替代") raise ValueError(f"插件名称 '{plugin_name}' 包含非法字符 '.',请使用下划线替代") - plugin_manager.plugin_classes[plugin_name] = cls splitted_name = cls.__module__.split(".") root_path = Path(__file__) @@ -40,6 +39,7 @@ def register_plugin(cls): logger.error(f"注册 {plugin_name} 无法找到项目根目录") return cls + plugin_manager.plugin_classes[plugin_name] = cls plugin_manager.plugin_paths[plugin_name] = str(Path(root_path, *splitted_name).resolve()) logger.debug(f"插件类已注册: {plugin_name}, 路径: {plugin_manager.plugin_paths[plugin_name]}") diff --git a/src/plugin_system/core/component_registry.py b/src/plugin_system/core/component_registry.py index 7283cf9eb..a804c5be7 100644 --- a/src/plugin_system/core/component_registry.py +++ b/src/plugin_system/core/component_registry.py @@ -27,7 +27,7 @@ class ComponentRegistry: def __init__(self): # 组件注册表 self._components: Dict[str, ComponentInfo] = {} # 命名空间式组件名 -> 组件信息 - # 类型 -> 命名空间式名称 -> 组件信息 + # 类型 -> 组件原名称 -> 组件信息 self._components_by_type: Dict[ComponentType, Dict[str, ComponentInfo]] = {types: {} for types in ComponentType} # 命名空间式组件名 -> 组件类 self._components_classes: Dict[str, Type[Union[BaseCommand, BaseAction, BaseEventHandler]]] = {} @@ -160,7 +160,9 @@ class ComponentRegistry: if pattern not in self._command_patterns: self._command_patterns[pattern] = command_name else: - logger.warning(f"'{command_name}' 对应的命令模式与 '{self._command_patterns[pattern]}' 重复,忽略此命令") + logger.warning( + f"'{command_name}' 对应的命令模式与 '{self._command_patterns[pattern]}' 重复,忽略此命令" + ) return True @@ -176,6 +178,10 @@ class ComponentRegistry: 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 # 延迟导入防止循环导入问题 if events_manager.register_event_subscriber(handler_info, handler_class): @@ -185,6 +191,33 @@ class ComponentRegistry: logger.error(f"注册事件处理器 {handler_name} 失败") 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( self, component_name: str, component_type: Optional[ComponentType] = None @@ -287,7 +320,7 @@ class ComponentRegistry: # === Action特定查询方法 === def get_action_registry(self) -> Dict[str, Type[BaseAction]]: - """获取Action注册表(用于兼容现有系统)""" + """获取Action注册表""" return self._action_registry.copy() def get_registered_action_info(self, action_name: str) -> Optional[ActionInfo]: diff --git a/src/plugin_system/core/events_manager.py b/src/plugin_system/core/events_manager.py index 6352c4a09..bcaef59e7 100644 --- a/src/plugin_system/core/events_manager.py +++ b/src/plugin_system/core/events_manager.py @@ -28,18 +28,16 @@ class EventsManager: bool: 是否注册成功 """ handler_name = handler_info.name - plugin_name = getattr(handler_info, "plugin_name", "unknown") - namespace_name = f"{plugin_name}.{handler_name}" - if namespace_name in self._handler_mapping: - logger.warning(f"事件处理器 {namespace_name} 已存在,跳过注册") + if handler_name in self._handler_mapping: + logger.warning(f"事件处理器 {handler_name} 已存在,跳过注册") return False if not issubclass(handler_class, BaseEventHandler): logger.error(f"类 {handler_class.__name__} 不是 BaseEventHandler 的子类") 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) async def handle_mai_events( @@ -71,7 +69,7 @@ class EventsManager: try: handler_task = asyncio.create_task(handler.execute(transformed_message)) 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) except Exception as e: logger.error(f"创建事件处理器任务 {handler.handler_name} 时发生异常: {e}") @@ -91,7 +89,7 @@ class EventsManager: 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__ if handler_class.event_type == EventType.UNKNOWN: @@ -190,5 +188,20 @@ class EventsManager: finally: 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() diff --git a/src/plugin_system/core/plugin_manager.py b/src/plugin_system/core/plugin_manager.py index 3ce9c9e52..59dad8bbd 100644 --- a/src/plugin_system/core/plugin_manager.py +++ b/src/plugin_system/core/plugin_manager.py @@ -1,5 +1,4 @@ import os -import inspect import traceback 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.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.component_types import ComponentType, PluginInfo, PythonDependency 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") @@ -36,19 +35,7 @@ class PluginManager: self._ensure_plugin_directories() 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: """添加插件目录""" @@ -63,6 +50,8 @@ class PluginManager: logger.warning(f"插件目录不存在: {directory}") return False + # === 插件加载管理 === + def load_all_plugins(self) -> Tuple[int, int]: """加载所有插件 @@ -86,7 +75,7 @@ class PluginManager: total_failed_registration = 0 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: total_registered += 1 else: @@ -96,90 +85,32 @@ class PluginManager: return total_registered, total_failed_registration - def load_registered_plugin_classes(self, plugin_name: str) -> Tuple[bool, int]: - # sourcery skip: extract-duplicate-method, extract-method + async def remove_registered_plugin(self, plugin_name: str) -> None: """ - 加载已经注册的插件类 + 禁用插件模块 """ - 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_name: + raise ValueError("插件名称不能为空") + if plugin_name not in self.loaded_plugins: + logger.warning(f"插件 {plugin_name} 未加载") + return + plugin_instance = self.loaded_plugins[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] - # 如果没有记录,直接返回失败 - 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: + async def reload_registered_plugin_module(self, plugin_name: str) -> None: """ 重载插件模块 """ - self.unload_registered_plugin_module(plugin_name) - self.load_registered_plugin_classes(plugin_name) + await self.remove_registered_plugin(plugin_name) + self._load_registered_plugin_classes(plugin_name) def rescan_plugin_directory(self) -> None: """ 重新扫描插件根目录 """ - # --------------------------------------- NEED REFACTORING --------------------------------------- for directory in self.plugin_directories: if os.path.exists(directory): logger.debug(f"重新扫描插件根目录: {directory}") @@ -195,30 +126,6 @@ class PluginManager: """获取所有启用的插件信息""" 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"]: """获取插件实例 @@ -230,25 +137,6 @@ class PluginManager: """ 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]: """检查所有插件的Python依赖包 @@ -347,6 +235,24 @@ class PluginManager: 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]: """从指定目录加载插件模块""" loaded_count = 0 @@ -372,18 +278,6 @@ class PluginManager: 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: # sourcery skip: extract-method """加载单个插件模块文件 @@ -416,6 +310,74 @@ class PluginManager: self.failed_plugins[module_name] = error_msg 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]: """检查插件版本兼容性 @@ -451,6 +413,8 @@ class PluginManager: logger.warning(f"插件 {plugin_name} 版本兼容性检查失败: {e}") return False, f"插件 {plugin_name} 版本兼容性检查失败: {e}" # 检查失败时默认不允许加载 + # == 显示统计与插件信息 == + def _show_stats(self, total_registered: int, total_failed_registration: int): # sourcery skip: low-code-quality # 获取组件统计信息 @@ -493,9 +457,15 @@ class PluginManager: # 组件列表 if plugin_info.components: - action_components = [c for c in plugin_info.components if c.component_type == ComponentType.ACTION] - 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] + action_components = [ + c for c in plugin_info.components if c.component_type == ComponentType.ACTION + ] + 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: action_names = [c.name for c in action_components] @@ -504,7 +474,7 @@ class PluginManager: if command_components: command_names = [c.name for c in command_components] logger.info(f" ⚡ Command组件: {', '.join(command_names)}") - + if event_handler_components: event_handler_names = [c.name for c in event_handler_components] logger.info(f" 📢 EventHandler组件: {', '.join(event_handler_names)}") From 35ec390dfdde3c6cc41b1dab9016d23624c47c29 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Tue, 22 Jul 2025 22:38:40 +0800 Subject: [PATCH 2/4] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BA=86=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E7=9A=84=E5=85=A8=E5=B1=80=E5=90=AF=E7=94=A8=E5=92=8C?= =?UTF-8?q?=E7=A6=81=E7=94=A8=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- changes.md | 3 + src/chat/chat_loop/heartFC_chat.py | 81 +++++++++++--------- src/chat/message_receive/chat_stream.py | 11 ++- src/plugin_system/core/component_registry.py | 77 ++++++++++++++++++- 4 files changed, 129 insertions(+), 43 deletions(-) diff --git a/changes.md b/changes.md index 86b2f9b28..9ca193ac7 100644 --- a/changes.md +++ b/changes.md @@ -46,6 +46,9 @@ 11. 修正了`command`所编译的`Pattern`注册时的错误输出。 12. `events_manager`有了task相关逻辑了。 13. 现在有了插件卸载和重载功能了,也就是热插拔。 +14. 实现了组件的全局启用和禁用功能。 + - 通过`enable_component`和`disable_component`方法来启用或禁用组件。 + - 不过这个操作不会保存到配置文件~ ### TODO 把这个看起来就很别扭的config获取方式改一下 diff --git a/src/chat/chat_loop/heartFC_chat.py b/src/chat/chat_loop/heartFC_chat.py index 5a82e8390..efe7413cf 100644 --- a/src/chat/chat_loop/heartFC_chat.py +++ b/src/chat/chat_loop/heartFC_chat.py @@ -21,7 +21,7 @@ from src.plugin_system.base.component_types import ActionInfo, ChatMode from src.plugin_system.apis import generator_api, send_api, message_api from src.chat.willing.willing_manager import get_willing_manager from src.chat.mai_thinking.mai_think import mai_thinking_manager -from maim_message.message_base import GroupInfo,UserInfo +from maim_message.message_base import GroupInfo ENABLE_THINKING = False @@ -257,31 +257,29 @@ class HeartFChatting: ) person_name = await person_info_manager.get_value(person_id, "person_name") return f"{person_name}:{message_data.get('processed_plain_text')}" - + async def send_typing(self): - group_info = GroupInfo(platform = "amaidesu_default",group_id = 114514,group_name = "内心") - - chat = await get_chat_manager().get_or_create_stream( - platform = "amaidesu_default", - user_info = None, - group_info = group_info + group_info = GroupInfo(platform="amaidesu_default", group_id="114514", group_name="内心") + + chat = await get_chat_manager().get_or_create_stream( + platform="amaidesu_default", + user_info=None, # type: ignore + group_info=group_info, ) - - + await send_api.custom_to_stream( message_type="state", content="typing", stream_id=chat.stream_id, storage_message=False ) - + async def stop_typing(self): - group_info = GroupInfo(platform = "amaidesu_default",group_id = 114514,group_name = "内心") - - chat = await get_chat_manager().get_or_create_stream( - platform = "amaidesu_default", - user_info = None, - group_info = group_info + group_info = GroupInfo(platform="amaidesu_default", group_id="114514", group_name="内心") + + chat = await get_chat_manager().get_or_create_stream( + platform="amaidesu_default", + user_info=None, # type: ignore + group_info=group_info, ) - - + await send_api.custom_to_stream( message_type="state", content="stop_typing", stream_id=chat.stream_id, storage_message=False ) @@ -295,7 +293,7 @@ class HeartFChatting: cycle_timers, thinking_id = self.start_cycle() logger.info(f"{self.log_prefix} 开始第{self._cycle_counter}次思考[模式:{self.loop_mode}]") - + await self.send_typing() async with global_prompt_manager.async_message_scope(self.chat_stream.context.get_template_name()): @@ -364,15 +362,12 @@ class HeartFChatting: logger.info(f"[{self.log_prefix}] {global_config.bot.nickname} 决定的回复内容: {content}") # 发送回复 (不再需要传入 chat) - reply_text = await self._send_response(response_set, reply_to_str, loop_start_time,message_data) - + reply_text = await self._send_response(response_set, reply_to_str, loop_start_time, message_data) + await self.stop_typing() - - - + if ENABLE_THINKING: await mai_thinking_manager.get_mai_think(self.stream_id).do_think_after_response(reply_text) - return True @@ -504,10 +499,9 @@ class HeartFChatting: """ interested_rate = (message_data.get("interest_value") or 0.0) * self.willing_amplifier - + self.willing_manager.setup(message_data, self.chat_stream) - - + reply_probability = await self.willing_manager.get_reply_probability(message_data.get("message_id", "")) talk_frequency = -1.00 @@ -517,7 +511,7 @@ class HeartFChatting: if additional_config and "maimcore_reply_probability_gain" in additional_config: reply_probability += additional_config["maimcore_reply_probability_gain"] reply_probability = min(max(reply_probability, 0), 1) # 确保概率在 0-1 之间 - + talk_frequency = global_config.chat.get_current_talk_frequency(self.stream_id) reply_probability = talk_frequency * reply_probability @@ -527,9 +521,9 @@ class HeartFChatting: # 打印消息信息 mes_name = self.chat_stream.group_info.group_name if self.chat_stream.group_info else "私聊" - + # logger.info(f"[{mes_name}] 当前聊天频率: {talk_frequency:.2f},兴趣值: {interested_rate:.2f},回复概率: {reply_probability * 100:.1f}%") - + if reply_probability > 0.05: logger.info( f"[{mes_name}]" @@ -545,7 +539,6 @@ class HeartFChatting: # 意愿管理器:注销当前message信息 (无论是否回复,只要处理过就删除) self.willing_manager.delete(message_data.get("message_id", "")) return False - async def _generate_response( self, message_data: dict, available_actions: Optional[Dict[str, ActionInfo]], reply_to: str @@ -570,7 +563,7 @@ class HeartFChatting: logger.error(f"[{self.log_prefix}] 回复生成出现错误:{str(e)} {traceback.format_exc()}") return None - async def _send_response(self, reply_set, reply_to, thinking_start_time,message_data): + async def _send_response(self, reply_set, reply_to, thinking_start_time, message_data): current_time = time.time() new_message_count = message_api.count_new_messages( chat_id=self.chat_stream.stream_id, start_time=thinking_start_time, end_time=current_time @@ -592,13 +585,27 @@ class HeartFChatting: if not first_replied: if need_reply: await send_api.text_to_stream( - text=data, stream_id=self.chat_stream.stream_id, reply_to=reply_to, reply_to_platform_id=reply_to_platform_id, typing=False + text=data, + stream_id=self.chat_stream.stream_id, + reply_to=reply_to, + reply_to_platform_id=reply_to_platform_id, + typing=False, ) else: - await send_api.text_to_stream(text=data, stream_id=self.chat_stream.stream_id, reply_to_platform_id=reply_to_platform_id, typing=False) + await send_api.text_to_stream( + text=data, + stream_id=self.chat_stream.stream_id, + reply_to_platform_id=reply_to_platform_id, + typing=False, + ) first_replied = True else: - await send_api.text_to_stream(text=data, stream_id=self.chat_stream.stream_id, reply_to_platform_id=reply_to_platform_id, typing=True) + await send_api.text_to_stream( + text=data, + stream_id=self.chat_stream.stream_id, + reply_to_platform_id=reply_to_platform_id, + typing=True, + ) reply_text += data return reply_text diff --git a/src/chat/message_receive/chat_stream.py b/src/chat/message_receive/chat_stream.py index e4a61900e..2ee2be05a 100644 --- a/src/chat/message_receive/chat_stream.py +++ b/src/chat/message_receive/chat_stream.py @@ -163,20 +163,25 @@ class ChatManager: """注册消息到聊天流""" stream_id = self._generate_stream_id( message.message_info.platform, # type: ignore - message.message_info.user_info, # type: ignore + message.message_info.user_info, message.message_info.group_info, ) self.last_messages[stream_id] = message # logger.debug(f"注册消息到聊天流: {stream_id}") @staticmethod - def _generate_stream_id(platform: str, user_info: UserInfo, group_info: Optional[GroupInfo] = None) -> str: + def _generate_stream_id( + platform: str, user_info: Optional[UserInfo], group_info: Optional[GroupInfo] = None + ) -> str: """生成聊天流唯一ID""" + if not user_info and not group_info: + raise ValueError("用户信息或群组信息必须提供") + if group_info: # 组合关键信息 components = [platform, str(group_info.group_id)] else: - components = [platform, str(user_info.user_id), "private"] + components = [platform, str(user_info.user_id), "private"] # type: ignore # 使用MD5生成唯一ID key = "_".join(components) diff --git a/src/plugin_system/core/component_registry.py b/src/plugin_system/core/component_registry.py index a804c5be7..e9509dd9b 100644 --- a/src/plugin_system/core/component_registry.py +++ b/src/plugin_system/core/component_registry.py @@ -110,11 +110,17 @@ class ComponentRegistry: # 根据组件类型进行特定注册(使用原始名称) match component_type: case ComponentType.ACTION: - ret = self._register_action_component(component_info, component_class) # type: ignore + assert isinstance(component_info, ActionInfo) + assert issubclass(component_class, BaseAction) + ret = self._register_action_component(component_info, component_class) case ComponentType.COMMAND: - ret = self._register_command_component(component_info, component_class) # type: ignore + assert isinstance(component_info, CommandInfo) + assert issubclass(component_class, BaseCommand) + ret = self._register_command_component(component_info, component_class) case ComponentType.EVENT_HANDLER: - ret = self._register_event_handler_component(component_info, component_class) # type: ignore + assert isinstance(component_info, EventHandlerInfo) + assert issubclass(component_class, BaseEventHandler) + ret = self._register_event_handler_component(component_info, component_class) case _: logger.warning(f"未知组件类型: {component_type}") @@ -218,6 +224,71 @@ class ComponentRegistry: self._components_classes.pop(component_name, None) logger.info(f"组件 {component_name} 已移除") + # === 组件全局启用/禁用方法 === + + def enable_component(self, component_name: str, component_type: ComponentType) -> bool: + """全局的启用某个组件 + Parameters: + component_name: 组件名称 + component_type: 组件类型 + Returns: + bool: 启用成功返回True,失败返回False + """ + target_component_class = self.get_component_class(component_name, component_type) + target_component_info = self.get_component_info(component_name, component_type) + if not target_component_class or not target_component_info: + logger.warning(f"组件 {component_name} 未注册,无法启用") + return False + target_component_info.enabled = True + match component_type: + case ComponentType.ACTION: + assert isinstance(target_component_info, ActionInfo) + self._default_actions[component_name] = target_component_info + case ComponentType.COMMAND: + assert isinstance(target_component_info, CommandInfo) + pattern = target_component_info.command_pattern + self._command_patterns[re.compile(pattern)] = component_name + case ComponentType.EVENT_HANDLER: + assert isinstance(target_component_info, EventHandlerInfo) + assert issubclass(target_component_class, BaseEventHandler) + self._enabled_event_handlers[component_name] = target_component_class + from .events_manager import events_manager # 延迟导入防止循环导入问题 + + events_manager.register_event_subscriber(target_component_info, target_component_class) + self._components[component_name].enabled = True + self._components_by_type[component_type][component_name].enabled = True + logger.info(f"组件 {component_name} 已启用") + return True + + async def disable_component(self, component_name: str, component_type: ComponentType) -> bool: + """全局的禁用某个组件 + Parameters: + component_name: 组件名称 + component_type: 组件类型 + Returns: + bool: 禁用成功返回True,失败返回False + """ + target_component_class = self.get_component_class(component_name, component_type) + target_component_info = self.get_component_info(component_name, component_type) + if not target_component_class or not target_component_info: + logger.warning(f"组件 {component_name} 未注册,无法禁用") + return False + target_component_info.enabled = False + match component_type: + case ComponentType.ACTION: + self._default_actions.pop(component_name, None) + case ComponentType.COMMAND: + self._command_patterns = {k: v for k, v in self._command_patterns.items() if v != component_name} + case ComponentType.EVENT_HANDLER: + self._enabled_event_handlers.pop(component_name, None) + from .events_manager import events_manager # 延迟导入防止循环导入问题 + + await events_manager.unregister_event_subscriber(component_name) + self._components[component_name].enabled = False + self._components_by_type[component_type][component_name].enabled = False + logger.info(f"组件 {component_name} 已禁用") + return True + # === 组件查询方法 === def get_component_info( self, component_name: str, component_type: Optional[ComponentType] = None From 87dd9a3756eae6f40e7d447a0bc48a6ff7b57154 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Tue, 22 Jul 2025 23:12:11 +0800 Subject: [PATCH 3/4] typing fix --- src/individuality/individuality.py | 12 +++++------- src/individuality/personality.py | 9 ++++----- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/individuality/individuality.py b/src/individuality/individuality.py index ac2281d39..bd9e38184 100644 --- a/src/individuality/individuality.py +++ b/src/individuality/individuality.py @@ -173,12 +173,10 @@ class Individuality: personality = short_impression[0] identity = short_impression[1] prompt_personality = f"{personality},{identity}" - identity_block = f"你的名字是{bot_name}{bot_nickname},你{prompt_personality}:" - - return identity_block + return f"你的名字是{bot_name}{bot_nickname},你{prompt_personality}:" def _get_config_hash( - self, bot_nickname: str, personality_core: str, personality_side: str, identity: list + self, bot_nickname: str, personality_core: str, personality_side: str, identity: str ) -> tuple[str, str]: """获取personality和identity配置的哈希值 @@ -197,7 +195,7 @@ class Individuality: # 身份配置哈希 identity_config = { - "identity": sorted(identity), + "identity": identity, "compress_identity": self.personality.compress_identity if self.personality else True, } identity_str = json.dumps(identity_config, sort_keys=True) @@ -206,7 +204,7 @@ class Individuality: return personality_hash, identity_hash async def _check_config_and_clear_if_changed( - self, bot_nickname: str, personality_core: str, personality_side: str, identity: list + self, bot_nickname: str, personality_core: str, personality_side: str, identity: str ) -> tuple[bool, bool]: """检查配置是否发生变化,如果变化则清空相应缓存 @@ -321,7 +319,7 @@ class Individuality: return personality_result - async def _create_identity(self, identity: list) -> str: + async def _create_identity(self, identity: str) -> str: """使用LLM创建压缩版本的impression""" logger.info("正在构建身份.........") diff --git a/src/individuality/personality.py b/src/individuality/personality.py index 87907df76..2a0a17101 100644 --- a/src/individuality/personality.py +++ b/src/individuality/personality.py @@ -1,6 +1,5 @@ - from dataclasses import dataclass -from typing import Dict, List +from typing import Dict, Optional @dataclass @@ -10,7 +9,7 @@ class Personality: bot_nickname: str # 机器人昵称 personality_core: str # 人格核心特点 personality_side: str # 人格侧面描述 - identity: List[str] # 身份细节描述 + identity: Optional[str] # 身份细节描述 compress_personality: bool # 是否压缩人格 compress_identity: bool # 是否压缩身份 @@ -21,7 +20,7 @@ class Personality: cls._instance = super().__new__(cls) return cls._instance - def __init__(self, personality_core: str = "", personality_side: str = "", identity: List[str] = None): + def __init__(self, personality_core: str = "", personality_side: str = "", identity: Optional[str] = None): self.personality_core = personality_core self.personality_side = personality_side self.identity = identity @@ -45,7 +44,7 @@ class Personality: bot_nickname: str, personality_core: str, personality_side: str, - identity: List[str] = None, + identity: Optional[str] = None, compress_personality: bool = True, compress_identity: bool = True, ) -> "Personality": From 10bf424540f700f05708b6e09048009111081dbb Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Wed, 23 Jul 2025 00:41:31 +0800 Subject: [PATCH 4/4] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BA=86=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E7=9A=84=E5=B1=80=E9=83=A8=E7=A6=81=E7=94=A8=E6=96=B9?= =?UTF-8?q?=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- changes.md | 5 + src/chat/chat_loop/heartFC_chat.py | 12 +- src/chat/message_receive/bot.py | 33 +++--- src/chat/planner_actions/action_manager.py | 107 +----------------- src/chat/planner_actions/action_modifier.py | 45 ++++---- src/chat/planner_actions/planner.py | 16 +-- src/plugin_system/__init__.py | 2 + src/plugin_system/base/base_action.py | 9 +- src/plugin_system/base/base_command.py | 7 +- src/plugin_system/base/base_events_handler.py | 19 +++- src/plugin_system/core/__init__.py | 2 + src/plugin_system/core/component_registry.py | 5 +- src/plugin_system/core/events_manager.py | 5 + .../core/global_announcement_manager.py | 90 +++++++++++++++ 14 files changed, 195 insertions(+), 162 deletions(-) create mode 100644 src/plugin_system/core/global_announcement_manager.py diff --git a/changes.md b/changes.md index 9ca193ac7..0776ea653 100644 --- a/changes.md +++ b/changes.md @@ -49,10 +49,15 @@ 14. 实现了组件的全局启用和禁用功能。 - 通过`enable_component`和`disable_component`方法来启用或禁用组件。 - 不过这个操作不会保存到配置文件~ +15. 实现了组件的局部禁用,也就是针对某一个聊天禁用的功能。 + - 通过`disable_specific_chat_action`,`enable_specific_chat_action`,`disable_specific_chat_command`,`enable_specific_chat_command`,`disable_specific_chat_event_handler`,`enable_specific_chat_event_handler`来操作 + - 同样不保存到配置文件~ ### TODO 把这个看起来就很别扭的config获取方式改一下 +来个API管理这些启用禁用! + # 吐槽 ```python diff --git a/src/chat/chat_loop/heartFC_chat.py b/src/chat/chat_loop/heartFC_chat.py index efe7413cf..6f6be3757 100644 --- a/src/chat/chat_loop/heartFC_chat.py +++ b/src/chat/chat_loop/heartFC_chat.py @@ -52,6 +52,8 @@ NO_ACTION = { "action_prompt": "", } +IS_MAI4U = False + install(extra_lines=3) # 注释:原来的动作修改超时常量已移除,因为改为顺序执行 @@ -263,7 +265,7 @@ class HeartFChatting: chat = await get_chat_manager().get_or_create_stream( platform="amaidesu_default", - user_info=None, # type: ignore + user_info=None, group_info=group_info, ) @@ -276,7 +278,7 @@ class HeartFChatting: chat = await get_chat_manager().get_or_create_stream( platform="amaidesu_default", - user_info=None, # type: ignore + user_info=None, group_info=group_info, ) @@ -294,7 +296,8 @@ class HeartFChatting: logger.info(f"{self.log_prefix} 开始第{self._cycle_counter}次思考[模式:{self.loop_mode}]") - await self.send_typing() + if IS_MAI4U: + await self.send_typing() async with global_prompt_manager.async_message_scope(self.chat_stream.context.get_template_name()): loop_start_time = time.time() @@ -364,7 +367,8 @@ class HeartFChatting: # 发送回复 (不再需要传入 chat) reply_text = await self._send_response(response_set, reply_to_str, loop_start_time, message_data) - await self.stop_typing() + if IS_MAI4U: + await self.stop_typing() if ENABLE_THINKING: await mai_thinking_manager.get_mai_think(self.stream_id).do_think_after_response(reply_text) diff --git a/src/chat/message_receive/bot.py b/src/chat/message_receive/bot.py index d229fc94f..b58377e27 100644 --- a/src/chat/message_receive/bot.py +++ b/src/chat/message_receive/bot.py @@ -13,10 +13,9 @@ from src.chat.message_receive.message import MessageRecv, MessageRecvS4U from src.chat.message_receive.storage import MessageStorage from src.chat.heart_flow.heartflow_message_processor import HeartFCMessageReceiver from src.chat.utils.prompt_builder import Prompt, global_prompt_manager -from src.plugin_system.core import component_registry, events_manager # 导入新插件系统 +from src.plugin_system.core import component_registry, events_manager, global_announcement_manager from src.plugin_system.base import BaseCommand, EventType from src.mais4u.mais4u_chat.s4u_msg_processor import S4UMessageProcessor -from src.llm_models.utils_model import LLMRequest # 定义日志配置 @@ -92,8 +91,20 @@ class ChatBot: # 使用新的组件注册中心查找命令 command_result = component_registry.find_command_by_text(text) if command_result: + command_class, matched_groups, command_info = command_result + intercept_message = command_info.intercept_message + plugin_name = command_info.plugin_name + command_name = command_info.name + if ( + message.chat_stream + and message.chat_stream.stream_id + and command_name + in global_announcement_manager.get_disabled_chat_commands(message.chat_stream.stream_id) + ): + logger.info("用户禁用的命令,跳过处理") + return False, None, True + message.is_command = True - command_class, matched_groups, intercept_message, plugin_name = command_result # 获取插件配置 plugin_config = component_registry.get_plugin_config(plugin_name) @@ -135,13 +146,12 @@ class ChatBot: except Exception as e: logger.error(f"处理命令时出错: {e}") return False, None, True # 出错时继续处理消息 - + async def hanle_notice_message(self, message: MessageRecv): if message.message_info.message_id == "notice": logger.info("收到notice消息,暂时不支持处理") return True - - + async def do_s4u(self, message_data: Dict[str, Any]): message = MessageRecvS4U(message_data) group_info = message.message_info.group_info @@ -163,7 +173,6 @@ class ChatBot: return - async def message_process(self, message_data: Dict[str, Any]) -> None: """处理转化后的统一格式消息 这个函数本质是预处理一些数据,根据配置信息和消息内容,预处理消息,并分发到合适的消息处理器中 @@ -179,8 +188,6 @@ class ChatBot: - 性能计时 """ try: - - # 确保所有任务已启动 await self._ensure_started() @@ -201,11 +208,10 @@ class ChatBot: # print(message_data) # logger.debug(str(message_data)) message = MessageRecv(message_data) - + if await self.hanle_notice_message(message): return - - + group_info = message.message_info.group_info user_info = message.message_info.user_info if message.message_info.additional_config: @@ -229,11 +235,10 @@ class ChatBot: # 处理消息内容,生成纯文本 await message.process() - + # if await self.check_ban_content(message): # logger.warning(f"检测到消息中含有违法,色情,暴力,反动,敏感内容,消息内容:{message.processed_plain_text},发送者:{message.message_info.user_info.user_nickname}") # return - # 过滤检查 if _check_ban_words(message.processed_plain_text, chat, user_info) or _check_ban_regex( # type: ignore diff --git a/src/chat/planner_actions/action_manager.py b/src/chat/planner_actions/action_manager.py index 0c6e17400..37f939b92 100644 --- a/src/chat/planner_actions/action_manager.py +++ b/src/chat/planner_actions/action_manager.py @@ -1,5 +1,3 @@ -import traceback - from typing import Dict, Optional, Type from src.plugin_system.base.base_action import BaseAction from src.chat.message_receive.chat_stream import ChatStream @@ -24,38 +22,13 @@ class ActionManager: def __init__(self): """初始化动作管理器""" - # 所有注册的动作集合 - self._registered_actions: Dict[str, ActionInfo] = {} + # 当前正在使用的动作集合,默认加载默认动作 self._using_actions: Dict[str, ActionInfo] = {} - # 初始化管理器注册表 - self._load_plugin_system_actions() - # 初始化时将默认动作加载到使用中的动作 self._using_actions = component_registry.get_default_actions() - def _load_plugin_system_actions(self) -> None: - """从插件系统的component_registry加载Action组件""" - try: - # 获取所有Action组件 - action_components: Dict[str, ActionInfo] = component_registry.get_components_by_type(ComponentType.ACTION) # type: ignore - - for action_name, action_info in action_components.items(): - if action_name in self._registered_actions: - logger.debug(f"Action组件 {action_name} 已存在,跳过") - continue - - self._registered_actions[action_name] = action_info - - logger.debug(f"从插件系统加载Action组件: {action_name} (插件: {action_info.plugin_name})") - - logger.info(f"加载了 {len(action_components)} 个Action动作") - logger.debug("从插件系统加载Action组件成功") - except Exception as e: - logger.error(f"从插件系统加载Action组件失败: {e}") - logger.error(traceback.format_exc()) - # === 执行Action方法 === def create_action( @@ -127,44 +100,10 @@ class ActionManager: logger.error(traceback.format_exc()) return None - def get_registered_actions(self) -> Dict[str, ActionInfo]: - """获取所有已注册的动作集""" - return self._registered_actions.copy() - def get_using_actions(self) -> Dict[str, ActionInfo]: """获取当前正在使用的动作集合""" return self._using_actions.copy() - # === 增删Action方法 === - def add_action(self, action_name: str) -> bool: - """增加一个Action到管理器 - - Parameters: - action_name: 动作名称 - Returns: - bool: 添加是否成功 - """ - 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} 未注册") - return False - self._registered_actions[action_name] = component_info - 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: """ @@ -189,47 +128,3 @@ class ActionManager: actions_to_restore = list(self._using_actions.keys()) self._using_actions = component_registry.get_default_actions() logger.debug(f"恢复动作集: 从 {actions_to_restore} 恢复到默认动作集 {list(self._using_actions.keys())}") - - # def add_action_to_using(self, action_name: str) -> bool: - - # """ - # 添加已注册的动作到当前使用的动作集 - - # Args: - # action_name: 动作名称 - - # Returns: - # bool: 添加是否成功 - # """ - # if action_name not in self._registered_actions: - # logger.warning(f"添加失败: 动作 {action_name} 未注册") - # return False - - # 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 - - # 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 diff --git a/src/chat/planner_actions/action_modifier.py b/src/chat/planner_actions/action_modifier.py index bdc4a2f3a..c7964edc9 100644 --- a/src/chat/planner_actions/action_modifier.py +++ b/src/chat/planner_actions/action_modifier.py @@ -2,7 +2,7 @@ import random import asyncio import hashlib import time -from typing import List, Any, Dict, TYPE_CHECKING +from typing import List, Any, Dict, TYPE_CHECKING, Tuple from src.common.logger import get_logger from src.config.config import global_config @@ -11,6 +11,7 @@ from src.chat.message_receive.chat_stream import get_chat_manager, ChatMessageCo from src.chat.planner_actions.action_manager import ActionManager from src.chat.utils.chat_message_builder import get_raw_msg_before_timestamp_with_chat, build_readable_messages from src.plugin_system.base.component_types import ActionInfo, ActionActivationType +from src.plugin_system.core.global_announcement_manager import global_announcement_manager if TYPE_CHECKING: from src.chat.message_receive.chat_stream import ChatStream @@ -60,8 +61,9 @@ class ActionModifier: """ logger.debug(f"{self.log_prefix}开始完整动作修改流程") - removals_s1 = [] - removals_s2 = [] + removals_s1: List[Tuple[str, str]] = [] + removals_s2: List[Tuple[str, str]] = [] + removals_s3: List[Tuple[str, str]] = [] self.action_manager.restore_actions() all_actions = self.action_manager.get_using_actions() @@ -83,25 +85,28 @@ class ActionModifier: if message_content: chat_content = chat_content + "\n" + f"现在,最新的消息是:{message_content}" - # === 第一阶段:传统观察处理 === - # if history_loop: - # removals_from_loop = await self.analyze_loop_actions(history_loop) - # if removals_from_loop: - # removals_s1.extend(removals_from_loop) + # === 第一阶段:去除用户自行禁用的 === + disabled_actions = global_announcement_manager.get_disabled_chat_actions(self.chat_id) + if disabled_actions: + for disabled_action_name in disabled_actions: + if disabled_action_name in all_actions: + removals_s1.append((disabled_action_name, "用户自行禁用")) + self.action_manager.remove_action_from_using(disabled_action_name) + logger.debug(f"{self.log_prefix}阶段一移除动作: {disabled_action_name},原因: 用户自行禁用") - # 检查动作的关联类型 + # === 第二阶段:检查动作的关联类型 === chat_context = self.chat_stream.context type_mismatched_actions = self._check_action_associated_types(all_actions, chat_context) if type_mismatched_actions: - removals_s1.extend(type_mismatched_actions) + removals_s2.extend(type_mismatched_actions) - # 应用第一阶段的移除 - for action_name, reason in removals_s1: + # 应用第二阶段的移除 + for action_name, reason in removals_s2: self.action_manager.remove_action_from_using(action_name) - logger.debug(f"{self.log_prefix}阶段一移除动作: {action_name},原因: {reason}") + logger.debug(f"{self.log_prefix}阶段二移除动作: {action_name},原因: {reason}") - # === 第二阶段:激活类型判定 === + # === 第三阶段:激活类型判定 === if chat_content is not None: logger.debug(f"{self.log_prefix}开始激活类型判定阶段") @@ -109,18 +114,18 @@ class ActionModifier: current_using_actions = self.action_manager.get_using_actions() # 获取因激活类型判定而需要移除的动作 - removals_s2 = await self._get_deactivated_actions_by_type( + removals_s3 = await self._get_deactivated_actions_by_type( current_using_actions, chat_content, ) - # 应用第二阶段的移除 - for action_name, reason in removals_s2: + # 应用第三阶段的移除 + for action_name, reason in removals_s3: self.action_manager.remove_action_from_using(action_name) - logger.debug(f"{self.log_prefix}阶段二移除动作: {action_name},原因: {reason}") + logger.debug(f"{self.log_prefix}阶段三移除动作: {action_name},原因: {reason}") # === 统一日志记录 === - all_removals = removals_s1 + removals_s2 + all_removals = removals_s1 + removals_s2 + removals_s3 removals_summary: str = "" if all_removals: removals_summary = " | ".join([f"{name}({reason})" for name, reason in all_removals]) @@ -130,7 +135,7 @@ class ActionModifier: ) def _check_action_associated_types(self, all_actions: Dict[str, ActionInfo], chat_context: ChatMessageContext): - type_mismatched_actions = [] + type_mismatched_actions: List[Tuple[str, str]] = [] for action_name, action_info in all_actions.items(): if action_info.associated_types and not chat_context.check_types(action_info.associated_types): associated_types_str = ", ".join(action_info.associated_types) diff --git a/src/chat/planner_actions/planner.py b/src/chat/planner_actions/planner.py index 09d0a5edc..dd0a3457c 100644 --- a/src/chat/planner_actions/planner.py +++ b/src/chat/planner_actions/planner.py @@ -1,7 +1,7 @@ import json import time import traceback -from typing import Dict, Any, Optional, Tuple +from typing import Dict, Any, Optional, Tuple, List from rich.traceback import install from datetime import datetime from json_repair import repair_json @@ -19,8 +19,8 @@ from src.chat.utils.chat_message_builder import ( from src.chat.utils.utils import get_chat_type_and_target_info from src.chat.planner_actions.action_manager import ActionManager from src.chat.message_receive.chat_stream import get_chat_manager -from src.plugin_system.base.component_types import ActionInfo, ChatMode - +from src.plugin_system.base.component_types import ActionInfo, ChatMode, ComponentType +from src.plugin_system.core.component_registry import component_registry logger = get_logger("planner") @@ -99,7 +99,7 @@ class ActionPlanner: async def plan( self, mode: ChatMode = ChatMode.FOCUS - ) -> Tuple[Dict[str, Dict[str, Any] | str], Optional[Dict[str, Any]]]: # sourcery skip: dict-comprehension + ) -> Tuple[Dict[str, Dict[str, Any] | str], Optional[Dict[str, Any]]]: """ 规划器 (Planner): 使用LLM根据上下文决定做出什么动作。 """ @@ -119,9 +119,11 @@ class ActionPlanner: current_available_actions_dict = self.action_manager.get_using_actions() # 获取完整的动作信息 - all_registered_actions = self.action_manager.get_registered_actions() - - for action_name in current_available_actions_dict.keys(): + all_registered_actions: List[ActionInfo] = list( + component_registry.get_components_by_type(ComponentType.ACTION).values() # type: ignore + ) + current_available_actions = {} + for action_name in current_available_actions_dict: if action_name in all_registered_actions: current_available_actions[action_name] = all_registered_actions[action_name] else: diff --git a/src/plugin_system/__init__.py b/src/plugin_system/__init__.py index 491da7c1c..72d8e3b37 100644 --- a/src/plugin_system/__init__.py +++ b/src/plugin_system/__init__.py @@ -28,6 +28,7 @@ from .core import ( component_registry, dependency_manager, events_manager, + global_announcement_manager, ) # 导入工具模块 @@ -67,6 +68,7 @@ __all__ = [ "component_registry", "dependency_manager", "events_manager", + "global_announcement_manager", # 装饰器 "register_plugin", "ConfigField", diff --git a/src/plugin_system/base/base_action.py b/src/plugin_system/base/base_action.py index a61a0339d..14e32f280 100644 --- a/src/plugin_system/base/base_action.py +++ b/src/plugin_system/base/base_action.py @@ -65,21 +65,28 @@ class BaseAction(ABC): self.thinking_id = thinking_id self.log_prefix = log_prefix - # 保存插件配置 self.plugin_config = plugin_config or {} + """对应的插件配置""" # 设置动作基本信息实例属性 self.action_name: str = getattr(self, "action_name", self.__class__.__name__.lower().replace("action", "")) + """Action的名字""" self.action_description: str = getattr(self, "action_description", self.__doc__ or "Action组件") + """Action的描述""" self.action_parameters: dict = getattr(self.__class__, "action_parameters", {}).copy() self.action_require: list[str] = getattr(self.__class__, "action_require", []).copy() # 设置激活类型实例属性(从类属性复制,提供默认值) self.focus_activation_type = getattr(self.__class__, "focus_activation_type", ActionActivationType.ALWAYS) + """FOCUS模式下的激活类型""" self.normal_activation_type = getattr(self.__class__, "normal_activation_type", ActionActivationType.ALWAYS) + """NORMAL模式下的激活类型""" self.random_activation_probability: float = getattr(self.__class__, "random_activation_probability", 0.0) + """当激活类型为RANDOM时的概率""" self.llm_judge_prompt: str = getattr(self.__class__, "llm_judge_prompt", "") + """协助LLM进行判断的Prompt""" self.activation_keywords: list[str] = getattr(self.__class__, "activation_keywords", []).copy() + """激活类型为KEYWORD时的KEYWORDS列表""" self.keyword_case_sensitive: bool = getattr(self.__class__, "keyword_case_sensitive", False) self.mode_enable: ChatMode = getattr(self.__class__, "mode_enable", ChatMode.ALL) self.parallel_action: bool = getattr(self.__class__, "parallel_action", True) diff --git a/src/plugin_system/base/base_command.py b/src/plugin_system/base/base_command.py index 5387e01dd..813b40529 100644 --- a/src/plugin_system/base/base_command.py +++ b/src/plugin_system/base/base_command.py @@ -21,13 +21,18 @@ class BaseCommand(ABC): """ command_name: str = "" + """Command组件的名称""" command_description: str = "" + """Command组件的描述""" # 默认命令设置(子类可以覆盖) command_pattern: str = "" + """命令匹配的正则表达式""" command_help: str = "" + """命令帮助信息""" command_examples: List[str] = [] - intercept_message: bool = True # 默认拦截消息,不继续处理 + intercept_message: bool = True + """是否拦截信息,默认拦截,不进行后续处理""" def __init__(self, message: MessageRecv, plugin_config: Optional[dict] = None): """初始化Command组件 diff --git a/src/plugin_system/base/base_events_handler.py b/src/plugin_system/base/base_events_handler.py index b6c9e965d..5118885ff 100644 --- a/src/plugin_system/base/base_events_handler.py +++ b/src/plugin_system/base/base_events_handler.py @@ -13,16 +13,23 @@ class BaseEventHandler(ABC): 所有事件处理器都应该继承这个基类,提供事件处理的基本接口 """ - event_type: EventType = EventType.UNKNOWN # 事件类型,默认为未知 - handler_name: str = "" # 处理器名称 + event_type: EventType = EventType.UNKNOWN + """事件类型,默认为未知""" + handler_name: str = "" + """处理器名称""" handler_description: str = "" - weight: int = 0 # 权重,数值越大优先级越高 - intercept_message: bool = False # 是否拦截消息,默认为否 + """处理器描述""" + weight: int = 0 + """处理器权重,越大权重越高""" + intercept_message: bool = False + """是否拦截消息,默认为否""" def __init__(self): self.log_prefix = "[EventHandler]" - self.plugin_name = "" # 对应插件名 - self.plugin_config: Optional[Dict] = None # 插件配置字典 + self.plugin_name = "" + """对应插件名""" + self.plugin_config: Optional[Dict] = None + """插件配置字典""" if self.event_type == EventType.UNKNOWN: raise NotImplementedError("事件处理器必须指定 event_type") diff --git a/src/plugin_system/core/__init__.py b/src/plugin_system/core/__init__.py index c6041ece7..3193828bf 100644 --- a/src/plugin_system/core/__init__.py +++ b/src/plugin_system/core/__init__.py @@ -8,10 +8,12 @@ from src.plugin_system.core.plugin_manager import plugin_manager from src.plugin_system.core.component_registry import component_registry from src.plugin_system.core.dependency_manager import dependency_manager from src.plugin_system.core.events_manager import events_manager +from src.plugin_system.core.global_announcement_manager import global_announcement_manager __all__ = [ "plugin_manager", "component_registry", "dependency_manager", "events_manager", + "global_announcement_manager", ] diff --git a/src/plugin_system/core/component_registry.py b/src/plugin_system/core/component_registry.py index e9509dd9b..0112962db 100644 --- a/src/plugin_system/core/component_registry.py +++ b/src/plugin_system/core/component_registry.py @@ -418,7 +418,7 @@ class ComponentRegistry: """获取Command模式注册表""" return self._command_patterns.copy() - def find_command_by_text(self, text: str) -> Optional[Tuple[Type[BaseCommand], dict, bool, str]]: + def find_command_by_text(self, text: str) -> Optional[Tuple[Type[BaseCommand], dict, CommandInfo]]: # sourcery skip: use-named-expression, use-next """根据文本查找匹配的命令 @@ -439,8 +439,7 @@ class ComponentRegistry: return ( self._command_registry[command_name], candidates[0].match(text).groupdict(), # type: ignore - command_info.intercept_message, - command_info.plugin_name, + command_info, ) # === 事件处理器特定查询方法 === diff --git a/src/plugin_system/core/events_manager.py b/src/plugin_system/core/events_manager.py index bcaef59e7..0182409cd 100644 --- a/src/plugin_system/core/events_manager.py +++ b/src/plugin_system/core/events_manager.py @@ -6,6 +6,7 @@ from src.chat.message_receive.message import MessageRecv from src.common.logger import get_logger from src.plugin_system.base.component_types import EventType, EventHandlerInfo, MaiMessages from src.plugin_system.base.base_events_handler import BaseEventHandler +from .global_announcement_manager import global_announcement_manager logger = get_logger("events_manager") @@ -53,6 +54,10 @@ class EventsManager: continue_flag = True transformed_message = self._transform_event_message(message, llm_prompt, llm_response) for handler in self._events_subscribers.get(event_type, []): + if message.chat_stream and message.chat_stream.stream_id: + stream_id = message.chat_stream.stream_id + if handler.handler_name in global_announcement_manager.get_disabled_chat_event_handlers(stream_id): + continue handler.set_plugin_config(component_registry.get_plugin_config(handler.plugin_name) or {}) if handler.intercept_message: try: diff --git a/src/plugin_system/core/global_announcement_manager.py b/src/plugin_system/core/global_announcement_manager.py new file mode 100644 index 000000000..afff34f11 --- /dev/null +++ b/src/plugin_system/core/global_announcement_manager.py @@ -0,0 +1,90 @@ +from typing import List, Dict + +from src.common.logger import get_logger + +logger = get_logger("global_announcement_manager") + + +class GlobalAnnouncementManager: + def __init__(self) -> None: + # 用户禁用的动作,chat_id -> [action_name] + self._user_disabled_actions: Dict[str, List[str]] = {} + # 用户禁用的命令,chat_id -> [command_name] + self._user_disabled_commands: Dict[str, List[str]] = {} + # 用户禁用的事件处理器,chat_id -> [handler_name] + self._user_disabled_event_handlers: Dict[str, List[str]] = {} + + def disable_specific_chat_action(self, chat_id: str, action_name: str) -> bool: + """禁用特定聊天的某个动作""" + if chat_id not in self._user_disabled_actions: + self._user_disabled_actions[chat_id] = [] + if action_name in self._user_disabled_actions[chat_id]: + logger.warning(f"动作 {action_name} 已经被禁用") + return False + self._user_disabled_actions[chat_id].append(action_name) + return True + + def enable_specific_chat_action(self, chat_id: str, action_name: str) -> bool: + """启用特定聊天的某个动作""" + if chat_id in self._user_disabled_actions: + try: + self._user_disabled_actions[chat_id].remove(action_name) + return True + except ValueError: + return False + return False + + def disable_specific_chat_command(self, chat_id: str, command_name: str) -> bool: + """禁用特定聊天的某个命令""" + if chat_id not in self._user_disabled_commands: + self._user_disabled_commands[chat_id] = [] + if command_name in self._user_disabled_commands[chat_id]: + logger.warning(f"命令 {command_name} 已经被禁用") + return False + self._user_disabled_commands[chat_id].append(command_name) + return True + + def enable_specific_chat_command(self, chat_id: str, command_name: str) -> bool: + """启用特定聊天的某个命令""" + if chat_id in self._user_disabled_commands: + try: + self._user_disabled_commands[chat_id].remove(command_name) + return True + except ValueError: + return False + return False + + def disable_specific_chat_event_handler(self, chat_id: str, handler_name: str) -> bool: + """禁用特定聊天的某个事件处理器""" + if chat_id not in self._user_disabled_event_handlers: + self._user_disabled_event_handlers[chat_id] = [] + if handler_name in self._user_disabled_event_handlers[chat_id]: + logger.warning(f"事件处理器 {handler_name} 已经被禁用") + return False + self._user_disabled_event_handlers[chat_id].append(handler_name) + return True + + def enable_specific_chat_event_handler(self, chat_id: str, handler_name: str) -> bool: + """启用特定聊天的某个事件处理器""" + if chat_id in self._user_disabled_event_handlers: + try: + self._user_disabled_event_handlers[chat_id].remove(handler_name) + return True + except ValueError: + return False + return False + + def get_disabled_chat_actions(self, chat_id: str) -> List[str]: + """获取特定聊天禁用的所有动作""" + return self._user_disabled_actions.get(chat_id, []).copy() + + def get_disabled_chat_commands(self, chat_id: str) -> List[str]: + """获取特定聊天禁用的所有命令""" + return self._user_disabled_commands.get(chat_id, []).copy() + + def get_disabled_chat_event_handlers(self, chat_id: str) -> List[str]: + """获取特定聊天禁用的所有事件处理器""" + return self._user_disabled_event_handlers.get(chat_id, []).copy() + + +global_announcement_manager = GlobalAnnouncementManager()