diff --git a/src/chat/emoji_system/emoji_manager.py b/src/chat/emoji_system/emoji_manager.py index 26440c1c9..56461d8b7 100644 --- a/src/chat/emoji_system/emoji_manager.py +++ b/src/chat/emoji_system/emoji_manager.py @@ -334,8 +334,6 @@ async def clean_unused_emojis(emoji_dir: str, emoji_objects: List["MaiEmoji"], r # 遍历指定目录中的所有文件 for file_name in os.listdir(emoji_dir): file_full_path = os.path.join(emoji_dir, file_name) - - # 确保处理的是文件而不是子目录 if not os.path.isfile(file_full_path): diff --git a/src/chat/focus_chat/heartFC_chat.py b/src/chat/focus_chat/heartFC_chat.py index ce4a43cba..d48ec465b 100644 --- a/src/chat/focus_chat/heartFC_chat.py +++ b/src/chat/focus_chat/heartFC_chat.py @@ -217,7 +217,7 @@ class HeartFChatting: async def start(self): """检查是否需要启动主循环,如果未激活则启动。""" logger.debug(f"{self.log_prefix} 开始启动 HeartFChatting") - + # 如果循环已经激活,直接返回 if self._loop_active: logger.debug(f"{self.log_prefix} HeartFChatting 已激活,无需重复启动") @@ -244,7 +244,7 @@ class HeartFChatting: self._loop_task = asyncio.create_task(self._run_focus_chat()) self._loop_task.add_done_callback(self._handle_loop_completion) logger.debug(f"{self.log_prefix} HeartFChatting 启动完成") - + except Exception as e: # 启动失败时重置状态 self._loop_active = False @@ -275,7 +275,7 @@ class HeartFChatting: try: while True: # 主循环 logger.debug(f"{self.log_prefix} 开始第{self._cycle_counter}次循环") - + # 检查关闭标志 if self._shutting_down: logger.info(f"{self.log_prefix} 检测到关闭标志,退出 Focus Chat 循环。") @@ -295,15 +295,17 @@ class HeartFChatting: async with self._get_cycle_context(): thinking_id = "tid" + str(round(time.time(), 2)) self._current_cycle_detail.set_thinking_id(thinking_id) - + # 使用异步上下文管理器处理消息 try: - async with global_prompt_manager.async_message_scope(self.chat_stream.context.get_template_name()): + async with global_prompt_manager.async_message_scope( + self.chat_stream.context.get_template_name() + ): # 在上下文内部检查关闭状态 if self._shutting_down: logger.info(f"{self.log_prefix} 在处理上下文中检测到关闭信号,退出") break - + logger.debug(f"模板 {self.chat_stream.context.get_template_name()}") loop_info = await self._observe_process_plan_action_loop(cycle_timers, thinking_id) @@ -318,7 +320,7 @@ class HeartFChatting: logger.error(f"{self.log_prefix} 调用停止专注聊天回调函数时出错: {e}") logger.error(traceback.format_exc()) break - + except asyncio.CancelledError: logger.info(f"{self.log_prefix} 处理上下文时任务被取消") break @@ -357,7 +359,9 @@ class HeartFChatting: timer_strings.append(f"{name}: {formatted_time}") # 新增:输出每个处理器的耗时 - processor_time_costs = self._current_cycle_detail.loop_processor_info.get("processor_time_costs", {}) + processor_time_costs = self._current_cycle_detail.loop_processor_info.get( + "processor_time_costs", {} + ) processor_time_strings = [] for pname, ptime in processor_time_costs.items(): formatted_ptime = f"{ptime * 1000:.2f}毫秒" if ptime < 1 else f"{ptime:.2f}秒" @@ -375,7 +379,7 @@ class HeartFChatting: ) await asyncio.sleep(global_config.focus_chat.think_interval) - + except asyncio.CancelledError: logger.info(f"{self.log_prefix} 循环处理时任务被取消") break diff --git a/src/chat/heart_flow/sub_heartflow.py b/src/chat/heart_flow/sub_heartflow.py index 661a4db96..b78fba3ec 100644 --- a/src/chat/heart_flow/sub_heartflow.py +++ b/src/chat/heart_flow/sub_heartflow.py @@ -179,12 +179,12 @@ class SubHeartflow: async def _start_heart_fc_chat(self) -> bool: """启动 HeartFChatting 实例,确保 NormalChat 已停止""" logger.debug(f"{self.log_prefix} 开始启动 HeartFChatting") - + try: # 确保普通聊天监控已停止 await self._stop_normal_chat() self.interest_dict.clear() - + log_prefix = self.log_prefix # 如果实例已存在,检查其循环任务状态 if self.heart_fc_instance: @@ -236,7 +236,7 @@ class SubHeartflow: logger.error(traceback.format_exc()) self.heart_fc_instance = None # 创建或初始化异常,清理实例 return False - + except Exception as e: logger.error(f"{self.log_prefix} _start_heart_fc_chat 执行时出错: {e}") logger.error(traceback.format_exc()) diff --git a/src/chat/normal_chat/normal_chat.py b/src/chat/normal_chat/normal_chat.py index 841927654..d7bd0b840 100644 --- a/src/chat/normal_chat/normal_chat.py +++ b/src/chat/normal_chat/normal_chat.py @@ -160,14 +160,14 @@ class NormalChat: 通常由start_monitoring_interest()启动 """ logger.debug(f"[{self.stream_name}] 兴趣监控任务开始") - + try: while True: # 第一层检查:立即检查取消和停用状态 if self._disabled: logger.info(f"[{self.stream_name}] 检测到停用标志,退出兴趣监控") break - + # 检查当前任务是否已被取消 current_task = asyncio.current_task() if current_task and current_task.cancelled(): @@ -177,7 +177,7 @@ class NormalChat: try: # 短暂等待,让出控制权 await asyncio.sleep(0.1) - + # 第二层检查:睡眠后再次检查状态 if self._disabled: logger.info(f"[{self.stream_name}] 睡眠后检测到停用标志,退出") @@ -196,7 +196,9 @@ class NormalChat: # 使用异步上下文管理器处理消息 try: - async with global_prompt_manager.async_message_scope(self.chat_stream.context.get_template_name()): + async with global_prompt_manager.async_message_scope( + self.chat_stream.context.get_template_name() + ): # 在上下文内部再次检查取消状态 if self._disabled: logger.info(f"[{self.stream_name}] 在处理上下文中检测到停止信号,退出") @@ -249,7 +251,7 @@ class NormalChat: limited_tasks = [limited_process(task, semaphore) for task in tasks] await asyncio.gather(*limited_tasks, return_exceptions=True) - + except asyncio.CancelledError: logger.info(f"[{self.stream_name}] 处理上下文时任务被取消") break @@ -531,31 +533,31 @@ class NormalChat: async def start_chat(self): """启动聊天任务。""" logger.debug(f"[{self.stream_name}] 开始启动聊天任务") - + # 重置停用标志 self._disabled = False - + # 检查是否已有运行中的任务 if self._chat_task and not self._chat_task.done(): logger.info(f"[{self.stream_name}] 聊天轮询任务已在运行中。") return - + # 清理可能存在的已完成任务引用 if self._chat_task and self._chat_task.done(): self._chat_task = None - + try: logger.debug(f"[{self.stream_name}] 创建新的聊天轮询任务") polling_task = asyncio.create_task(self._reply_interested_message()) - + # 设置回调 polling_task.add_done_callback(lambda t: self._handle_task_completion(t)) - + # 保存任务引用 self._chat_task = polling_task - + logger.debug(f"[{self.stream_name}] 聊天任务启动完成") - + except Exception as e: logger.error(f"[{self.stream_name}] 启动聊天任务失败: {e}") self._chat_task = None @@ -566,17 +568,17 @@ class NormalChat: try: # 简化回调逻辑,避免复杂的异常处理 logger.debug(f"[{self.stream_name}] 任务完成回调被调用") - + # 检查是否是我们管理的任务 if task is not self._chat_task: # 如果已经不是当前任务(可能在stop_chat中已被清空),直接返回 logger.debug(f"[{self.stream_name}] 回调的任务不是当前管理的任务") return - + # 清理任务引用 self._chat_task = None logger.debug(f"[{self.stream_name}] 任务引用已清理") - + # 简单记录任务状态,不进行复杂处理 if task.cancelled(): logger.debug(f"[{self.stream_name}] 任务已取消") @@ -591,7 +593,7 @@ class NormalChat: except Exception as e: # 获取异常时也可能出错,静默处理 logger.debug(f"[{self.stream_name}] 获取任务异常时出错: {e}") - + except Exception as e: # 回调函数中的任何异常都要捕获,避免影响系统 logger.error(f"[{self.stream_name}] 任务完成回调处理出错: {e}") @@ -602,31 +604,31 @@ class NormalChat: async def stop_chat(self): """停止当前实例的兴趣监控任务。""" logger.debug(f"[{self.stream_name}] 开始停止聊天任务") - + # 立即设置停用标志,防止新任务启动 self._disabled = True - + # 如果没有运行中的任务,直接返回 if not self._chat_task or self._chat_task.done(): logger.debug(f"[{self.stream_name}] 没有运行中的任务,直接完成停止") self._chat_task = None return - + # 保存任务引用并立即清空,避免回调中的循环引用 task_to_cancel = self._chat_task self._chat_task = None - + logger.debug(f"[{self.stream_name}] 取消聊天任务") - + # 尝试优雅取消任务 task_to_cancel.cancel() - + # 不等待任务完成,让它自然结束 # 这样可以避免等待过程中的潜在递归问题 - + # 异步清理思考消息,不阻塞当前流程 asyncio.create_task(self._cleanup_thinking_messages_async()) - + logger.debug(f"[{self.stream_name}] 聊天任务停止完成") async def _cleanup_thinking_messages_async(self): @@ -634,7 +636,7 @@ class NormalChat: try: # 添加短暂延迟,让任务有时间响应取消 await asyncio.sleep(0.1) - + container = await message_manager.get_container(self.stream_id) if container: # 查找并移除所有 MessageThinking 类型的消息 diff --git a/src/common/logger.py b/src/common/logger.py index 7b4a145ed..4c34ff15d 100644 --- a/src/common/logger.py +++ b/src/common/logger.py @@ -490,18 +490,19 @@ def _immediate_setup(): # 立即执行配置 _immediate_setup() -raw_logger = structlog.get_logger() +raw_logger: structlog.stdlib.BoundLogger = structlog.get_logger() binds: dict[str, Callable] = {} -def get_logger(name: Optional[str]): +def get_logger(name: Optional[str]) -> structlog.stdlib.BoundLogger: """获取logger实例,支持按名称绑定""" if name is None: return raw_logger logger = binds.get(name) if logger is None: - binds[name] = logger = structlog.get_logger(name).bind(logger_name=name) + logger: structlog.stdlib.BoundLogger = structlog.get_logger(name).bind(logger_name=name) + binds[name] = logger return logger diff --git a/src/plugin_system/core/component_registry.py b/src/plugin_system/core/component_registry.py index 863146a86..fba04e8d5 100644 --- a/src/plugin_system/core/component_registry.py +++ b/src/plugin_system/core/component_registry.py @@ -252,7 +252,16 @@ class ComponentRegistry: def get_registry_stats(self) -> Dict[str, Any]: """获取注册中心统计信息""" + action_components: int = 0 + command_components: int = 0 + for component in self._components.values(): + if component.component_type == ComponentType.ACTION: + action_components += 1 + elif component.component_type == ComponentType.COMMAND: + command_components += 1 return { + "action_components": action_components, + "command_components": command_components, "total_components": len(self._components), "total_plugins": len(self._plugins), "components_by_type": { diff --git a/src/plugin_system/core/plugin_manager.py b/src/plugin_system/core/plugin_manager.py index de2305234..2304a838f 100644 --- a/src/plugin_system/core/plugin_manager.py +++ b/src/plugin_system/core/plugin_manager.py @@ -38,14 +38,20 @@ class PluginManager: if not os.path.exists(directory): os.makedirs(directory, exist_ok=True) logger.info(f"创建插件目录: {directory}") - self.plugin_directories.append(directory) - logger.debug(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): """添加插件目录""" if os.path.exists(directory): - self.plugin_directories.append(directory) - logger.debug(f"已添加插件目录: {directory}") + if directory not in self.plugin_directories: + self.plugin_directories.append(directory) + logger.debug(f"已添加插件目录: {directory}") + else: + logger.warning(f"插件不可重复加载: {directory}") else: logger.warning(f"插件目录不存在: {directory}") @@ -130,7 +136,7 @@ class PluginManager: if plugin_info: # 插件基本信息 version_info = f"v{plugin_info.version}" if plugin_info.version else "" - author_info = f"by {plugin_info.author}" if plugin_info.author else "" + author_info = f"by {plugin_info.author}" if plugin_info.author else "unknown" info_parts = [part for part in [version_info, author_info] if part] extra_info = f" ({', '.join(info_parts)})" if info_parts else "" @@ -342,7 +348,9 @@ class PluginManager: # 全局插件管理器实例 plugin_manager = PluginManager() +# 注释掉以解决插件目录重复加载的情况 # 默认插件目录 -plugin_manager.add_plugin_directory("src/plugins/built_in") -plugin_manager.add_plugin_directory("src/plugins/examples") -plugin_manager.add_plugin_directory("plugins") # 用户插件目录 +# plugin_manager.add_plugin_directory("src/plugins/built_in") +# plugin_manager.add_plugin_directory("src/plugins/examples") +# 用户插件目录 +# plugin_manager.add_plugin_directory("plugins") diff --git a/test_normal_chat_stop.py b/test_normal_chat_stop.py index 743353441..1a08565ae 100644 --- a/test_normal_chat_stop.py +++ b/test_normal_chat_stop.py @@ -4,21 +4,21 @@ NormalChat 启动停止测试脚本 """ import asyncio -import time import logging from src.common.logger import get_logger logger = get_logger("test_normal_chat_stop") + async def test_task_cancel_behavior(): """测试任务取消行为""" - + class MockNormalChat: def __init__(self): self._disabled = False self._chat_task = None self.stream_name = "test_stream" - + async def mock_reply_loop(self): """模拟回复循环""" logger.info("模拟回复循环开始") @@ -28,11 +28,11 @@ async def test_task_cancel_behavior(): if self._disabled: logger.info("检测到停用标志,退出循环") break - + # 模拟工作 logger.info("模拟处理消息...") await asyncio.sleep(0.1) - + except asyncio.CancelledError: logger.info("模拟回复循环被取消") raise @@ -40,65 +40,68 @@ async def test_task_cancel_behavior(): logger.error(f"模拟回复循环出错: {e}") finally: logger.info("模拟回复循环结束") - + async def start_chat(self): """启动聊天""" if self._chat_task and not self._chat_task.done(): logger.info("任务已在运行") return - + self._disabled = False self._chat_task = asyncio.create_task(self.mock_reply_loop()) logger.info("聊天任务已启动") - + async def stop_chat(self): """停止聊天""" logger.info("开始停止聊天") - + # 设置停用标志 self._disabled = True - + if not self._chat_task or self._chat_task.done(): logger.info("没有运行中的任务") return - + # 保存任务引用并清空 task_to_cancel = self._chat_task self._chat_task = None - + # 取消任务 task_to_cancel.cancel() - + logger.info("聊天任务停止完成") - + # 测试正常启动停止 logger.info("=== 测试正常启动停止 ===") chat = MockNormalChat() - + # 启动 await chat.start_chat() await asyncio.sleep(0.5) # 让任务运行一会 - + # 停止 await chat.stop_chat() await asyncio.sleep(0.1) # 让取消操作完成 - + logger.info("=== 测试完成 ===") + async def main(): """主函数""" logger.info("开始 NormalChat 停止测试") - + try: await test_task_cancel_behavior() except Exception as e: logger.error(f"测试失败: {e}") import traceback + logger.error(traceback.format_exc()) - + logger.info("测试结束") + if __name__ == "__main__": # 设置日志级别 logging.basicConfig(level=logging.INFO) - asyncio.run(main()) \ No newline at end of file + asyncio.run(main())