diff --git a/docs/plugins/action-components.md b/docs/plugins/action-components.md index 3bdbdf0b6..192b60ba2 100644 --- a/docs/plugins/action-components.md +++ b/docs/plugins/action-components.md @@ -125,6 +125,39 @@ action_require = [ associated_types = ["text", "emoji", "image"] ``` +### 4. 动作记录必须项 + +每个 Action 在执行完成后,**必须**使用 `store_action_info` 记录动作信息。这是非常重要的,因为: + +1. **记忆连续性**:让麦麦记住自己执行过的动作,避免重复执行 +2. **上下文理解**:帮助麦麦理解自己的行为历史,做出更合理的决策 +3. **行为追踪**:便于后续查询和分析麦麦的行为模式 + +```python +async def execute(self) -> Tuple[bool, Optional[str]]: + # ... 执行动作的代码 ... + + if success: + # 存储动作信息 + await self.api.store_action_info( + action_build_into_prompt=True, # 让麦麦知道这个动作 + action_prompt_display=f"执行了xxx动作,参数:{param}", # 动作描述 + action_done=True, # 动作是否完成 + thinking_id=self.thinking_id, # 关联的思考ID + action_data={ # 动作的详细数据 + "param1": value1, + "param2": value2 + } + ) + return True, "动作执行成功" +``` + +> ⚠️ **重要提示**:如果不记录动作信息,可能会导致以下问题: +> - 麦麦不知道自己执行过什么动作,可能会重复执行 +> - 无法追踪动作历史,影响后续决策 +> - 在长对话中失去上下文连续性 +> - 无法进行行为分析和优化 + ## 🔧 激活类型详解 ### KEYWORD激活 diff --git a/scripts/log_viewer_optimized.py b/scripts/log_viewer_optimized.py new file mode 100644 index 000000000..115b49fab --- /dev/null +++ b/scripts/log_viewer_optimized.py @@ -0,0 +1,686 @@ +import tkinter as tk +from tkinter import ttk, colorchooser, messagebox, filedialog +import json +from pathlib import Path +import threading +import queue +import time +import toml +from datetime import datetime +import bisect +from collections import defaultdict +import os + + +class LogIndex: + """日志索引,用于快速检索和过滤""" + + def __init__(self): + self.entries = [] # 所有日志条目 + self.module_index = defaultdict(list) # 按模块索引 + self.level_index = defaultdict(list) # 按级别索引 + self.filtered_indices = [] # 当前过滤结果的索引 + self.total_entries = 0 + + def add_entry(self, index, entry): + """添加日志条目到索引""" + if index >= len(self.entries): + self.entries.extend([None] * (index - len(self.entries) + 1)) + + self.entries[index] = entry + self.total_entries = max(self.total_entries, index + 1) + + # 更新各种索引 + logger_name = entry.get("logger_name", "") + level = entry.get("level", "") + + self.module_index[logger_name].append(index) + self.level_index[level].append(index) + + def filter_entries(self, modules=None, level=None, search_text=None): + """根据条件过滤日志条目""" + if not modules and not level and not search_text: + self.filtered_indices = list(range(self.total_entries)) + return self.filtered_indices + + candidate_indices = set(range(self.total_entries)) + + # 模块过滤 + if modules and "全部" not in modules: + module_indices = set() + for module in modules: + module_indices.update(self.module_index.get(module, [])) + candidate_indices &= module_indices + + # 级别过滤 + if level and level != "全部": + level_indices = set(self.level_index.get(level, [])) + candidate_indices &= level_indices + + # 文本搜索过滤 + if search_text: + search_text = search_text.lower() + text_indices = set() + for i in candidate_indices: + if i < len(self.entries) and self.entries[i]: + entry = self.entries[i] + text_content = f"{entry.get('logger_name', '')} {entry.get('event', '')}".lower() + if search_text in text_content: + text_indices.add(i) + candidate_indices &= text_indices + + self.filtered_indices = sorted(list(candidate_indices)) + return self.filtered_indices + + def get_filtered_count(self): + """获取过滤后的条目数量""" + return len(self.filtered_indices) + + def get_entry_at_filtered_position(self, position): + """获取过滤结果中指定位置的条目""" + if 0 <= position < len(self.filtered_indices): + index = self.filtered_indices[position] + return self.entries[index] if index < len(self.entries) else None + return None + + +class LogFormatter: + """日志格式化器""" + + def __init__(self, config, custom_module_colors=None, custom_level_colors=None): + self.config = config + + # 日志级别颜色 + self.level_colors = { + "debug": "#FFA500", + "info": "#0000FF", + "success": "#008000", + "warning": "#FFFF00", + "error": "#FF0000", + "critical": "#800080", + } + + # 模块颜色映射 + self.module_colors = { + "api": "#00FF00", + "emoji": "#00FF00", + "chat": "#0080FF", + "config": "#FFFF00", + "common": "#FF00FF", + "tools": "#00FFFF", + "lpmm": "#00FFFF", + "plugin_system": "#FF0080", + "experimental": "#FFFFFF", + "person_info": "#008000", + "individuality": "#000080", + "manager": "#800080", + "llm_models": "#008080", + "plugins": "#800000", + "plugin_api": "#808000", + "remote": "#8000FF", + } + + # 应用自定义颜色 + if custom_module_colors: + self.module_colors.update(custom_module_colors) + if custom_level_colors: + self.level_colors.update(custom_level_colors) + + # 根据配置决定颜色启用状态 + color_text = self.config.get("color_text", "full") + if color_text == "none": + self.enable_colors = False + self.enable_module_colors = False + self.enable_level_colors = False + elif color_text == "title": + self.enable_colors = True + self.enable_module_colors = True + self.enable_level_colors = False + elif color_text == "full": + self.enable_colors = True + self.enable_module_colors = True + self.enable_level_colors = True + else: + self.enable_colors = True + self.enable_module_colors = True + self.enable_level_colors = False + + def format_log_entry(self, log_entry): + """格式化日志条目,返回格式化后的文本和样式标签""" + timestamp = log_entry.get("timestamp", "") + level = log_entry.get("level", "info") + logger_name = log_entry.get("logger_name", "") + event = log_entry.get("event", "") + + # 格式化时间戳 + formatted_timestamp = self.format_timestamp(timestamp) + + # 构建输出部分 + parts = [] + tags = [] + + # 日志级别样式配置 + log_level_style = self.config.get("log_level_style", "lite") + + # 时间戳 + if formatted_timestamp: + if log_level_style == "lite" and self.enable_level_colors: + parts.append(formatted_timestamp) + tags.append(f"level_{level}") + else: + parts.append(formatted_timestamp) + tags.append("timestamp") + + # 日志级别显示 + if log_level_style == "full": + level_text = f"[{level.upper():>8}]" + parts.append(level_text) + if self.enable_level_colors: + tags.append(f"level_{level}") + else: + tags.append("level") + elif log_level_style == "compact": + level_text = f"[{level.upper()[0]:>8}]" + parts.append(level_text) + if self.enable_level_colors: + tags.append(f"level_{level}") + else: + tags.append("level") + + # 模块名称 + if logger_name: + module_text = f"[{logger_name}]" + parts.append(module_text) + if self.enable_module_colors: + tags.append(f"module_{logger_name}") + else: + tags.append("module") + + # 消息内容 + if isinstance(event, str): + parts.append(event) + elif isinstance(event, dict): + try: + parts.append(json.dumps(event, ensure_ascii=False, indent=None)) + except (TypeError, ValueError): + parts.append(str(event)) + else: + parts.append(str(event)) + tags.append("message") + + return parts, tags + + def format_timestamp(self, timestamp): + """格式化时间戳""" + if not timestamp: + return "" + + try: + if "T" in timestamp: + dt = datetime.fromisoformat(timestamp.replace("Z", "+00:00")) + else: + return timestamp + + date_style = self.config.get("date_style", "m-d H:i:s") + format_map = { + "Y": "%Y", + "m": "%m", + "d": "%d", + "H": "%H", + "i": "%M", + "s": "%S", + } + + python_format = date_style + for php_char, python_char in format_map.items(): + python_format = python_format.replace(php_char, python_char) + + return dt.strftime(python_format) + except Exception: + return timestamp + + +class VirtualLogDisplay: + """虚拟滚动日志显示组件""" + + def __init__(self, parent, formatter): + self.parent = parent + self.formatter = formatter + self.line_height = 20 # 每行高度(像素) + self.visible_lines = 30 # 可见行数 + + # 创建主框架 + self.main_frame = ttk.Frame(parent) + + # 创建文本框和滚动条 + self.scrollbar = ttk.Scrollbar(self.main_frame) + self.scrollbar.pack(side=tk.RIGHT, fill=tk.Y) + + self.text_widget = tk.Text( + self.main_frame, + wrap=tk.WORD, + yscrollcommand=self.scrollbar.set, + background="#1e1e1e", + foreground="#ffffff", + insertbackground="#ffffff", + selectbackground="#404040", + font=("Consolas", 10) + ) + self.text_widget.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) + self.scrollbar.config(command=self.text_widget.yview) + + # 配置文本标签样式 + self.configure_text_tags() + + # 数据源 + self.log_index = None + self.current_page = 0 + self.page_size = 500 # 每页显示条数 + self.max_display_lines = 2000 # 最大显示行数 + + def pack(self, **kwargs): + """包装pack方法""" + self.main_frame.pack(**kwargs) + + def configure_text_tags(self): + """配置文本标签样式""" + # 基础标签 + self.text_widget.tag_configure("timestamp", foreground="#808080") + self.text_widget.tag_configure("level", foreground="#808080") + self.text_widget.tag_configure("module", foreground="#808080") + self.text_widget.tag_configure("message", foreground="#ffffff") + + # 日志级别颜色标签 + for level, color in self.formatter.level_colors.items(): + self.text_widget.tag_configure(f"level_{level}", foreground=color) + + # 模块颜色标签 + for module, color in self.formatter.module_colors.items(): + self.text_widget.tag_configure(f"module_{module}", foreground=color) + + def set_log_index(self, log_index): + """设置日志索引数据源""" + self.log_index = log_index + self.current_page = 0 + self.refresh_display() + + def refresh_display(self): + """刷新显示""" + if not self.log_index: + self.text_widget.delete(1.0, tk.END) + return + + # 清空显示 + self.text_widget.delete(1.0, tk.END) + + # 批量加载和显示日志 + total_count = self.log_index.get_filtered_count() + if total_count == 0: + self.text_widget.insert(tk.END, "没有符合条件的日志记录\n") + return + + # 计算显示范围 + start_index = 0 + end_index = min(total_count, self.max_display_lines) + + # 批量处理和显示 + batch_size = 100 + for batch_start in range(start_index, end_index, batch_size): + batch_end = min(batch_start + batch_size, end_index) + self.display_batch(batch_start, batch_end) + + # 让UI有机会响应 + self.parent.update_idletasks() + + # 滚动到底部(如果需要) + self.text_widget.see(tk.END) + + def display_batch(self, start_index, end_index): + """批量显示日志条目""" + batch_text = [] + batch_tags = [] + + for i in range(start_index, end_index): + log_entry = self.log_index.get_entry_at_filtered_position(i) + if log_entry: + parts, tags = self.formatter.format_log_entry(log_entry) + + # 合并部分为单行文本 + line_text = " ".join(parts) + "\n" + batch_text.append(line_text) + + # 记录标签信息(简化处理) + if tags and self.formatter.enable_level_colors: + level = log_entry.get("level", "info") + batch_tags.append(("line", len("".join(batch_text)) - len(line_text), + len("".join(batch_text)) - 1, f"level_{level}")) + + # 一次性插入所有文本 + if batch_text: + start_pos = self.text_widget.index(tk.END) + all_text = "".join(batch_text) + self.text_widget.insert(tk.END, all_text) + + # 应用标签(可选,为了性能可以考虑简化) + for tag_info in batch_tags: + try: + tag_name = tag_info[3] + self.text_widget.tag_add(tag_name, f"{start_pos}+{tag_info[1]}c", f"{start_pos}+{tag_info[2]}c") + except: + pass + + +class AsyncLogLoader: + """异步日志加载器""" + + def __init__(self, callback): + self.callback = callback + self.loading = False + self.should_stop = False + + def load_file_async(self, file_path, progress_callback=None): + """异步加载日志文件""" + if self.loading: + return + + self.loading = True + self.should_stop = False + + def load_worker(): + try: + log_index = LogIndex() + + if not os.path.exists(file_path): + self.callback(log_index, "文件不存在") + return + + file_size = os.path.getsize(file_path) + processed_size = 0 + + with open(file_path, "r", encoding="utf-8") as f: + line_count = 0 + batch_size = 1000 # 批量处理 + + while not self.should_stop: + lines = [] + for _ in range(batch_size): + line = f.readline() + if not line: + break + lines.append(line) + processed_size += len(line.encode('utf-8')) + + if not lines: + break + + # 处理这批数据 + for line in lines: + try: + log_entry = json.loads(line.strip()) + log_index.add_entry(line_count, log_entry) + line_count += 1 + except json.JSONDecodeError: + continue + + # 更新进度 + if progress_callback: + progress = min(100, (processed_size / file_size) * 100) + progress_callback(progress, line_count) + + if not self.should_stop: + self.callback(log_index, None) + + except Exception as e: + self.callback(None, str(e)) + finally: + self.loading = False + + thread = threading.Thread(target=load_worker) + thread.daemon = True + thread.start() + + def stop_loading(self): + """停止加载""" + self.should_stop = True + self.loading = False + + +class LogViewer: + def __init__(self, root): + self.root = root + self.root.title("MaiBot日志查看器 (优化版)") + self.root.geometry("1200x800") + + # 加载配置 + self.load_config() + + # 初始化日志格式化器 + self.formatter = LogFormatter(self.log_config, {}, {}) + + # 初始化日志文件路径 + self.current_log_file = Path("logs/app.log.jsonl") + + # 初始化异步加载器 + self.async_loader = AsyncLogLoader(self.on_file_loaded) + + # 初始化日志索引 + self.log_index = LogIndex() + + # 创建主框架 + self.main_frame = ttk.Frame(root) + self.main_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) + + # 创建控制面板 + self.create_control_panel() + + # 创建虚拟滚动日志显示区域 + self.log_display = VirtualLogDisplay(self.main_frame, self.formatter) + self.log_display.pack(fill=tk.BOTH, expand=True) + + # 模块名映射 + self.module_name_mapping = { + "api": "API接口", + "config": "配置", + "chat": "聊天", + "plugin": "插件", + "main": "主程序", + } + + # 选中的模块集合 + self.selected_modules = set() + self.modules = set() + + # 绑定事件 + self.level_combo.bind("<>", self.filter_logs) + self.search_var.trace("w", self.filter_logs) + + # 初始加载文件 + if self.current_log_file.exists(): + self.load_log_file_async() + + def load_config(self): + """加载配置文件""" + self.default_config = { + "log": {"date_style": "m-d H:i:s", "log_level_style": "lite", "color_text": "full"}, + } + + self.log_config = self.default_config["log"].copy() + + config_path = Path("config/bot_config.toml") + try: + if config_path.exists(): + with open(config_path, "r", encoding="utf-8") as f: + bot_config = toml.load(f) + if "log" in bot_config: + self.log_config.update(bot_config["log"]) + except Exception as e: + print(f"加载配置失败: {e}") + + def create_control_panel(self): + """创建控制面板""" + # 控制面板 + self.control_frame = ttk.Frame(self.main_frame) + self.control_frame.pack(fill=tk.X, pady=(0, 5)) + + # 文件选择框架 + self.file_frame = ttk.LabelFrame(self.control_frame, text="日志文件") + self.file_frame.pack(side=tk.TOP, fill=tk.X, padx=5, pady=(0, 5)) + + # 当前文件显示 + self.current_file_var = tk.StringVar(value=str(self.current_log_file)) + self.file_label = ttk.Label(self.file_frame, textvariable=self.current_file_var, foreground="blue") + self.file_label.pack(side=tk.LEFT, padx=5, pady=2) + + # 进度条 + self.progress_var = tk.DoubleVar() + self.progress_bar = ttk.Progressbar(self.file_frame, variable=self.progress_var, length=200) + self.progress_bar.pack(side=tk.LEFT, padx=5, pady=2) + self.progress_bar.pack_forget() + + # 状态标签 + self.status_var = tk.StringVar(value="就绪") + self.status_label = ttk.Label(self.file_frame, textvariable=self.status_var) + self.status_label.pack(side=tk.LEFT, padx=5, pady=2) + + # 按钮区域 + button_frame = ttk.Frame(self.file_frame) + button_frame.pack(side=tk.RIGHT, padx=5, pady=2) + + ttk.Button(button_frame, text="选择文件", command=self.select_log_file).pack(side=tk.LEFT, padx=2) + ttk.Button(button_frame, text="刷新", command=self.refresh_log_file).pack(side=tk.LEFT, padx=2) + + # 过滤控制框架 + filter_frame = ttk.Frame(self.control_frame) + filter_frame.pack(fill=tk.X, padx=5) + + # 日志级别选择 + ttk.Label(filter_frame, text="级别:").pack(side=tk.LEFT, padx=2) + self.level_var = tk.StringVar(value="全部") + self.level_combo = ttk.Combobox(filter_frame, textvariable=self.level_var, width=8) + self.level_combo["values"] = ["全部", "debug", "info", "warning", "error", "critical"] + self.level_combo.pack(side=tk.LEFT, padx=2) + + # 搜索框 + ttk.Label(filter_frame, text="搜索:").pack(side=tk.LEFT, padx=(20, 2)) + self.search_var = tk.StringVar() + self.search_entry = ttk.Entry(filter_frame, textvariable=self.search_var, width=20) + self.search_entry.pack(side=tk.LEFT, padx=2) + + # 模块选择 + ttk.Label(filter_frame, text="模块:").pack(side=tk.LEFT, padx=(20, 2)) + self.module_var = tk.StringVar(value="全部") + self.module_combo = ttk.Combobox(filter_frame, textvariable=self.module_var, width=15) + self.module_combo.pack(side=tk.LEFT, padx=2) + self.module_combo.bind("<>", self.on_module_selected) + + def on_file_loaded(self, log_index, error): + """文件加载完成回调""" + self.progress_bar.pack_forget() + + if error: + self.status_var.set(f"加载失败: {error}") + messagebox.showerror("错误", f"加载日志文件失败: {error}") + return + + self.log_index = log_index + self.status_var.set(f"已加载 {log_index.total_entries} 条日志") + + # 更新模块列表 + self.modules = set(log_index.module_index.keys()) + module_values = ["全部"] + sorted(list(self.modules)) + self.module_combo["values"] = module_values + + # 应用过滤并显示 + self.filter_logs() + + def on_loading_progress(self, progress, line_count): + """加载进度回调""" + self.root.after(0, lambda: self.update_progress(progress, line_count)) + + def update_progress(self, progress, line_count): + """更新进度显示""" + self.progress_var.set(progress) + self.status_var.set(f"正在加载... {line_count} 条 ({progress:.1f}%)") + + def load_log_file_async(self): + """异步加载日志文件""" + if not self.current_log_file.exists(): + self.status_var.set("文件不存在") + return + + # 显示进度条 + self.progress_bar.pack(side=tk.LEFT, padx=5, pady=2, before=self.status_label) + self.progress_var.set(0) + self.status_var.set("正在加载...") + + # 清空当前数据 + self.log_index = LogIndex() + self.modules.clear() + self.selected_modules.clear() + + # 开始异步加载 + self.async_loader.load_file_async( + str(self.current_log_file), + self.on_loading_progress + ) + + def on_module_selected(self, event=None): + """模块选择事件""" + module = self.module_var.get() + if module == "全部": + self.selected_modules = {"全部"} + else: + self.selected_modules = {module} + self.filter_logs() + + def filter_logs(self, *args): + """过滤日志""" + if not self.log_index: + return + + # 获取过滤条件 + selected_modules = self.selected_modules if self.selected_modules else None + level = self.level_var.get() if self.level_var.get() != "全部" else None + search_text = self.search_var.get().strip() if self.search_var.get().strip() else None + + # 应用过滤 + self.log_index.filter_entries(selected_modules, level, search_text) + + # 更新显示 + self.log_display.set_log_index(self.log_index) + + # 更新状态 + filtered_count = self.log_index.get_filtered_count() + total_count = self.log_index.total_entries + if filtered_count == total_count: + self.status_var.set(f"显示 {total_count} 条日志") + else: + self.status_var.set(f"显示 {filtered_count}/{total_count} 条日志") + + def select_log_file(self): + """选择日志文件""" + filename = filedialog.askopenfilename( + title="选择日志文件", + filetypes=[("JSONL日志文件", "*.jsonl"), ("所有文件", "*.*")], + initialdir="logs" if Path("logs").exists() else ".", + ) + if filename: + new_file = Path(filename) + if new_file != self.current_log_file: + self.current_log_file = new_file + self.current_file_var.set(str(self.current_log_file)) + self.load_log_file_async() + + def refresh_log_file(self): + """刷新日志文件""" + self.load_log_file_async() + + +def main(): + root = tk.Tk() + LogViewer(root) + root.mainloop() + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/src/chat/normal_chat/normal_chat_generator.py b/src/chat/normal_chat/normal_chat_generator.py index b68cfabb1..6a3c8cc52 100644 --- a/src/chat/normal_chat/normal_chat_generator.py +++ b/src/chat/normal_chat/normal_chat_generator.py @@ -96,7 +96,7 @@ class NormalChatGenerator: try: content, (reasoning_content, model_name) = await model.generate_response_async(prompt) - logger.debug(f"prompt:{prompt}\n生成回复:{content}") + logger.info(f"prompt:{prompt}\n生成回复:{content}") logger.info(f"对 {message.processed_plain_text} 的回复:{content}") diff --git a/src/chat/normal_chat/normal_chat_planner.py b/src/chat/normal_chat/normal_chat_planner.py index f1610981d..810df2dd9 100644 --- a/src/chat/normal_chat/normal_chat_planner.py +++ b/src/chat/normal_chat/normal_chat_planner.py @@ -158,10 +158,10 @@ class NormalChatPlanner: try: content, (reasoning_content, model_name) = await self.planner_llm.generate_response_async(prompt) - logger.debug(f"{self.log_prefix}规划器原始提示词: {prompt}") - logger.debug(f"{self.log_prefix}规划器原始响应: {content}") - logger.debug(f"{self.log_prefix}规划器推理: {reasoning_content}") - logger.debug(f"{self.log_prefix}规划器模型: {model_name}") + logger.info(f"{self.log_prefix}规划器原始提示词: {prompt}") + logger.info(f"{self.log_prefix}规划器原始响应: {content}") + logger.info(f"{self.log_prefix}规划器推理: {reasoning_content}") + logger.info(f"{self.log_prefix}规划器模型: {model_name}") # 解析JSON响应 try: @@ -263,7 +263,7 @@ class NormalChatPlanner: if action_parameters: param_text = "\n" - print(action_parameters) + # print(action_parameters) for param_name, param_description in action_parameters.items(): param_text += f' "{param_name}":"{param_description}"\n' param_text = param_text.rstrip("\n") diff --git a/src/chat/utils/chat_message_builder.py b/src/chat/utils/chat_message_builder.py index 4f1348639..715730a20 100644 --- a/src/chat/utils/chat_message_builder.py +++ b/src/chat/utils/chat_message_builder.py @@ -357,7 +357,7 @@ def _build_readable_messages_internal( limit = 200 replace_content = "......(内容太长了)" elif percentile < 1.0: # 80% 到 100% 之前的消息 (即较新的 20%) - limit = 300 + limit = 400 replace_content = "......(太长了)" truncated_content = content diff --git a/src/chat/utils/utils.py b/src/chat/utils/utils.py index 00c23b8d0..592964167 100644 --- a/src/chat/utils/utils.py +++ b/src/chat/utils/utils.py @@ -633,7 +633,7 @@ def translate_timestamp_to_human_readable(timestamp: float, mode: str = "normal" elif diff < 86400 * 2: return f"{int(diff / 86400)}天前" else: - return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(timestamp)) + ":\n" + return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(timestamp)) + ":" else: # mode = "lite" or unknown # 只返回时分秒格式,喵~ return time.strftime("%H:%M:%S", time.localtime(timestamp)) diff --git a/src/plugin_system/base/base_plugin.py b/src/plugin_system/base/base_plugin.py index 8c0f79f53..0636ebeb6 100644 --- a/src/plugin_system/base/base_plugin.py +++ b/src/plugin_system/base/base_plugin.py @@ -109,6 +109,11 @@ class BasePlugin(ABC): with open(config_file_path, "r", encoding="utf-8") as f: self.config = toml.load(f) or {} logger.debug(f"{self.log_prefix} 配置已从 {config_file_path} 加载") + + # 从配置中更新 enable_plugin + if "plugin" in self.config and "enabled" in self.config["plugin"]: + self.enable_plugin = self.config["plugin"]["enabled"] + logger.debug(f"{self.log_prefix} 从配置更新插件启用状态: {self.enable_plugin}") else: logger.warning(f"{self.log_prefix} 不支持的配置文件格式: {file_ext},仅支持 .toml") self.config = {} diff --git a/src/plugin_system/core/plugin_manager.py b/src/plugin_system/core/plugin_manager.py index 99c534a34..6509660d7 100644 --- a/src/plugin_system/core/plugin_manager.py +++ b/src/plugin_system/core/plugin_manager.py @@ -93,6 +93,12 @@ class PluginManager: self.plugin_paths[plugin_name] = plugin_dir plugin_instance = plugin_class(plugin_dir=plugin_dir) + + # 检查插件是否启用 + if not plugin_instance.enable_plugin: + logger.info(f"插件 {plugin_name} 已禁用,跳过加载") + continue + if plugin_instance.register_plugin(): total_registered += 1 self.loaded_plugins[plugin_name] = plugin_instance diff --git a/src/plugins/built_in/mute_plugin/config.toml b/src/plugins/built_in/mute_plugin/config.toml index 00474a886..32d32817f 100644 --- a/src/plugins/built_in/mute_plugin/config.toml +++ b/src/plugins/built_in/mute_plugin/config.toml @@ -3,13 +3,13 @@ [plugin] name = "mute_plugin" version = "2.0.0" -enabled = true +enabled = false description = "群聊禁言管理插件,提供智能禁言功能" # 组件启用控制 [components] enable_smart_mute = true # 启用智能禁言Action -enable_mute_command = false # 启用禁言命令Command +enable_mute_command = true # 启用禁言命令Command # 禁言配置 [mute] diff --git a/src/plugins/built_in/mute_plugin/plugin.py b/src/plugins/built_in/mute_plugin/plugin.py index bc3eb2ddc..e69bbf1b2 100644 --- a/src/plugins/built_in/mute_plugin/plugin.py +++ b/src/plugins/built_in/mute_plugin/plugin.py @@ -163,11 +163,25 @@ class MuteAction(BaseAction): success = await self.send_command( command_name="GROUP_BAN", args={"qq_id": str(user_id), "duration": str(duration_int)}, - display_message=f"禁言了 {target} {time_str}", + display_message=f"发送禁言命令", ) if success: logger.info(f"{self.log_prefix} 成功发送禁言命令,用户 {target}({user_id}),时长 {duration_int} 秒") + # 存储动作信息 + await self.api.store_action_info( + action_build_into_prompt=True, + action_prompt_display=f"尝试禁言了用户 {target},时长 {time_str},原因:{reason}", + action_done=True, + thinking_id=self.thinking_id, + action_data={ + "target": target, + "user_id": user_id, + "duration": duration_int, + "duration_str": time_str, + "reason": reason + } + ) return True, f"成功禁言 {target},时长 {time_str}" else: error_msg = "发送禁言命令失败"