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.
- {fr_cli-2.2.8/fr_cli.egg-info → fr_cli-2.2.9}/PKG-INFO +70 -2
- {fr_cli-2.2.8 → fr_cli-2.2.9}/README.md +68 -1
- fr_cli-2.2.9/fr_cli/agent/acp.py +195 -0
- fr_cli-2.2.9/fr_cli/agent/hermes.py +321 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/agent/master.py +9 -0
- fr_cli-2.2.9/fr_cli/agent/plugin_system.py +195 -0
- fr_cli-2.2.9/fr_cli/agent/shell_mode.py +181 -0
- fr_cli-2.2.9/fr_cli/core/llm.py +266 -0
- fr_cli-2.2.9/fr_cli/core/model_factory.py +173 -0
- fr_cli-2.2.9/fr_cli/weapon/mcp.py +186 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9/fr_cli.egg-info}/PKG-INFO +70 -2
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli.egg-info/SOURCES.txt +5 -1
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli.egg-info/requires.txt +1 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/pyproject.toml +2 -1
- fr_cli-2.2.8/fr_cli/core/llm.py +0 -401
- fr_cli-2.2.8/fr_cli/weapon/mcp.py +0 -202
- fr_cli-2.2.8/tests/test_coding_helper.py +0 -205
- {fr_cli-2.2.8 → fr_cli-2.2.9}/LICENSE +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/MANIFEST.in +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/README.md +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/WEAPON.MD +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/__init__.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/addon/plugin.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/agent/__init__.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/agent/a2a.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/agent/builtins/__init__.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/agent/builtins/_utils.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/agent/builtins/db.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/agent/builtins/local.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/agent/builtins/powerful_agent_template.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/agent/builtins/rag.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/agent/builtins/rag_watcher_daemon.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/agent/builtins/remote.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/agent/builtins/spider.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/agent/client.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/agent/coding_helper.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/agent/executor.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/agent/generator.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/agent/image_and_parallel.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/agent/manager.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/agent/master_prompt.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/agent/remote.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/agent/server.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/agent/workflow.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/agent/workflow_system.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/breakthrough/update.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/command/__init__.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/command/executor.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/command/registry.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/command/security.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/conf/config.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/conf/wizard.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/core/chat.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/core/core.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/core/intent.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/core/recommender.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/core/stream.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/core/sysmon.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/core/thinking.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/gatekeeper/__init__.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/gatekeeper/daemon.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/gatekeeper/manager.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/lang/i18n.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/main.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/memory/context.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/memory/history.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/memory/session.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/repl/__init__.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/repl/commands.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/security/security.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/ui/ui.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/weapon/cron.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/weapon/dataframe.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/weapon/disk.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/weapon/fs.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/weapon/launcher.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/weapon/loader.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/weapon/mail.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/weapon/vision.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli/weapon/web.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli.egg-info/dependency_links.txt +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli.egg-info/entry_points.txt +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/fr_cli.egg-info/top_level.txt +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/setup.cfg +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/tests/test_a2a_and_providers.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/tests/test_integration_real.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/tests/test_master_prompt_fix.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/tests/test_model_config.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.2.9}/tests/test_new_features.py +0 -0
- {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.
|
|
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
|
-
##
|
|
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
|
-
##
|
|
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
|
# ---------- 工具调用解析 ----------
|