diff --git a/.tasks/2025-11-20_1_ai_chat_plugin.md b/.tasks/2025-11-20_1_ai_chat_plugin.md
new file mode 100644
index 0000000..145f949
--- /dev/null
+++ b/.tasks/2025-11-20_1_ai_chat_plugin.md
@@ -0,0 +1,217 @@
+# 背景
+文件名:2025-11-20_1_ai_chat_plugin.md
+创建于:2025-11-20_11:06:10
+创建者:admin
+主分支:main
+任务分支:无(不创建分支)
+Yolo模式:Off
+
+# 任务描述
+创建一个 AI 对话插件,支持多用户、多群聊的会话隔离,具备短期记忆功能(内存存储,无需持久化)。
+
+## 核心需求
+1. 基于 LlamaIndex + Ollama 的 AI 对话功能
+2. **维护一个全局的会话历史**(不按用户/群聊分隔)
+3. AI 能同时在不同群聊和不同用户对话
+4. 历史消息包含时间戳、群聊ID、用户ID信息,AI 能分辨消息来源
+5. 最大历史消息数量可配置(默认20条)
+6. 不需要持久化,重启后会话清空
+7. 提供清空全局历史会话的指令
+8. **AI 能够 @ 用户**:使用 `` 格式,禁止直接输出 user_id
+
+## 指令入口
+- `ai_chat`: 对话指令
+- `ai_chat_clear`: 清空当前用户的会话历史
+
+## 技术要点
+- 参考 NewsReport.py 的架构模式
+- 使用内存字典存储会话历史
+- 模型使用 qwen3:0.6b
+- 消息格式包含上下文信息(时间、群聊、用户)
+
+# 项目概览
+这是一个基于 PWF 框架的 WPS 机器人项目,使用插件化架构。
+- 插件基类:WPSAPI → BasicWPSInterface → PluginInterface
+- 每个插件通过 callback(message, chat_id, user_id) 处理消息
+- 支持数据库、定时任务、路由等功能
+- 已有多个游戏系统插件(菜园、战斗、炼金等)
+
+# 分析
+## 现有架构分析
+1. **插件系统**:
+ - 插件通过继承 WPSAPI 实现
+ - callback 方法接收 message(已去除 command)、chat_id、user_id
+ - 通过 register_plugin(command) 注册指令入口
+ - 通过 dependencies() 声明依赖关系
+
+2. **NewsReport.py 架构**:
+ - NewsAIAgent 类:封装 AI 逻辑(LlamaIndex + Ollama)
+ - NewsAIPlugin 类:继承 WPSAPI,作为插件入口
+ - 使用 ProjectConfig().GetFile() 进行文件缓存
+
+3. **会话管理需求**:
+ - **全局单一会话历史**,不按用户/群聊分隔
+ - 每条消息标记 chat_id(群聊ID)和 user_id(用户ID)
+ - AI 能看到所有群聊、所有用户的对话历史
+ - 使用 List[Dict] 存储全局消息列表
+ - 内存存储,无需持久化
+
+4. **配置系统**:
+ - 使用 ProjectConfig.FindItem(key, default) 读取配置
+ - 使用 ProjectConfig.SaveProperties() 保存配置
+ - 配置存储在 Assets/config.json
+
+## 技术选型
+- **AI 框架**:LlamaIndex + Ollama
+- **模型**:qwen3:0.6b
+- **存储方式**:内存列表(List[Dict])- 全局单一历史
+- **消息格式**:包含 timestamp, chat_id, user_id, role, content
+
+# 提议的解决方案
+## 整体架构
+```
+ChatAI.py
+├── ChatAIAgent (AI 智能体)
+│ ├── 会话字典管理
+│ ├── 消息历史维护
+│ └── LLM 对话调用
+├── ChatAIPlugin (对话插件)
+│ ├── 注册 ai_chat 指令
+│ └── 处理用户对话
+└── ChatAIClearPlugin (清空历史插件)
+ ├── 注册 ai_chat_clear 指令
+ └── 清空会话历史
+```
+
+## 核心类设计
+### 1. ChatAIAgent
+- 单例模式,通过 Architecture 注册
+- 维护 **全局消息历史**: List[Dict]
+- 每条消息格式:
+ ```python
+ {
+ "timestamp": "2025-11-20 11:06:10",
+ "chat_id": 12345,
+ "user_id": 67890,
+ "role": "user" | "assistant",
+ "content": "消息内容"
+ }
+ ```
+- AI 能够看到所有群聊、所有用户的完整对话历史
+
+### 2. ChatAIPlugin
+- 继承 WPSAPI
+- 依赖 WPSAPI
+- 初始化时创建/获取 ChatAIAgent
+- callback 处理用户消息,调用 Agent.chat()
+
+### 3. ChatAIClearPlugin
+- 继承 WPSAPI
+- 依赖 ChatAIPlugin(确保 Agent 已初始化)
+- callback 调用 Agent.clear_history() 清空全局历史
+
+## 系统提示词设计
+AI 需要理解的关键信息:
+1. **多群聊、多用户环境**:能看到所有群聊和用户的对话历史
+2. **消息格式**:每条消息包含 [时间] [群聊ID] [用户ID] 标记
+3. **@ 用户格式**:AI 回复时,当需要特指或回复某个用户时,使用 ``
+4. **禁止行为**:禁止直接输出裸露的 user_id 数字
+
+示例提示词:
+```
+你是一个友好的AI助手,能够同时在多个群聊中与不同用户对话。
+
+重要规则:
+- 你能看到所有群聊的对话历史,每条消息都标注了时间、群聊ID和用户ID
+- 当你需要特指某个用户或专门回复某个用户时,必须在回复中使用以下格式:
+- 禁止直接输出用户ID数字,始终使用 标签包裹
+- 注意区分不同群聊和不同用户的对话上下文
+
+示例对话:
+[2025-11-20 10:00:00] [群聊#12345] [用户#67890]: 你好
+AI回复: 你好!有什么可以帮助你的吗?
+
+[2025-11-20 10:01:00] [群聊#12345] [用户#11111]: 今天天气怎么样?
+AI回复: 抱歉,我无法获取实时天气信息。
+
+[2025-11-20 10:02:00] [群聊#67890] [用户#22222]: 刚才12345群的用户说了什么?
+AI回复: 刚才群聊12345中,用户67890问候了我,用户11111询问了天气。
+```
+
+## 消息流程
+```
+用户消息 (chat_id, user_id) → ChatAIPlugin.callback()
+ ↓
+获取当前时间
+ ↓
+添加用户消息到全局历史(带时间戳、chat_id、user_id)
+ ↓
+格式化全局历史消息为 LLM 输入
+ ├─ 包含所有群聊、所有用户的历史
+ ├─ 每条消息标注来源(群聊、用户、时间)
+ └─ AI 能理解跨群聊、跨用户的上下文
+ ↓
+调用 Ollama LLM(使用包含 @ 格式说明的系统提示词)
+ ↓
+添加 AI 回复到全局历史(标注当前 chat_id, user_id)
+ ↓
+返回格式化的回复
+```
+
+# 当前执行步骤:"1. 创建任务文件"
+
+# 任务进度
+[2025-11-20_11:06:10]
+- 已创建:.tasks/2025-11-20_1_ai_chat_plugin.md
+- 更改:创建任务文件,记录任务背景和设计方案
+- 原因:按照 RIPER-5 协议要求,在进入 EXECUTE 模式前创建任务文件
+- 阻碍因素:无
+- 状态:成功
+
+[2025-11-20_11:13:25]
+- 已修改:.tasks/2025-11-20_1_ai_chat_plugin.md
+- 更改:
+ 1. 更新核心需求,明确全局单一会话历史设计
+ 2. 添加系统提示词设计章节
+ 3. 说明 @ 用户格式:
+ 4. 禁止直接输出 user_id
+- 原因:根据用户反馈,澄清架构设计(全局历史 vs 分用户历史)
+- 阻碍因素:无
+- 状态:成功
+
+[2025-11-20_11:16:49]
+- 已修改:.tasks/2025-11-20_1_ai_chat_plugin.md
+- 更改:修正系统提示词设计,明确用户消息是普通文本,只有 AI 回复时才使用 格式
+- 原因:用户澄清 @ 用户格式只用于 AI 回复,不用于用户消息
+- 阻碍因素:无
+- 状态:成功
+
+[2025-11-20_11:22:38]
+- 已创建:Plugins/Others/ChatAI.py
+- 更改:完整实现 AI 对话插件系统
+ 1. ChatAIAgent 类:全局会话管理、消息历史维护、LLM 对话
+ 2. ChatAIPlugin 类:注册 ai_chat 指令,处理用户对话
+ 3. ChatAIClearPlugin 类:注册 ai_chat_clear 指令,清空历史
+ 4. 使用 qwen3:0.6b 模型
+ 5. 最大历史消息数量可配置(默认20)
+ 6. 系统提示词包含 @ 用户格式说明
+ 7. 全局单一会话历史设计
+- 原因:实施 EXECUTE 模式清单项 1-28
+- 阻碍因素:无
+- 状态:成功
+
+[2025-11-20_11:29:24]
+- 已修改:Plugins/Others/ChatAI.py
+- 更改:修复 AI 重复用户消息的问题
+ 1. 从 acomplete(文本补全)改为 achat(对话)方法
+ 2. 使用 ChatMessage 结构化消息格式
+ 3. 分离系统提示词和对话历史
+ 4. 优化提示词结构,更清晰地指示 AI 回复
+ 5. 添加 llama_index.core.llms.ChatMessage 导入
+- 原因:用户反馈 AI 一直重复用户说的话
+- 阻碍因素:无
+- 状态:未确认
+
+# 最终审查
+待完成
+
diff --git a/Plugins/Others/ChatAI.py b/Plugins/Others/ChatAI.py
new file mode 100644
index 0000000..733b3a0
--- /dev/null
+++ b/Plugins/Others/ChatAI.py
@@ -0,0 +1,423 @@
+from Plugins.WPSAPI import *
+from datetime import datetime
+from llama_index.llms.ollama import Ollama
+from llama_index.core.llms import ChatMessage
+from typing import List, Dict, Optional
+
+logger: ProjectConfig = Architecture.Get(ProjectConfig)
+OLLAMA_URL = logger.FindItem("ollama_url", "http://ollama.liubai.site")
+OLLAMA_MODEL = logger.FindItem("ollama_model", "qwen3:0.6b")
+MAX_HISTORY = logger.FindItem("chat_ai_max_history", 20)
+logger.SaveProperties()
+
+
+class ChatAIAgent:
+ """AI 对话智能体 - 维护全局会话历史"""
+
+ _instance: Optional['ChatAIAgent'] = None
+
+ def __init__(self, ollama_url: str = OLLAMA_URL, max_history: int = MAX_HISTORY):
+ """初始化 AI 智能体
+
+ Args:
+ ollama_url: Ollama 服务地址
+ max_history: 最大历史消息数量
+ """
+ self.ollama_url = ollama_url
+ self.max_history = max_history
+ self.llm = Ollama(model=OLLAMA_MODEL, base_url=ollama_url, request_timeout=600.0)
+
+ # 全局消息历史列表
+ self.global_history: List[Dict[str, any]] = []
+
+ # 系统提示词
+ self.system_prompt = """你是一个友好的AI助手,能够同时在多个群聊中与不同用户对话。
+
+重要规则:
+- 你能看到所有群聊的对话历史,每条消息都标注了时间、群聊ID和用户ID
+- 当你需要特指某个用户或专门回复某个用户时,必须在回复中使用以下格式:
+- 禁止直接输出用户ID数字,始终使用 标签包裹
+- 注意区分不同群聊和不同用户的对话上下文
+
+示例对话:
+[2025-11-20 10:00:00] [群聊#12345] [用户#67890]: 你好
+你的回复: 你好!有什么可以帮助你的吗?
+
+[2025-11-20 10:01:00] [群聊#12345] [用户#11111]: 今天天气怎么样?
+你的回复: 抱歉,我无法获取实时天气信息。"""
+
+ logger.Log("Info", f"{ConsoleFrontColor.GREEN}ChatAIAgent 初始化完成{ConsoleFrontColor.RESET}")
+ logger.Log("Info", f"模型: {OLLAMA_MODEL}, 最大历史: {max_history}")
+
+ @classmethod
+ def get_instance(cls) -> 'ChatAIAgent':
+ """获取单例实例"""
+ if cls._instance is None:
+ cls._instance = cls()
+ return cls._instance
+
+ def _add_message(self, chat_id: int, user_id: int, role: str, content: str) -> None:
+ """添加消息到全局历史
+
+ Args:
+ chat_id: 群聊ID
+ user_id: 用户ID
+ role: 角色 (user/assistant)
+ content: 消息内容
+ """
+ timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
+ message = {
+ "timestamp": timestamp,
+ "chat_id": chat_id,
+ "user_id": user_id,
+ "role": role,
+ "content": content
+ }
+ self.global_history.append(message)
+ self._trim_history()
+
+ def _trim_history(self) -> None:
+ """裁剪历史消息到最大长度"""
+ if len(self.global_history) > self.max_history:
+ # 保留最新的 max_history 条消息
+ self.global_history = self.global_history[-self.max_history:]
+ logger.Log("Info", f"历史消息已裁剪到 {self.max_history} 条")
+
+ def clear_history(self) -> bool:
+ """清空全局历史
+
+ Returns:
+ 是否成功
+ """
+ try:
+ old_count = len(self.global_history)
+ self.global_history.clear()
+ logger.Log("Info", f"已清空全局历史,共删除 {old_count} 条消息")
+ return True
+ except Exception as e:
+ logger.Log("Error", f"清空历史失败: {e}")
+ return False
+
+ def _format_message_with_context(self, msg: Dict[str, any]) -> str:
+ """格式化单条消息,包含上下文信息
+
+ Args:
+ msg: 消息字典
+
+ Returns:
+ 格式化后的消息字符串
+ """
+ timestamp = msg.get("timestamp", "")
+ chat_id = msg.get("chat_id", "")
+ user_id = msg.get("user_id", "")
+ role = msg.get("role", "")
+ content = msg.get("content", "")
+
+ if role == "user":
+ return f"[{timestamp}] [群聊#{chat_id}] [用户#{user_id}]: {content}"
+ else: # assistant
+ return f"[{timestamp}] [AI助手]: {content}"
+
+ def _format_history_for_llm(self) -> str:
+ """格式化全局历史为 LLM 输入
+
+ Returns:
+ 格式化后的历史字符串
+ """
+ if not self.global_history:
+ return ""
+
+ formatted_messages = [
+ self._format_message_with_context(msg)
+ for msg in self.global_history
+ ]
+ return "\n".join(formatted_messages)
+
+ async def chat(self, message: str, chat_id: int, user_id: int) -> str:
+ """处理对话
+
+ Args:
+ message: 用户消息
+ chat_id: 群聊ID
+ user_id: 用户ID
+
+ Returns:
+ AI 回复
+ """
+ try:
+ # 添加用户消息到历史
+ self._add_message(chat_id, user_id, "user", message)
+
+ # 格式化历史消息(不包括刚添加的用户消息)
+ history_without_last = self.global_history[:-1] if len(self.global_history) > 1 else []
+ history_text = "\n".join([
+ self._format_message_with_context(msg)
+ for msg in history_without_last
+ ]) if history_without_last else "(这是第一条对话)"
+
+ # 构建当前用户消息的格式化版本
+ current_msg = f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] [群聊#{chat_id}] [用户#{user_id}]: {message}"
+
+ # 构建对话提示
+ conversation_prompt = f"""以下是历史对话记录:
+{history_text}
+
+当前消息:
+{current_msg}
+
+请作为AI助手回复当前用户。记住:
+1. 使用 格式来称呼该用户
+2. 根据对话历史和当前消息,给出有意义的回复
+3. 如果历史中有其他群聊或用户的信息,你可以引用它们"""
+
+ logger.Log("Info", f"处理对话 - 群聊#{chat_id} 用户#{user_id}")
+ logger.Log("Info", f"当前历史消息数: {len(self.global_history)}")
+
+ # 使用 achat 方法进行对话
+ messages = [
+ ChatMessage(role="system", content=self.system_prompt),
+ ChatMessage(role="user", content=conversation_prompt)
+ ]
+
+ response = await self.llm.achat(messages)
+ answer = str(response.message.content)
+
+ # 添加 AI 回复到历史
+ self._add_message(chat_id, user_id, "assistant", answer)
+
+ logger.Log("Info", f"AI 回复长度: {len(answer)} 字符")
+
+ return answer
+
+ except Exception as e:
+ logger.Log("Error", f"对话处理失败: {e}")
+ import traceback
+ error_trace = traceback.format_exc()
+ logger.Log("Error", f"详细错误:\n{error_trace}")
+ return f"处理对话时出错: {str(e)}"
+
+
+class ChatAIPlugin(WPSAPI):
+ """AI 对话插件"""
+
+ def __init__(self):
+ super().__init__()
+ self.ai_agent = ChatAIAgent.get_instance()
+
+ @override
+ def dependencies(self) -> List[Type]:
+ return [WPSAPI]
+
+ @override
+ def is_enable_plugin(self) -> bool:
+ return True
+
+ def get_guide_title(self) -> str:
+ return "AI 智能对话"
+
+ def get_guide_subtitle(self) -> str:
+ return "基于 LlamaIndex + Ollama 的多群聊 AI 对话系统"
+
+ def get_guide_metadata(self) -> Dict[str, str]:
+ return {
+ "AI模型": OLLAMA_MODEL,
+ "最大历史": str(MAX_HISTORY),
+ "功能": "跨群聊智能对话",
+ }
+
+ def collect_command_entries(self) -> Sequence[GuideEntry]:
+ return (
+ {
+ "title": "AI对话",
+ "identifier": "ai_chat",
+ "description": "与AI助手对话,AI能看到所有群聊的历史消息并智能回复。",
+ "metadata": {"模型": OLLAMA_MODEL},
+ "icon": "🤖",
+ "badge": "AI",
+ "details": [
+ {
+ "type": "list",
+ "items": [
+ "AI 维护全局会话历史,能看到所有群聊的对话",
+ "每条消息包含时间戳、群聊ID、用户ID",
+ f"最多保留最近 {MAX_HISTORY} 条消息",
+ "AI 会使用 @用户 格式回复",
+ "示例:ai_chat 你好",
+ "示例:ai_chat 刚才其他群聊说了什么?",
+ ]
+ }
+ ]
+ },
+ )
+
+ def collect_guide_entries(self) -> Sequence[GuideEntry]:
+ return (
+ {
+ "title": "全局会话",
+ "description": (
+ "AI 维护一个全局的对话历史,能够跨群聊、跨用户理解上下文。"
+ "所有群聊的对话都在同一个历史中,AI 能够关联不同群聊的信息。"
+ ),
+ "icon": "🌐",
+ },
+ {
+ "title": "智能回复",
+ "description": (
+ "AI 能够识别消息来源(群聊、用户、时间),并使用 @ 格式回复特定用户。"
+ ),
+ "icon": "💬",
+ },
+ {
+ "title": "历史管理",
+ "description": (
+ f"自动保留最近 {MAX_HISTORY} 条消息,超出部分自动清理。"
+ "可使用 ai_chat_clear 指令手动清空所有历史。"
+ ),
+ "icon": "📝",
+ },
+ )
+
+ @override
+ def wake_up(self) -> None:
+ logger.Log("Info", f"{ConsoleFrontColor.GREEN}ChatAIPlugin AI对话插件已加载{ConsoleFrontColor.RESET}")
+ self.register_plugin("ai_chat")
+
+ @override
+ async def callback(self, message: str, chat_id: int, user_id: int) -> str|None:
+ """处理用户对话"""
+ try:
+ if not message or message.strip() == "":
+ help_text = f"""# 🤖 AI 智能对话使用帮助
+
+**直接发送消息即可与 AI 对话**
+
+**特性:**
+- ✨ AI 能看到所有群聊的对话历史
+- 🌐 跨群聊智能理解上下文
+- 💬 自动使用 @ 格式回复用户
+- 📝 保留最近 {MAX_HISTORY} 条消息
+
+**示例:**
+- `ai_chat 你好`
+- `ai_chat 今天天气怎么样?`
+- `ai_chat 刚才其他群聊说了什么?`
+- `ai_chat 请总结一下最近的对话`
+
+**清空历史:**
+使用 `ai_chat_clear` 指令清空所有历史消息
+
+**技术:**
+基于 LlamaIndex + Ollama ({OLLAMA_MODEL})"""
+ return await self.send_markdown_message(help_text, chat_id, user_id)
+
+ # 使用智能体处理对话
+ answer = await self.ai_agent.chat(message, chat_id, user_id)
+
+ # 格式化返回结果
+ formatted_answer = f"""🤖 **AI 智能对话**
+
+{answer}
+
+---
+*由 LlamaIndex + Ollama 驱动*"""
+
+ return await self.send_markdown_message(formatted_answer, chat_id, user_id)
+
+ except Exception as e:
+ logger.Log("Error", f"AI对话异常: {e}")
+ import traceback
+ error_detail = traceback.format_exc()
+ logger.Log("Error", f"详细错误: {error_detail}")
+ error_msg = f"""❌ **处理对话时出错**
+
+错误信息:{str(e)}
+
+请稍后重试或联系管理员。"""
+ return await self.send_markdown_message(error_msg, chat_id, user_id)
+
+
+class ChatAIClearPlugin(WPSAPI):
+ """清空 AI 对话历史插件"""
+
+ def __init__(self):
+ super().__init__()
+
+ @override
+ def dependencies(self) -> List[Type]:
+ return [ChatAIPlugin]
+
+ @override
+ def is_enable_plugin(self) -> bool:
+ return True
+
+ def get_guide_title(self) -> str:
+ return "清空 AI 对话历史"
+
+ def get_guide_subtitle(self) -> str:
+ return "清空全局 AI 对话历史记录"
+
+ def get_guide_metadata(self) -> Dict[str, str]:
+ return {
+ "功能": "历史管理",
+ "作用范围": "全局",
+ }
+
+ def collect_command_entries(self) -> Sequence[GuideEntry]:
+ return (
+ {
+ "title": "清空历史",
+ "identifier": "ai_chat_clear",
+ "description": "清空 AI 的全局对话历史记录。",
+ "icon": "🗑️",
+ "badge": "管理",
+ "details": [
+ {
+ "type": "list",
+ "items": [
+ "清空所有群聊的对话历史",
+ "AI 将不再记得之前的对话",
+ "不影响 AI 的基础功能",
+ "示例:ai_chat_clear",
+ ]
+ }
+ ]
+ },
+ )
+
+ @override
+ def wake_up(self) -> None:
+ logger.Log("Info", f"{ConsoleFrontColor.GREEN}ChatAIClearPlugin 清空历史插件已加载{ConsoleFrontColor.RESET}")
+ self.register_plugin("ai_chat_clear")
+
+ @override
+ async def callback(self, message: str, chat_id: int, user_id: int) -> str|None:
+ """清空对话历史"""
+ try:
+ # 获取 AI Agent 实例
+ ai_agent = ChatAIAgent.get_instance()
+
+ # 清空历史
+ success = ai_agent.clear_history()
+
+ if success:
+ result_msg = """✅ **历史已清空**
+
+AI 的全局对话历史已成功清空。
+
+AI 将不再记得之前的对话内容。"""
+ else:
+ result_msg = """❌ **清空失败**
+
+清空历史时出现错误,请稍后重试。"""
+
+ return await self.send_markdown_message(result_msg, chat_id, user_id)
+
+ except Exception as e:
+ logger.Log("Error", f"清空历史异常: {e}")
+ error_msg = f"""❌ **清空历史时出错**
+
+错误信息:{str(e)}
+
+请稍后重试或联系管理员。"""
+ return await self.send_markdown_message(error_msg, chat_id, user_id)
+
diff --git a/Plugins/Others/NewsReport.py b/Plugins/Others/NewsReport.py
index 38aed63..51fca51 100644
--- a/Plugins/Others/NewsReport.py
+++ b/Plugins/Others/NewsReport.py
@@ -59,9 +59,7 @@ class NewsAIAgent:
【重要】你必须按照以下步骤使用工具:
步骤1: 确定日期
-- 如果用户问"今天"或"今日",调用 get_current_date
-- 如果用户问"昨天"或"昨日",调用 get_yesterday_date
-- 如果用户提到具体日期(如"2025年11月17日"),调用 parse_date_from_text
+- 如果用户问"今天"或"今日"或者是与今日日期相关的问题,调用 get_current_date 获取今日日期
步骤2: 获取新闻内容
- 拿到日期后,必须调用 get_news_content(date="YYYY-MM-DD") 获取新闻