fr-cli 2.2.8__tar.gz → 2.2.9__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. {fr_cli-2.2.8/fr_cli.egg-info → fr_cli-2.2.9}/PKG-INFO +70 -2
  2. {fr_cli-2.2.8 → fr_cli-2.2.9}/README.md +68 -1
  3. fr_cli-2.2.9/fr_cli/agent/acp.py +195 -0
  4. fr_cli-2.2.9/fr_cli/agent/hermes.py +321 -0
  5. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/agent/master.py +9 -0
  6. fr_cli-2.2.9/fr_cli/agent/plugin_system.py +195 -0
  7. fr_cli-2.2.9/fr_cli/agent/shell_mode.py +181 -0
  8. fr_cli-2.2.9/fr_cli/core/llm.py +266 -0
  9. fr_cli-2.2.9/fr_cli/core/model_factory.py +173 -0
  10. fr_cli-2.2.9/fr_cli/weapon/mcp.py +186 -0
  11. {fr_cli-2.2.8 → fr_cli-2.2.9/fr_cli.egg-info}/PKG-INFO +70 -2
  12. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli.egg-info/SOURCES.txt +5 -1
  13. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli.egg-info/requires.txt +1 -0
  14. {fr_cli-2.2.8 → fr_cli-2.2.9}/pyproject.toml +2 -1
  15. fr_cli-2.2.8/fr_cli/core/llm.py +0 -401
  16. fr_cli-2.2.8/fr_cli/weapon/mcp.py +0 -202
  17. fr_cli-2.2.8/tests/test_coding_helper.py +0 -205
  18. {fr_cli-2.2.8 → fr_cli-2.2.9}/LICENSE +0 -0
  19. {fr_cli-2.2.8 → fr_cli-2.2.9}/MANIFEST.in +0 -0
  20. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/README.md +0 -0
  21. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/WEAPON.MD +0 -0
  22. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/__init__.py +0 -0
  23. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/addon/plugin.py +0 -0
  24. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/agent/__init__.py +0 -0
  25. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/agent/a2a.py +0 -0
  26. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/agent/builtins/__init__.py +0 -0
  27. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/agent/builtins/_utils.py +0 -0
  28. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/agent/builtins/db.py +0 -0
  29. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/agent/builtins/local.py +0 -0
  30. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/agent/builtins/powerful_agent_template.py +0 -0
  31. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/agent/builtins/rag.py +0 -0
  32. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/agent/builtins/rag_watcher_daemon.py +0 -0
  33. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/agent/builtins/remote.py +0 -0
  34. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/agent/builtins/spider.py +0 -0
  35. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/agent/client.py +0 -0
  36. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/agent/coding_helper.py +0 -0
  37. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/agent/executor.py +0 -0
  38. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/agent/generator.py +0 -0
  39. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/agent/image_and_parallel.py +0 -0
  40. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/agent/manager.py +0 -0
  41. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/agent/master_prompt.py +0 -0
  42. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/agent/remote.py +0 -0
  43. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/agent/server.py +0 -0
  44. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/agent/workflow.py +0 -0
  45. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/agent/workflow_system.py +0 -0
  46. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/breakthrough/update.py +0 -0
  47. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/command/__init__.py +0 -0
  48. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/command/executor.py +0 -0
  49. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/command/registry.py +0 -0
  50. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/command/security.py +0 -0
  51. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/conf/config.py +0 -0
  52. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/conf/wizard.py +0 -0
  53. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/core/chat.py +0 -0
  54. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/core/core.py +0 -0
  55. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/core/intent.py +0 -0
  56. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/core/recommender.py +0 -0
  57. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/core/stream.py +0 -0
  58. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/core/sysmon.py +0 -0
  59. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/core/thinking.py +0 -0
  60. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/gatekeeper/__init__.py +0 -0
  61. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/gatekeeper/daemon.py +0 -0
  62. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/gatekeeper/manager.py +0 -0
  63. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/lang/i18n.py +0 -0
  64. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/main.py +0 -0
  65. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/memory/context.py +0 -0
  66. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/memory/history.py +0 -0
  67. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/memory/session.py +0 -0
  68. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/repl/__init__.py +0 -0
  69. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/repl/commands.py +0 -0
  70. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/security/security.py +0 -0
  71. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/ui/ui.py +0 -0
  72. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/weapon/cron.py +0 -0
  73. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/weapon/dataframe.py +0 -0
  74. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/weapon/disk.py +0 -0
  75. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/weapon/fs.py +0 -0
  76. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/weapon/launcher.py +0 -0
  77. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/weapon/loader.py +0 -0
  78. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/weapon/mail.py +0 -0
  79. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/weapon/vision.py +0 -0
  80. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/weapon/web.py +0 -0
  81. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli.egg-info/dependency_links.txt +0 -0
  82. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli.egg-info/entry_points.txt +0 -0
  83. {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli.egg-info/top_level.txt +0 -0
  84. {fr_cli-2.2.8 → fr_cli-2.2.9}/setup.cfg +0 -0
  85. {fr_cli-2.2.8 → fr_cli-2.2.9}/tests/test_a2a_and_providers.py +0 -0
  86. {fr_cli-2.2.8 → fr_cli-2.2.9}/tests/test_integration_real.py +0 -0
  87. {fr_cli-2.2.8 → fr_cli-2.2.9}/tests/test_master_prompt_fix.py +0 -0
  88. {fr_cli-2.2.8 → fr_cli-2.2.9}/tests/test_model_config.py +0 -0
  89. {fr_cli-2.2.8 → fr_cli-2.2.9}/tests/test_new_features.py +0 -0
  90. {fr_cli-2.2.8 → fr_cli-2.2.9}/tests/test_new_providers.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fr-cli
3
- Version: 2.2.8
3
+ Version: 2.2.9
4
4
  Summary: 凡人打字机 - 支持多模型(Zhipu/DeepSeek/Kimi/Qwen/StepFun/MiniMax/Spark/Doubao/MiMo)的终极全能终端工具
5
5
  Author: FANREN CLI Author
6
6
  License-Expression: MIT
@@ -27,6 +27,7 @@ Requires-Dist: zhipuai>=2.0.0
27
27
  Requires-Dist: openai>=1.0.0
28
28
  Requires-Dist: requests>=2.28.0
29
29
  Requires-Dist: mcp>=1.6.0
30
+ Requires-Dist: pyyaml>=6.0.0
30
31
  Provides-Extra: data
31
32
  Requires-Dist: pandas>=1.5.0; extra == "data"
32
33
  Requires-Dist: openpyxl>=3.0.0; extra == "data"
@@ -73,6 +74,8 @@ Dynamic: license-file
73
74
 
74
75
  **支持 27+ 种 AI 模型(智谱/智谱Coding/DeepSeek/Kimi/Kimi K2/Kimi Code/StepFun/Step-3/MiniMax/M2.7/讯飞星火/豆包/MiMo/LongCat)的终极全能终端工具。**
75
76
 
77
+ **集成 Hermes Agent 核心功能:自我进化的 AI 助手。**
78
+
76
79
  ## ✨ 功能特性
77
80
 
78
81
  ### 🤖 多模型支持
@@ -234,7 +237,72 @@ python3 main.py
234
237
  /security 查看安全设置
235
238
  ```
236
239
 
237
- ## 📦 支持的模型提供商(25+)
240
+ ## Hermes 核心功能
241
+
242
+ ### 📋 任务管理
243
+ ```
244
+ from fr_cli.agent.hermes import get_task_manager, get_analytics
245
+
246
+ # 创建任务
247
+ tm = get_task_manager()
248
+ task = tm.create_task("完成代码审查")
249
+
250
+ # 记录分析
251
+ an = get_analytics()
252
+ an.record_request("glm-4-flash", 1000, 0.01)
253
+ ```
254
+
255
+ ### 🎯 目标追踪
256
+ ```
257
+ from fr_cli.agent.hermes import GoalTracker
258
+
259
+ gt = GoalTracker()
260
+ gt.set_goal("完成项目", ["阶段1", "阶段2", "阶段3"])
261
+ gt.update_progress("已完成阶段1", 0.33)
262
+ ```
263
+
264
+ ### ⏰ 定时任务
265
+ ```
266
+ from fr_cli.agent.hermes import get_cron_scheduler
267
+
268
+ cron = get_cron_scheduler()
269
+ cron.add_job("daily-report", "0 9 * * *", "生成日报")
270
+ ```
271
+
272
+ ### 🔌 插件系统
273
+ ```
274
+ from fr_cli.agent.plugin_system import get_plugin_registry
275
+
276
+ registry = get_plugin_registry()
277
+ plugins = registry.list_all()
278
+ ```
279
+
280
+ ### 🐚 Shell 模式 (Ctrl-X 切换)
281
+ ```
282
+ Agent 模式: 输入消息与 AI 对话
283
+ Shell 模式: 直接执行 shell 命令
284
+
285
+ 按 Ctrl-X 切换模式
286
+ ```
287
+
288
+ ### 🔗 ACP (Agent Client Protocol)
289
+ ```bash
290
+ # 启动 ACP 服务
291
+ fr acp
292
+
293
+ # 配置到 Zed
294
+ # ~/.config/zed/settings.json
295
+ {
296
+ "agent_servers": {
297
+ "fr-cli": {
298
+ "command": "fr",
299
+ "args": ["acp"]
300
+ }
301
+ }
302
+ }
303
+ ```
304
+
305
+ ## �📦 支持的模型提供商(25+)
238
306
 
239
307
  | 道统 | 默认模型 | API 地址 |
240
308
  |------|---------|----------|
@@ -2,6 +2,8 @@
2
2
 
3
3
  **支持 27+ 种 AI 模型(智谱/智谱Coding/DeepSeek/Kimi/Kimi K2/Kimi Code/StepFun/Step-3/MiniMax/M2.7/讯飞星火/豆包/MiMo/LongCat)的终极全能终端工具。**
4
4
 
5
+ **集成 Hermes Agent 核心功能:自我进化的 AI 助手。**
6
+
5
7
  ## ✨ 功能特性
6
8
 
7
9
  ### 🤖 多模型支持
@@ -163,7 +165,72 @@ python3 main.py
163
165
  /security 查看安全设置
164
166
  ```
165
167
 
166
- ## 📦 支持的模型提供商(25+)
168
+ ## Hermes 核心功能
169
+
170
+ ### 📋 任务管理
171
+ ```
172
+ from fr_cli.agent.hermes import get_task_manager, get_analytics
173
+
174
+ # 创建任务
175
+ tm = get_task_manager()
176
+ task = tm.create_task("完成代码审查")
177
+
178
+ # 记录分析
179
+ an = get_analytics()
180
+ an.record_request("glm-4-flash", 1000, 0.01)
181
+ ```
182
+
183
+ ### 🎯 目标追踪
184
+ ```
185
+ from fr_cli.agent.hermes import GoalTracker
186
+
187
+ gt = GoalTracker()
188
+ gt.set_goal("完成项目", ["阶段1", "阶段2", "阶段3"])
189
+ gt.update_progress("已完成阶段1", 0.33)
190
+ ```
191
+
192
+ ### ⏰ 定时任务
193
+ ```
194
+ from fr_cli.agent.hermes import get_cron_scheduler
195
+
196
+ cron = get_cron_scheduler()
197
+ cron.add_job("daily-report", "0 9 * * *", "生成日报")
198
+ ```
199
+
200
+ ### 🔌 插件系统
201
+ ```
202
+ from fr_cli.agent.plugin_system import get_plugin_registry
203
+
204
+ registry = get_plugin_registry()
205
+ plugins = registry.list_all()
206
+ ```
207
+
208
+ ### 🐚 Shell 模式 (Ctrl-X 切换)
209
+ ```
210
+ Agent 模式: 输入消息与 AI 对话
211
+ Shell 模式: 直接执行 shell 命令
212
+
213
+ 按 Ctrl-X 切换模式
214
+ ```
215
+
216
+ ### 🔗 ACP (Agent Client Protocol)
217
+ ```bash
218
+ # 启动 ACP 服务
219
+ fr acp
220
+
221
+ # 配置到 Zed
222
+ # ~/.config/zed/settings.json
223
+ {
224
+ "agent_servers": {
225
+ "fr-cli": {
226
+ "command": "fr",
227
+ "args": ["acp"]
228
+ }
229
+ }
230
+ }
231
+ ```
232
+
233
+ ## �📦 支持的模型提供商(25+)
167
234
 
168
235
  | 道统 | 默认模型 | API 地址 |
169
236
  |------|---------|----------|
@@ -0,0 +1,195 @@
1
+ """
2
+ ACP (Agent Client Protocol) 支持
3
+ 参考 kimi-cli 实现的 ACP 集成
4
+ """
5
+
6
+ import os
7
+ import json
8
+ import asyncio
9
+ import subprocess
10
+ from typing import Dict, Optional, Any
11
+ from dataclasses import dataclass
12
+ from enum import Enum
13
+
14
+
15
+ class ACPMessageType(Enum):
16
+ """ACP 消息类型"""
17
+ REQUEST = "request"
18
+ RESPONSE = "response"
19
+ ERROR = "error"
20
+ STREAM = "stream"
21
+
22
+
23
+ @dataclass
24
+ class ACPMessage:
25
+ """ACP 消息"""
26
+ id: str
27
+ type: ACPMessageType
28
+ method: str
29
+ params: Dict = None
30
+ result: Any = None
31
+ error: str = None
32
+
33
+
34
+ class ACPServer:
35
+ """ACP 服务器 - 作为 Agent 服务端"""
36
+
37
+ def __init__(self, name: str = "fr-cli"):
38
+ self.name = name
39
+ self.running = False
40
+ self.port = 8765
41
+
42
+ async def start(self, port: int = 8765):
43
+ """启动 ACP 服务器"""
44
+ self.port = port
45
+ self.running = True
46
+
47
+ try:
48
+ import uvicorn
49
+ from fastapi import FastAPI
50
+ from fastapi.websockets import WebSocket
51
+
52
+ app = FastAPI()
53
+
54
+ @app.websocket("/acp")
55
+ async def acp_endpoint(websocket: WebSocket):
56
+ await websocket.accept()
57
+ try:
58
+ while self.running:
59
+ data = await websocket.receive_text()
60
+ message = json.loads(data)
61
+
62
+ # 处理 ACP 消息
63
+ response = await self.handle_message(message)
64
+
65
+ if response:
66
+ await websocket.send_text(json.dumps(response))
67
+ except Exception as e:
68
+ print(f"ACP 连接错误: {e}")
69
+
70
+ config = uvicorn.Config(app, host="127.0.0.1", port=port, log_level="error")
71
+ server = uvicorn.Server(config)
72
+ await server.serve()
73
+
74
+ except ImportError:
75
+ print("警告: 需要安装 uvicorn 和 fastapi 来启用 ACP 服务")
76
+ except Exception as e:
77
+ print(f"ACP 服务器错误: {e}")
78
+
79
+ async def handle_message(self, message: Dict) -> Optional[Dict]:
80
+ """处理 ACP 消息"""
81
+ msg_type = message.get("type")
82
+
83
+ if msg_type == "request":
84
+ method = message.get("method")
85
+ params = message.get("params", {})
86
+
87
+ if method == "chat":
88
+ result = await self.chat(params)
89
+ return {"id": message.get("id"), "type": "response", "result": result}
90
+ elif method == "tools":
91
+ return {"id": message.get("id"), "type": "response", "result": self.get_tools()}
92
+
93
+ return None
94
+
95
+ async def chat(self, params: Dict) -> str:
96
+ """处理聊天请求"""
97
+ from fr_cli.core.core import ask
98
+ message = params.get("message", "")
99
+ try:
100
+ result, _ = await asyncio.to_thread(ask, message)
101
+ return result
102
+ except:
103
+ return "Error processing request"
104
+
105
+ def get_tools(self) -> list:
106
+ """获取可用工具列表"""
107
+ return [
108
+ {"name": "bash", "description": "Execute shell commands"},
109
+ {"name": "read", "description": "Read file contents"},
110
+ {"name": "write", "description": "Write file contents"},
111
+ {"name": "search", "description": "Search files"}
112
+ ]
113
+
114
+ def stop(self):
115
+ """停止 ACP 服务器"""
116
+ self.running = False
117
+
118
+
119
+ class ACPClient:
120
+ """ACP 客户端 - 连接其他 Agent"""
121
+
122
+ def __init__(self, server_url: str):
123
+ self.server_url = server_url
124
+ self.ws = None
125
+
126
+ async def connect(self):
127
+ """连接到 ACP 服务器"""
128
+ try:
129
+ import websockets
130
+ self.ws = await websockets.connect(f"{self.server_url}/acp")
131
+ return True
132
+ except Exception as e:
133
+ print(f"ACP 连接失败: {e}")
134
+ return False
135
+
136
+ async def send_message(self, method: str, params: Dict = None) -> Optional[Dict]:
137
+ """发送消息"""
138
+ if not self.ws:
139
+ return None
140
+
141
+ message = {
142
+ "id": str(asyncio.get_event_loop().time()),
143
+ "type": "request",
144
+ "method": method,
145
+ "params": params or {}
146
+ }
147
+
148
+ await self.ws.send(json.dumps(message))
149
+ response = await self.ws.recv()
150
+ return json.loads(response)
151
+
152
+ async def close(self):
153
+ """关闭连接"""
154
+ if self.ws:
155
+ await self.ws.close()
156
+
157
+
158
+ def run_acp_mode():
159
+ """运行 ACP 模式"""
160
+ print("""
161
+ ╔════════════════════════════════════════════════════╗
162
+ ║ ACP (Agent Client Protocol) 模式 ║
163
+ ╚════════════════════════════════════════════════════╝
164
+
165
+ fr-cli 将作为 ACP Agent 服务端运行,
166
+ 可与 Zed, VS Code 等 ACP 兼容编辑器集成。
167
+
168
+ 启动命令示例配置:
169
+
170
+ Zed: ~/.config/zed/settings.json
171
+ {
172
+ "agent_servers": {
173
+ "fr-cli": {
174
+ "command": "fr",
175
+ "args": ["acp"],
176
+ "env": {}
177
+ }
178
+ }
179
+ }
180
+ """)
181
+ server = ACPServer(name="fr-cli")
182
+ asyncio.run(server.start())
183
+
184
+
185
+ # CLI 命令
186
+ def main():
187
+ import sys
188
+ if len(sys.argv) > 1 and sys.argv[1] == "acp":
189
+ run_acp_mode()
190
+ else:
191
+ print("使用: fr acp 启动 ACP 服务")
192
+
193
+
194
+ if __name__ == "__main__":
195
+ main()
@@ -0,0 +1,321 @@
1
+ """
2
+ Hermes 功能模块
3
+ 参考 NousResearch/hermes-agent 实现核心功能
4
+ """
5
+
6
+ import os
7
+ import json
8
+ import time
9
+ from typing import Dict, List, Optional, Any
10
+ from dataclasses import dataclass, field
11
+ from enum import Enum
12
+
13
+
14
+ class TaskStatus(Enum):
15
+ """任务状态"""
16
+ PENDING = "pending"
17
+ RUNNING = "running"
18
+ COMPLETED = "completed"
19
+ FAILED = "failed"
20
+ PAUSED = "paused"
21
+
22
+
23
+ @dataclass
24
+ class Task:
25
+ """任务"""
26
+ id: str
27
+ description: str
28
+ status: TaskStatus = TaskStatus.PENDING
29
+ created_at: float = field(default_factory=time.time)
30
+ result: Optional[str] = None
31
+ error: Optional[str] = None
32
+
33
+
34
+ class TaskManager:
35
+ """任务管理器 - Hermes 核心功能"""
36
+
37
+ def __init__(self):
38
+ self.tasks: Dict[str, Task] = {}
39
+ self.history: List[Task] = []
40
+ self._task_counter = 0
41
+
42
+ def create_task(self, description: str) -> Task:
43
+ """创建任务"""
44
+ self._task_counter += 1
45
+ task_id = f"task-{self._task_counter}-{int(time.time())}"
46
+ task = Task(id=task_id, description=description)
47
+ self.tasks[task_id] = task
48
+ return task
49
+
50
+ def get_task(self, task_id: str) -> Optional[Task]:
51
+ """获取任务"""
52
+ return self.tasks.get(task_id)
53
+
54
+ def complete_task(self, task_id: str, result: str):
55
+ """完成任务"""
56
+ if task_id in self.tasks:
57
+ task = self.tasks[task_id]
58
+ task.status = TaskStatus.COMPLETED
59
+ task.result = result
60
+ self.history.append(task)
61
+ del self.tasks[task_id]
62
+
63
+ def fail_task(self, task_id: str, error: str):
64
+ """任务失败"""
65
+ if task_id in self.tasks:
66
+ task = self.tasks[task_id]
67
+ task.status = TaskStatus.FAILED
68
+ task.error = error
69
+ self.history.append(task)
70
+ del self.tasks[task_id]
71
+
72
+ def list_tasks(self) -> List[Task]:
73
+ """列出所有任务"""
74
+ return list(self.tasks.values())
75
+
76
+ def list_history(self, limit: int = 50) -> List[Task]:
77
+ """列出历史任务"""
78
+ return self.history[-limit:]
79
+
80
+
81
+ class GoalTracker:
82
+ """目标追踪器"""
83
+
84
+ def __init__(self):
85
+ self.goals: List[Dict] = []
86
+ self.current_goal: Optional[Dict] = None
87
+
88
+ def set_goal(self, description: str, milestones: List[str] = None):
89
+ """设置目标"""
90
+ goal = {
91
+ "description": description,
92
+ "milestones": milestones or [],
93
+ "progress": 0,
94
+ "started_at": time.time(),
95
+ "steps": []
96
+ }
97
+ self.current_goal = goal
98
+ self.goals.append(goal)
99
+ return goal
100
+
101
+ def update_progress(self, step: str, progress: float = None):
102
+ """更新进度"""
103
+ if self.current_goal:
104
+ self.current_goal["steps"].append({
105
+ "step": step,
106
+ "time": time.time()
107
+ })
108
+ if progress is not None:
109
+ self.current_goal["progress"] = progress
110
+
111
+ def complete_goal(self):
112
+ """完成目标"""
113
+ if self.current_goal:
114
+ self.current_goal["completed_at"] = time.time()
115
+ self.current_goal = None
116
+
117
+
118
+ class Analytics:
119
+ """分析统计"""
120
+
121
+ def __init__(self):
122
+ self.stats = {
123
+ "total_requests": 0,
124
+ "total_tokens": 0,
125
+ "total_cost": 0.0,
126
+ "successful_tasks": 0,
127
+ "failed_tasks": 0,
128
+ "models_used": {},
129
+ "start_time": time.time()
130
+ }
131
+
132
+ def record_request(self, model: str, tokens: int, cost: float):
133
+ """记录请求"""
134
+ self.stats["total_requests"] += 1
135
+ self.stats["total_tokens"] += tokens
136
+ self.stats["total_cost"] += cost
137
+
138
+ if model not in self.stats["models_used"]:
139
+ self.stats["models_used"][model] = {"requests": 0, "tokens": 0}
140
+ self.stats["models_used"][model]["requests"] += 1
141
+ self.stats["models_used"][model]["tokens"] += tokens
142
+
143
+ def record_task(self, success: bool):
144
+ """记录任务"""
145
+ if success:
146
+ self.stats["successful_tasks"] += 1
147
+ else:
148
+ self.stats["failed_tasks"] += 1
149
+
150
+ def get_stats(self) -> Dict:
151
+ """获取统计"""
152
+ uptime = time.time() - self.stats["start_time"]
153
+ return {
154
+ **self.stats,
155
+ "uptime_seconds": uptime,
156
+ "success_rate": (
157
+ self.stats["successful_tasks"] /
158
+ max(1, self.stats["successful_tasks"] + self.stats["failed_tasks"])
159
+ )
160
+ }
161
+
162
+ def format_report(self) -> str:
163
+ """格式化报告"""
164
+ stats = self.get_stats()
165
+ return f"""
166
+ 📊 Hermes 分析报告
167
+ ==================
168
+ 运行时间: {stats['uptime_seconds']:.0f} 秒
169
+ 总请求数: {stats['total_requests']}
170
+ 总 Token: {stats['total_tokens']:,}
171
+ 总费用: ${stats['total_cost']:.4f}
172
+ 成功率: {stats['success_rate']*100:.1f}%
173
+
174
+ 模型使用:
175
+ {''.join(f" - {m}: {v['requests']} 次\n" for m, v in stats['models_used'].items())}
176
+ """
177
+
178
+
179
+ class ConfigManager:
180
+ """配置管理器 - 支持环境变量和配置文件"""
181
+
182
+ _instance = None
183
+
184
+ def __new__(cls):
185
+ if cls._instance is None:
186
+ cls._instance = super().__new__(cls)
187
+ cls._instance._init()
188
+ return cls._instance
189
+
190
+ def _init(self):
191
+ """初始化"""
192
+ self.config = {}
193
+ self._load_env()
194
+ self._load_config_file()
195
+
196
+ def _load_env(self):
197
+ """从环境变量加载"""
198
+ for key in ["ZHIPU_API_KEY", "MOONSHOT_API_KEY", "DEEPSEEK_API_KEY",
199
+ "QWEN_API_KEY", "OPENAI_API_KEY", "ANTHROPIC_API_KEY"]:
200
+ if key in os.environ:
201
+ provider = key.lower().replace("_api_key", "")
202
+ if "zhipu" in provider:
203
+ provider = "zhipu"
204
+ elif "moonshot" in provider or "kimi" in provider:
205
+ provider = "kimi"
206
+ elif "deepseek" in provider:
207
+ provider = "deepseek"
208
+ elif "qwen" in provider:
209
+ provider = "qwen"
210
+ self.config.setdefault("providers", {})
211
+ self.config["providers"].setdefault(provider, {})
212
+ self.config["providers"][provider]["key"] = os.environ[key]
213
+
214
+ def _load_config_file(self):
215
+ """从配置文件加载"""
216
+ config_paths = [
217
+ os.path.expanduser("~/.fr_cli/config.json"),
218
+ os.path.expanduser("~/.hermes/config.json"),
219
+ "./config.json"
220
+ ]
221
+
222
+ for path in config_paths:
223
+ if os.path.exists(path):
224
+ try:
225
+ with open(path) as f:
226
+ data = json.load(f)
227
+ self.config.update(data)
228
+ except:
229
+ pass
230
+
231
+ def get(self, key: str, default=None):
232
+ """获取配置"""
233
+ keys = key.split(".")
234
+ value = self.config
235
+ for k in keys:
236
+ if isinstance(value, dict):
237
+ value = value.get(k)
238
+ else:
239
+ return default
240
+ return value if value is not None else default
241
+
242
+ def set(self, key: str, value: Any):
243
+ """设置配置"""
244
+ keys = key.split(".")
245
+ config = self.config
246
+ for k in keys[:-1]:
247
+ config = config.setdefault(k, {})
248
+ config[keys[-1]] = value
249
+
250
+ def save(self):
251
+ """保存配置"""
252
+ path = os.path.expanduser("~/.fr_cli/config.json")
253
+ os.makedirs(os.path.dirname(path), exist_ok=True)
254
+ with open(path, 'w') as f:
255
+ json.dump(self.config, f, indent=2)
256
+
257
+
258
+ class CronScheduler:
259
+ """定时任务调度器"""
260
+
261
+ def __init__(self):
262
+ self.jobs: List[Dict] = []
263
+ self.running = False
264
+
265
+ def add_job(self, name: str, schedule: str, command: str):
266
+ """添加定时任务"""
267
+ job = {
268
+ "name": name,
269
+ "schedule": schedule,
270
+ "command": command,
271
+ "last_run": None,
272
+ "next_run": None,
273
+ "enabled": True
274
+ }
275
+ self.jobs.append(job)
276
+ return job
277
+
278
+ def remove_job(self, name: str) -> bool:
279
+ """移除定时任务"""
280
+ self.jobs = [j for j in self.jobs if j["name"] != name]
281
+ return True
282
+
283
+ def list_jobs(self) -> List[Dict]:
284
+ """列出所有任务"""
285
+ return self.jobs
286
+
287
+ def run_job(self, name: str) -> bool:
288
+ """运行任务"""
289
+ for job in self.jobs:
290
+ if job["name"] == name:
291
+ job["last_run"] = time.time()
292
+ return True
293
+ return False
294
+
295
+
296
+ # 全局实例
297
+ _task_manager = None
298
+ _analytics = None
299
+ _config = None
300
+ _cron = None
301
+
302
+ def get_task_manager() -> TaskManager:
303
+ global _task_manager
304
+ if _task_manager is None:
305
+ _task_manager = TaskManager()
306
+ return _task_manager
307
+
308
+ def get_analytics() -> Analytics:
309
+ global _analytics
310
+ if _analytics is None:
311
+ _analytics = Analytics()
312
+ return _analytics
313
+
314
+ def get_config_manager() -> ConfigManager:
315
+ return ConfigManager()
316
+
317
+ def get_cron_scheduler() -> CronScheduler:
318
+ global _cron
319
+ if _cron is None:
320
+ _cron = CronScheduler()
321
+ return _cron
@@ -375,6 +375,15 @@ class MasterAgent:
375
375
  # 3. 智能法宝/Agent 检测
376
376
  self._detect_artifacts(final_answer, lang)
377
377
 
378
+ # 4. Hermes 自动学习更新
379
+ try:
380
+ from fr_cli.agent.hermes import get_analytics, get_task_manager
381
+ analytics = get_analytics()
382
+ task_mgr = get_task_manager()
383
+ analytics.record_request(self.state.model_name, 100, 0.001)
384
+ except Exception:
385
+ pass
386
+
378
387
  return final_answer, True
379
388
 
380
389
  # ---------- 工具调用解析 ----------