fr-cli 2.1.0__py3-none-any.whl
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/README.md +148 -0
- fr_cli/WEAPON.MD +186 -0
- fr_cli/__init__.py +4 -0
- fr_cli/addon/plugin.py +69 -0
- fr_cli/agent/__init__.py +9 -0
- fr_cli/agent/builtins/__init__.py +4 -0
- fr_cli/agent/builtins/_utils.py +48 -0
- fr_cli/agent/builtins/db.py +269 -0
- fr_cli/agent/builtins/local.py +105 -0
- fr_cli/agent/builtins/rag.py +652 -0
- fr_cli/agent/builtins/rag_watcher_daemon.py +156 -0
- fr_cli/agent/builtins/remote.py +214 -0
- fr_cli/agent/builtins/spider.py +247 -0
- fr_cli/agent/client.py +164 -0
- fr_cli/agent/executor.py +86 -0
- fr_cli/agent/generator.py +104 -0
- fr_cli/agent/manager.py +193 -0
- fr_cli/agent/master.py +604 -0
- fr_cli/agent/master_prompt.py +118 -0
- fr_cli/agent/remote.py +70 -0
- fr_cli/agent/server.py +279 -0
- fr_cli/agent/workflow.py +164 -0
- fr_cli/breakthrough/update.py +154 -0
- fr_cli/command/__init__.py +4 -0
- fr_cli/command/executor.py +276 -0
- fr_cli/command/registry.py +1034 -0
- fr_cli/command/security.py +30 -0
- fr_cli/conf/config.py +126 -0
- fr_cli/conf/wizard.py +172 -0
- fr_cli/core/chat.py +280 -0
- fr_cli/core/core.py +111 -0
- fr_cli/core/intent.py +129 -0
- fr_cli/core/recommender.py +71 -0
- fr_cli/core/stream.py +83 -0
- fr_cli/core/sysmon.py +117 -0
- fr_cli/core/thinking.py +215 -0
- fr_cli/gatekeeper/__init__.py +7 -0
- fr_cli/gatekeeper/daemon.py +216 -0
- fr_cli/gatekeeper/manager.py +218 -0
- fr_cli/lang/i18n.py +827 -0
- fr_cli/main.py +329 -0
- fr_cli/memory/context.py +119 -0
- fr_cli/memory/history.py +96 -0
- fr_cli/memory/session.py +134 -0
- fr_cli/repl/__init__.py +0 -0
- fr_cli/repl/commands.py +1098 -0
- fr_cli/security/security.py +46 -0
- fr_cli/ui/ui.py +116 -0
- fr_cli/weapon/cron.py +217 -0
- fr_cli/weapon/dataframe.py +97 -0
- fr_cli/weapon/disk.py +141 -0
- fr_cli/weapon/fs.py +206 -0
- fr_cli/weapon/launcher.py +249 -0
- fr_cli/weapon/loader.py +98 -0
- fr_cli/weapon/mail.py +227 -0
- fr_cli/weapon/mcp.py +204 -0
- fr_cli/weapon/vision.py +74 -0
- fr_cli/weapon/web.py +88 -0
- fr_cli-2.1.0.dist-info/METADATA +227 -0
- fr_cli-2.1.0.dist-info/RECORD +64 -0
- fr_cli-2.1.0.dist-info/WHEEL +5 -0
- fr_cli-2.1.0.dist-info/entry_points.txt +2 -0
- fr_cli-2.1.0.dist-info/licenses/LICENSE +21 -0
- fr_cli-2.1.0.dist-info/top_level.txt +1 -0
fr_cli/agent/master.py
ADDED
|
@@ -0,0 +1,604 @@
|
|
|
1
|
+
"""
|
|
2
|
+
主控 Agent(MasterAgent)—— 自我进化型全能助手
|
|
3
|
+
类似 OpenClaw 的中央控制器,负责理解用户意图、规划执行、调用工具、反思进化。
|
|
4
|
+
|
|
5
|
+
配置文件体系(~/.fr_cli_master/):
|
|
6
|
+
persona.md — 人设文件(自定义系统人设,覆盖默认 prompt)
|
|
7
|
+
skills.md — 技能装备文件(特殊能力、高级用法描述)
|
|
8
|
+
memory.json — 交互记忆(成功/失败记录)
|
|
9
|
+
evolution.json — 进化记录(prompt 追加、成功/失败模式统计)
|
|
10
|
+
session.json — 会话状态(当前任务、上下文延续)
|
|
11
|
+
status.json — 状态文件(启用状态、统计、时间戳)
|
|
12
|
+
"""
|
|
13
|
+
import json
|
|
14
|
+
import re
|
|
15
|
+
from datetime import datetime
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
# 上下文记忆与会话存档
|
|
19
|
+
from fr_cli.memory.context import extract_recent_turns, build_context_summary, save_context
|
|
20
|
+
from fr_cli.memory.session import create_session, update_session
|
|
21
|
+
from fr_cli.addon.plugin import extract_code, PLUGIN_DIR
|
|
22
|
+
from fr_cli.ui.ui import RED, YELLOW, GREEN, DIM, RESET
|
|
23
|
+
|
|
24
|
+
MASTER_DIR = Path.home() / ".fr_cli_master"
|
|
25
|
+
PERSONA_FILE = MASTER_DIR / "persona.md"
|
|
26
|
+
SKILLS_FILE = MASTER_DIR / "skills.md"
|
|
27
|
+
MEMORY_FILE = MASTER_DIR / "memory.json"
|
|
28
|
+
EVOLUTION_FILE = MASTER_DIR / "evolution.json"
|
|
29
|
+
SESSION_FILE = MASTER_DIR / "session.json"
|
|
30
|
+
STATUS_FILE = MASTER_DIR / "status.json"
|
|
31
|
+
|
|
32
|
+
# ---------- 默认配置内容 ----------
|
|
33
|
+
|
|
34
|
+
_DEFAULT_PERSONA = """# MasterAgent 人设
|
|
35
|
+
|
|
36
|
+
你是 凡人打字机 的【主控Agent】——一位全能的AI助手兼系统指挥官。
|
|
37
|
+
|
|
38
|
+
## 核心职责
|
|
39
|
+
1. 深入理解用户需求,将复杂任务拆解为可执行的步骤
|
|
40
|
+
2. 调用系统工具完成用户的请求(文件、搜索、邮件、画图、定时任务等)
|
|
41
|
+
3. 观察工具执行结果,必要时进行多轮修正
|
|
42
|
+
4. 用中文向用户汇报最终结果
|
|
43
|
+
|
|
44
|
+
## 执行原则
|
|
45
|
+
- 优先使用已验证成功的工具组合
|
|
46
|
+
- 如果工具调用失败,分析原因并尝试替代方案
|
|
47
|
+
- 禁止执行 rm -rf、格式化磁盘等危险操作
|
|
48
|
+
- 不在 Thought 中编造不存在的信息
|
|
49
|
+
- 每次 Action 后等待 Observation 再继续
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
_DEFAULT_SKILLS = """# MasterAgent 技能装备
|
|
53
|
+
|
|
54
|
+
## 高级规划
|
|
55
|
+
- 可将复杂任务分解为最多8步的ReAct循环
|
|
56
|
+
- 支持多工具串联调用(如:搜索→整理→写入文件)
|
|
57
|
+
- 支持条件分支:根据中间结果调整后续步骤
|
|
58
|
+
|
|
59
|
+
## 自我进化
|
|
60
|
+
- 自动记录每次工具调用的成功/失败模式
|
|
61
|
+
- 每10次交互自动反思并生成进化提示词
|
|
62
|
+
- 优先使用高频成功工具,规避高频失败路径
|
|
63
|
+
- 进化提示词自动追加到 system prompt 中
|
|
64
|
+
|
|
65
|
+
## 状态感知
|
|
66
|
+
- 读取当前工作目录、可用工具列表
|
|
67
|
+
- 感知用户语言偏好(zh/en)
|
|
68
|
+
- 跟踪任务执行上下文,支持多轮修正
|
|
69
|
+
- 从 session.json 中恢复未完成的任务上下文
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
_DEFAULT_SESSION = {
|
|
73
|
+
"current_task": None,
|
|
74
|
+
"task_history": [],
|
|
75
|
+
"context_notes": "",
|
|
76
|
+
"last_task_id": 0,
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
_DEFAULT_STATUS = {
|
|
80
|
+
"enabled": False,
|
|
81
|
+
"total_interactions": 0,
|
|
82
|
+
"evolution_count": 0,
|
|
83
|
+
"created_at": datetime.now().isoformat(),
|
|
84
|
+
"last_active": None,
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
_DEFAULT_MEMORY = {"interactions": []}
|
|
88
|
+
|
|
89
|
+
_DEFAULT_EVOLUTION = {
|
|
90
|
+
"success": [],
|
|
91
|
+
"failure": [],
|
|
92
|
+
"prompt_addon": "",
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# ---------- 文件工具 ----------
|
|
97
|
+
|
|
98
|
+
def _ensure_master_dir():
|
|
99
|
+
MASTER_DIR.mkdir(parents=True, exist_ok=True)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _load_json(path, default=None):
|
|
103
|
+
if path.exists():
|
|
104
|
+
try:
|
|
105
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
106
|
+
return json.load(f)
|
|
107
|
+
except Exception:
|
|
108
|
+
pass
|
|
109
|
+
return default if default is not None else {}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _save_json(path, data):
|
|
113
|
+
_ensure_master_dir()
|
|
114
|
+
try:
|
|
115
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
116
|
+
json.dump(data, f, ensure_ascii=False, indent=2)
|
|
117
|
+
except Exception:
|
|
118
|
+
pass
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _load_text(path, default=""):
|
|
122
|
+
if path.exists():
|
|
123
|
+
try:
|
|
124
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
125
|
+
return f.read()
|
|
126
|
+
except Exception:
|
|
127
|
+
pass
|
|
128
|
+
return default
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _save_text(path, content):
|
|
132
|
+
_ensure_master_dir()
|
|
133
|
+
try:
|
|
134
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
135
|
+
f.write(content)
|
|
136
|
+
except Exception:
|
|
137
|
+
pass
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _ensure_all_master_files():
|
|
141
|
+
"""初始化所有 MasterAgent 配置文件(有漏即补)"""
|
|
142
|
+
_ensure_master_dir()
|
|
143
|
+
if not PERSONA_FILE.exists():
|
|
144
|
+
_save_text(PERSONA_FILE, _DEFAULT_PERSONA)
|
|
145
|
+
if not SKILLS_FILE.exists():
|
|
146
|
+
_save_text(SKILLS_FILE, _DEFAULT_SKILLS)
|
|
147
|
+
if not MEMORY_FILE.exists():
|
|
148
|
+
_save_json(MEMORY_FILE, _DEFAULT_MEMORY)
|
|
149
|
+
if not EVOLUTION_FILE.exists():
|
|
150
|
+
_save_json(EVOLUTION_FILE, _DEFAULT_EVOLUTION)
|
|
151
|
+
if not SESSION_FILE.exists():
|
|
152
|
+
_save_json(SESSION_FILE, _DEFAULT_SESSION)
|
|
153
|
+
if not STATUS_FILE.exists():
|
|
154
|
+
_save_json(STATUS_FILE, _DEFAULT_STATUS)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class MasterAgent:
|
|
158
|
+
"""
|
|
159
|
+
主控 Agent —— 统一入口,自我进化
|
|
160
|
+
|
|
161
|
+
核心循环:
|
|
162
|
+
1. 接收用户输入
|
|
163
|
+
2. 分析意图 → 判断是否需要工具
|
|
164
|
+
3. 如需工具:规划 → 执行 → 观察 → 综合回答
|
|
165
|
+
4. 记录交互 → 反思 → 进化
|
|
166
|
+
"""
|
|
167
|
+
|
|
168
|
+
MAX_STEPS = 8 # 单次任务最大工具调用步数
|
|
169
|
+
|
|
170
|
+
def __init__(self, state):
|
|
171
|
+
self.state = state
|
|
172
|
+
# 确保所有配置文件存在(有漏即补)
|
|
173
|
+
_ensure_all_master_files()
|
|
174
|
+
self.persona = _load_text(PERSONA_FILE, _DEFAULT_PERSONA)
|
|
175
|
+
self.skills = _load_text(SKILLS_FILE, _DEFAULT_SKILLS)
|
|
176
|
+
self.evolution = _load_json(EVOLUTION_FILE, _DEFAULT_EVOLUTION)
|
|
177
|
+
self.memory = _load_json(MEMORY_FILE, _DEFAULT_MEMORY)
|
|
178
|
+
self.session = _load_json(SESSION_FILE, _DEFAULT_SESSION)
|
|
179
|
+
self._status_data = _load_json(STATUS_FILE, _DEFAULT_STATUS)
|
|
180
|
+
self._step_count = 0
|
|
181
|
+
|
|
182
|
+
# ---------- 工具描述生成 ----------
|
|
183
|
+
|
|
184
|
+
def _build_tools_desc(self):
|
|
185
|
+
"""从注册表动态生成工具描述文本,同时包含可用Agent列表"""
|
|
186
|
+
from fr_cli.command.registry import get_registry
|
|
187
|
+
from fr_cli.agent.client import discover_all_agents
|
|
188
|
+
reg = get_registry()
|
|
189
|
+
lines = []
|
|
190
|
+
for t in reg.get_tools():
|
|
191
|
+
params_str = ", ".join(f"{k}:{v.__name__ if hasattr(v, '__name__') else str(v)}"
|
|
192
|
+
for k, v in t.get("params", {}).items())
|
|
193
|
+
lines.append(f"- {t['name']}: {t['description']} 参数: {params_str or '无'}")
|
|
194
|
+
|
|
195
|
+
# 追加 MCP 外部神通
|
|
196
|
+
mcp_manager = getattr(self.state, "mcp", None)
|
|
197
|
+
if mcp_manager:
|
|
198
|
+
try:
|
|
199
|
+
mcp_tools = mcp_manager.list_all_tools()
|
|
200
|
+
if mcp_tools:
|
|
201
|
+
lines.append("\n=== MCP 外部神通 ===")
|
|
202
|
+
for t in mcp_tools:
|
|
203
|
+
lines.append(f"- {t['name']}: {t['description']} (服务器: {t['server']})")
|
|
204
|
+
lines.append("\n调用方式: mcp_call({\"server\": \"服务器名\", \"tool\": \"工具名\", \"arguments\": {...}})")
|
|
205
|
+
except Exception:
|
|
206
|
+
pass
|
|
207
|
+
|
|
208
|
+
# 追加可用 Agent 列表(本地 + 远程)
|
|
209
|
+
agents = discover_all_agents()
|
|
210
|
+
if agents:
|
|
211
|
+
lines.append("\n=== 可协作的独立Agent ===")
|
|
212
|
+
for a in agents:
|
|
213
|
+
lines.append(f"- [{a['type']}] {a['name']}: {a['description']}")
|
|
214
|
+
lines.append("\n调用方式: agent_call({\"name\": \"Agent名\", \"user_input\": \"任务描述\"})")
|
|
215
|
+
return "\n".join(lines)
|
|
216
|
+
|
|
217
|
+
# ---------- System Prompt 组装 ----------
|
|
218
|
+
|
|
219
|
+
def _build_system_prompt(self, lang):
|
|
220
|
+
"""组装完整的 system prompt:人设 + 技能 + 工具 + 进化追加"""
|
|
221
|
+
from fr_cli.agent.master_prompt import MASTER_SYSTEM_PROMPT_ZH, MASTER_SYSTEM_PROMPT_EN
|
|
222
|
+
base_prompt = MASTER_SYSTEM_PROMPT_ZH if lang == "zh" else MASTER_SYSTEM_PROMPT_EN
|
|
223
|
+
|
|
224
|
+
parts = [base_prompt.format(tools_desc=self._build_tools_desc())]
|
|
225
|
+
|
|
226
|
+
# 自定义人设(去重:如果 persona.md 内容与默认不同才追加)
|
|
227
|
+
custom_persona = self.persona.strip()
|
|
228
|
+
if custom_persona and custom_persona != _DEFAULT_PERSONA.strip():
|
|
229
|
+
parts.append(f"\n[自定义人设]\n{custom_persona}")
|
|
230
|
+
|
|
231
|
+
# 技能装备
|
|
232
|
+
skills_text = self.skills.strip()
|
|
233
|
+
if skills_text and skills_text != _DEFAULT_SKILLS.strip():
|
|
234
|
+
parts.append(f"\n[技能装备]\n{skills_text}")
|
|
235
|
+
|
|
236
|
+
# 进化追加
|
|
237
|
+
if self.evolution.get("prompt_addon"):
|
|
238
|
+
parts.append(f"\n[进化补充提示]\n{self.evolution['prompt_addon']}")
|
|
239
|
+
|
|
240
|
+
# 会话上下文延续
|
|
241
|
+
if self.session.get("context_notes"):
|
|
242
|
+
parts.append(f"\n[会话上下文]\n{self.session['context_notes']}")
|
|
243
|
+
|
|
244
|
+
return "\n".join(parts)
|
|
245
|
+
|
|
246
|
+
# ---------- 核心 ReAct 循环 ----------
|
|
247
|
+
|
|
248
|
+
def handle(self, user_input):
|
|
249
|
+
"""
|
|
250
|
+
处理用户输入的主入口。
|
|
251
|
+
返回 (assistant_reply, should_continue)
|
|
252
|
+
"""
|
|
253
|
+
self._step_count = 0
|
|
254
|
+
lang = self.state.lang
|
|
255
|
+
|
|
256
|
+
# 更新状态
|
|
257
|
+
self._status_data["last_active"] = datetime.now().isoformat()
|
|
258
|
+
self._status_data["total_interactions"] = self._status_data.get("total_interactions", 0) + 1
|
|
259
|
+
_save_json(STATUS_FILE, self._status_data)
|
|
260
|
+
|
|
261
|
+
# 更新当前任务
|
|
262
|
+
self.session["current_task"] = {
|
|
263
|
+
"input": user_input,
|
|
264
|
+
"started_at": datetime.now().isoformat(),
|
|
265
|
+
"steps": [],
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
# 组装 system prompt
|
|
269
|
+
system_content = self._build_system_prompt(lang)
|
|
270
|
+
|
|
271
|
+
messages = [
|
|
272
|
+
{"role": "system", "content": system_content},
|
|
273
|
+
{"role": "user", "content": user_input},
|
|
274
|
+
]
|
|
275
|
+
|
|
276
|
+
# 追加短期记忆(最近5条成功/失败记录)
|
|
277
|
+
recent_memory = self._get_recent_memory()
|
|
278
|
+
if recent_memory:
|
|
279
|
+
messages.insert(1, {"role": "system", "content": f"[近期记忆]\n{recent_memory}"})
|
|
280
|
+
|
|
281
|
+
# ReAct 循环
|
|
282
|
+
final_answer = None
|
|
283
|
+
observations = []
|
|
284
|
+
|
|
285
|
+
while self._step_count < self.MAX_STEPS:
|
|
286
|
+
self._step_count += 1
|
|
287
|
+
from fr_cli.core.stream import stream_cnt
|
|
288
|
+
|
|
289
|
+
# 调用 LLM 获取 Thought + Action
|
|
290
|
+
txt, usage, _ = stream_cnt(
|
|
291
|
+
self.state.client, self.state.model_name, messages, lang,
|
|
292
|
+
custom_prefix="", max_tokens=2048, silent=True
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
# 解析工具调用
|
|
296
|
+
tool_calls = self._extract_tool_calls(txt)
|
|
297
|
+
|
|
298
|
+
if not tool_calls:
|
|
299
|
+
# 没有工具调用 → 直接作为最终答案
|
|
300
|
+
final_answer = txt.strip()
|
|
301
|
+
break
|
|
302
|
+
|
|
303
|
+
# 有工具调用 → 执行并观察
|
|
304
|
+
observation_lines = []
|
|
305
|
+
for call in tool_calls:
|
|
306
|
+
result, error = self._execute_tool(call["tool"], call.get("params", {}))
|
|
307
|
+
step_info = {
|
|
308
|
+
"tool": call["tool"],
|
|
309
|
+
"params": call.get("params", {}),
|
|
310
|
+
"success": error is None,
|
|
311
|
+
"time": datetime.now().isoformat(),
|
|
312
|
+
}
|
|
313
|
+
self.session["current_task"]["steps"].append(step_info)
|
|
314
|
+
if error:
|
|
315
|
+
observation_lines.append(f"❌ 工具 {call['tool']} 失败: {error}")
|
|
316
|
+
self._record_interaction(user_input, call["tool"], False, error)
|
|
317
|
+
else:
|
|
318
|
+
observation_lines.append(f"✅ 工具 {call['tool']} 结果: {str(result)[:500]}")
|
|
319
|
+
self._record_interaction(user_input, call["tool"], True, str(result)[:200])
|
|
320
|
+
|
|
321
|
+
observation_text = "\n".join(observation_lines)
|
|
322
|
+
observations.append(observation_text)
|
|
323
|
+
|
|
324
|
+
# 将观察和之前的 assistant 回复加入 messages
|
|
325
|
+
messages.append({"role": "assistant", "content": txt})
|
|
326
|
+
messages.append({"role": "user", "content": f"[系统观察结果]\n{observation_text}\n\n请基于以上结果继续思考或给出最终回答。"})
|
|
327
|
+
|
|
328
|
+
if final_answer is None:
|
|
329
|
+
# 达到最大步数仍未收敛,强制要求总结
|
|
330
|
+
messages.append({"role": "user", "content": "已达到最大执行步数,请基于已有观察结果直接给出最终回答,不要再调用工具。"})
|
|
331
|
+
from fr_cli.core.stream import stream_cnt
|
|
332
|
+
final_answer, _, _ = stream_cnt(
|
|
333
|
+
self.state.client, self.state.model_name, messages, lang,
|
|
334
|
+
custom_prefix="", max_tokens=2048, silent=True
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
# 保存会话结果
|
|
338
|
+
task = self.session["current_task"]
|
|
339
|
+
task["finished_at"] = datetime.now().isoformat()
|
|
340
|
+
task["final_answer"] = final_answer[:500]
|
|
341
|
+
task["step_count"] = self._step_count
|
|
342
|
+
self.session["task_history"].append(task)
|
|
343
|
+
# 只保留最近 20 个任务历史
|
|
344
|
+
self.session["task_history"] = self.session["task_history"][-20:]
|
|
345
|
+
self.session["current_task"] = None
|
|
346
|
+
# 提取上下文笔记(供下次对话延续)
|
|
347
|
+
if final_answer and len(final_answer) > 50:
|
|
348
|
+
self.session["context_notes"] = f"上一轮任务摘要:{user_input[:50]}... → {final_answer[:100]}..."
|
|
349
|
+
_save_json(SESSION_FILE, self.session)
|
|
350
|
+
|
|
351
|
+
# 保存到 state.messages 以便会话连贯
|
|
352
|
+
self.state.messages.append({"role": "user", "content": user_input})
|
|
353
|
+
self.state.messages.append({"role": "assistant", "content": final_answer})
|
|
354
|
+
|
|
355
|
+
# 触发反思与进化(异步感,实际同步执行)
|
|
356
|
+
self._reflect_and_evolve(user_input, observations, final_answer)
|
|
357
|
+
|
|
358
|
+
# ---------- 后处理:与传统模式对齐体验 ----------
|
|
359
|
+
|
|
360
|
+
# 1. 更新上下文摘要(与传统模式共享同一套记忆系统)
|
|
361
|
+
recent = extract_recent_turns(self.state.messages, 5)
|
|
362
|
+
self.state.context_summary = build_context_summary(recent, lang)
|
|
363
|
+
save_context(self.state.sn, self.state.context_summary)
|
|
364
|
+
|
|
365
|
+
# 2. 自动按日期存档会话
|
|
366
|
+
if not self.state.auto_session_path:
|
|
367
|
+
path = create_session(self.state.messages)
|
|
368
|
+
if path:
|
|
369
|
+
self.state.auto_session_path = path
|
|
370
|
+
print(f"{DIM}📁 自动会话已创建: {Path(path).name}{RESET}")
|
|
371
|
+
else:
|
|
372
|
+
update_session(self.state.auto_session_path, self.state.messages)
|
|
373
|
+
|
|
374
|
+
# 3. 智能法宝/Agent 检测
|
|
375
|
+
self._detect_artifacts(final_answer, lang)
|
|
376
|
+
|
|
377
|
+
return final_answer, True
|
|
378
|
+
|
|
379
|
+
# ---------- 工具调用解析 ----------
|
|
380
|
+
|
|
381
|
+
@staticmethod
|
|
382
|
+
def _extract_tool_calls(text):
|
|
383
|
+
"""
|
|
384
|
+
从 assistant 回复中提取工具调用。
|
|
385
|
+
同时支持两种格式:
|
|
386
|
+
1. ```tool 代码块(MasterAgent 原生格式)
|
|
387
|
+
2. 【调用:tool_name({...})】(传统流式对话兼容格式)
|
|
388
|
+
"""
|
|
389
|
+
calls = []
|
|
390
|
+
|
|
391
|
+
# 格式 1:```tool 代码块
|
|
392
|
+
pattern = r'```tool\s*\n(.*?)\n```'
|
|
393
|
+
for m in re.finditer(pattern, text, re.DOTALL):
|
|
394
|
+
try:
|
|
395
|
+
data = json.loads(m.group(1).strip())
|
|
396
|
+
if "tool" in data:
|
|
397
|
+
calls.append(data)
|
|
398
|
+
except Exception:
|
|
399
|
+
pass
|
|
400
|
+
|
|
401
|
+
# 格式 2:【调用:tool_name({...})】(兼容传统模式)
|
|
402
|
+
i = 0
|
|
403
|
+
while True:
|
|
404
|
+
start = text.find('【调用:', i)
|
|
405
|
+
if start == -1:
|
|
406
|
+
break
|
|
407
|
+
paren = text.find('(', start)
|
|
408
|
+
if paren == -1:
|
|
409
|
+
break
|
|
410
|
+
tool_name = text[start + 4:paren].strip()
|
|
411
|
+
# 匹配嵌套括号
|
|
412
|
+
depth = 1
|
|
413
|
+
end = paren + 1
|
|
414
|
+
while end < len(text) and depth > 0:
|
|
415
|
+
if text[end] == '(' and (end == 0 or text[end - 1] != '\\'):
|
|
416
|
+
depth += 1
|
|
417
|
+
elif text[end] == ')' and (end == 0 or text[end - 1] != '\\'):
|
|
418
|
+
depth -= 1
|
|
419
|
+
end += 1
|
|
420
|
+
if depth != 0:
|
|
421
|
+
break
|
|
422
|
+
arg_str = text[paren + 1:end - 1]
|
|
423
|
+
try:
|
|
424
|
+
params = json.loads(arg_str)
|
|
425
|
+
calls.append({"tool": tool_name, "params": params})
|
|
426
|
+
except Exception:
|
|
427
|
+
pass
|
|
428
|
+
i = end
|
|
429
|
+
|
|
430
|
+
return calls
|
|
431
|
+
|
|
432
|
+
def _execute_tool(self, tool_name, params):
|
|
433
|
+
"""通过注册表执行工具"""
|
|
434
|
+
from fr_cli.command.executor import _build_deps
|
|
435
|
+
from fr_cli.command.registry import get_registry
|
|
436
|
+
reg = get_registry()
|
|
437
|
+
deps = _build_deps(self.state)
|
|
438
|
+
return reg.dispatch(deps, tool_name, **params)
|
|
439
|
+
|
|
440
|
+
# ---------- 记忆与进化 ----------
|
|
441
|
+
|
|
442
|
+
def _record_interaction(self, user_input, tool_name, success, detail):
|
|
443
|
+
"""记录单次交互到内存"""
|
|
444
|
+
self.memory.setdefault("interactions", []).append({
|
|
445
|
+
"time": datetime.now().isoformat(),
|
|
446
|
+
"input": user_input[:100],
|
|
447
|
+
"tool": tool_name,
|
|
448
|
+
"success": success,
|
|
449
|
+
"detail": detail[:200],
|
|
450
|
+
})
|
|
451
|
+
# 只保留最近 100 条
|
|
452
|
+
self.memory["interactions"] = self.memory["interactions"][-100:]
|
|
453
|
+
_save_json(MEMORY_FILE, self.memory)
|
|
454
|
+
|
|
455
|
+
def _get_recent_memory(self):
|
|
456
|
+
"""获取最近 5 条关键记忆摘要"""
|
|
457
|
+
interactions = self.memory.get("interactions", [])
|
|
458
|
+
if not interactions:
|
|
459
|
+
return ""
|
|
460
|
+
recent = interactions[-5:]
|
|
461
|
+
lines = []
|
|
462
|
+
for item in recent:
|
|
463
|
+
status = "✅" if item["success"] else "❌"
|
|
464
|
+
lines.append(f"{status} [{item['tool']}] {item['input']} → {item['detail'][:80]}")
|
|
465
|
+
return "\n".join(lines)
|
|
466
|
+
|
|
467
|
+
def _reflect_and_evolve(self, task, observations, result):
|
|
468
|
+
"""反思并触发自我进化(仅在积累足够数据时执行)"""
|
|
469
|
+
interactions = self.memory.get("interactions", [])
|
|
470
|
+
if len(interactions) < 5:
|
|
471
|
+
return
|
|
472
|
+
|
|
473
|
+
# 每 10 次交互触发一次进化
|
|
474
|
+
if len(interactions) % 10 != 0:
|
|
475
|
+
return
|
|
476
|
+
|
|
477
|
+
# 统计成功/失败模式
|
|
478
|
+
success_patterns = {}
|
|
479
|
+
failure_patterns = {}
|
|
480
|
+
for item in interactions[-50:]:
|
|
481
|
+
tool = item["tool"]
|
|
482
|
+
if item["success"]:
|
|
483
|
+
success_patterns[tool] = success_patterns.get(tool, 0) + 1
|
|
484
|
+
else:
|
|
485
|
+
failure_patterns[tool] = failure_patterns.get(tool, 0) + 1
|
|
486
|
+
|
|
487
|
+
# 更新进化数据
|
|
488
|
+
self.evolution["success"] = sorted(success_patterns.items(), key=lambda x: x[1], reverse=True)[:5]
|
|
489
|
+
self.evolution["failure"] = sorted(failure_patterns.items(), key=lambda x: x[1], reverse=True)[:5]
|
|
490
|
+
|
|
491
|
+
# 生成进化提示词追加
|
|
492
|
+
from fr_cli.agent.master_prompt import SELF_EVOLVE_PROMPT_ZH
|
|
493
|
+
prompt = SELF_EVOLVE_PROMPT_ZH.format(
|
|
494
|
+
success_patterns="\n".join(f"- {k}: {v}次" for k, v in self.evolution["success"]),
|
|
495
|
+
failure_patterns="\n".join(f"- {k}: {v}次" for k, v in self.evolution["failure"]),
|
|
496
|
+
)
|
|
497
|
+
|
|
498
|
+
messages = [{"role": "user", "content": prompt}]
|
|
499
|
+
from fr_cli.core.stream import stream_cnt
|
|
500
|
+
addon, _, _ = stream_cnt(
|
|
501
|
+
self.state.client, self.state.model_name, messages, self.state.lang,
|
|
502
|
+
custom_prefix="", max_tokens=512, silent=True
|
|
503
|
+
)
|
|
504
|
+
addon = addon.strip()
|
|
505
|
+
if addon and len(addon) < 500:
|
|
506
|
+
self.evolution["prompt_addon"] = addon
|
|
507
|
+
_save_json(EVOLUTION_FILE, self.evolution)
|
|
508
|
+
|
|
509
|
+
# 更新进化计数
|
|
510
|
+
self._status_data["evolution_count"] = self._status_data.get("evolution_count", 0) + 1
|
|
511
|
+
_save_json(STATUS_FILE, self._status_data)
|
|
512
|
+
|
|
513
|
+
# ---------- 法宝 / Agent 自动检测 ----------
|
|
514
|
+
|
|
515
|
+
def _detect_artifacts(self, txt, lang):
|
|
516
|
+
"""检测 AI 回复中的插件/Agent 代码结构,提示用户保存"""
|
|
517
|
+
if not txt:
|
|
518
|
+
return
|
|
519
|
+
|
|
520
|
+
# 智能法宝进化检测(插件)
|
|
521
|
+
if "def run(args='')" in txt and "```python" in txt:
|
|
522
|
+
code = extract_code(txt)
|
|
523
|
+
if code and "def run" in code and len(code) > 50:
|
|
524
|
+
try:
|
|
525
|
+
pname = input(f"{YELLOW}{T('artifact_detect', lang)}{RESET}").strip()
|
|
526
|
+
if pname:
|
|
527
|
+
safe_name = "".join(c for c in pname if c.isalnum() or c == '_')
|
|
528
|
+
if not safe_name:
|
|
529
|
+
print(f"{RED}名称无效,仅允许字母/数字/下划线{RESET}")
|
|
530
|
+
elif self.state.security.check("sec_write", f"/{safe_name}"):
|
|
531
|
+
PLUGIN_DIR.mkdir(parents=True, exist_ok=True)
|
|
532
|
+
p_path = PLUGIN_DIR / f"{safe_name}.py"
|
|
533
|
+
p_path.write_text(code, encoding='utf-8')
|
|
534
|
+
self.state.plugins[safe_name] = str(p_path)
|
|
535
|
+
print(f"{GREEN}{T('ok_forged', lang, safe_name)}{RESET}")
|
|
536
|
+
except EOFError:
|
|
537
|
+
pass
|
|
538
|
+
|
|
539
|
+
# 智能 Agent 分身检测
|
|
540
|
+
if "def run(context," in txt and "```python" in txt:
|
|
541
|
+
code = extract_code(txt)
|
|
542
|
+
if code and "def run(context," in code and len(code) > 50:
|
|
543
|
+
try:
|
|
544
|
+
aname = input(f"{YELLOW}⚡ 检测到 Agent 分身结构,赐名 (回车放弃): {RESET}").strip()
|
|
545
|
+
if aname:
|
|
546
|
+
safe_name = "".join(c for c in aname if c.isalnum() or c == '_')
|
|
547
|
+
if not safe_name:
|
|
548
|
+
print(f"{RED}名称无效,仅允许字母/数字/下划线{RESET}")
|
|
549
|
+
else:
|
|
550
|
+
from fr_cli.agent.manager import create_agent_dir, save_agent_code, save_persona, save_skills, agent_exists
|
|
551
|
+
if agent_exists(safe_name):
|
|
552
|
+
confirm = input(f"{YELLOW}Agent [{safe_name}] 已存在,是否覆盖? [y/N]: {RESET}").strip().lower()
|
|
553
|
+
if confirm not in ("y", "yes"):
|
|
554
|
+
print(f"{DIM}已取消。{RESET}")
|
|
555
|
+
else:
|
|
556
|
+
d = create_agent_dir(safe_name)
|
|
557
|
+
save_agent_code(safe_name, code)
|
|
558
|
+
print(f"{GREEN}✅ Agent [{safe_name}] 已覆盖更新。{RESET}")
|
|
559
|
+
print(f"{DIM} 路径: {d}{RESET}")
|
|
560
|
+
else:
|
|
561
|
+
d = create_agent_dir(safe_name)
|
|
562
|
+
save_agent_code(safe_name, code)
|
|
563
|
+
save_persona(safe_name, f"#{safe_name}\n\n由 AI 对话铸造的 Agent 分身。")
|
|
564
|
+
save_skills(safe_name, "## 技能\n\n- 执行自定义 Python 逻辑\n- 入口: run(context, **kwargs)")
|
|
565
|
+
print(f"{GREEN}✅ Agent [{safe_name}] 铸造完成!{RESET}")
|
|
566
|
+
print(f"{DIM} 路径: {d}{RESET}")
|
|
567
|
+
print(f"{DIM} 运行: /agent_run {safe_name} [参数]{RESET}")
|
|
568
|
+
except EOFError:
|
|
569
|
+
pass
|
|
570
|
+
|
|
571
|
+
# ---------- 状态管理 ----------
|
|
572
|
+
|
|
573
|
+
def toggle(self, enabled=None):
|
|
574
|
+
"""启用/禁用 MasterAgent"""
|
|
575
|
+
if enabled is None:
|
|
576
|
+
enabled = not self._status_data.get("enabled", False)
|
|
577
|
+
self._status_data["enabled"] = enabled
|
|
578
|
+
# 同步到配置文件(兼容旧逻辑)
|
|
579
|
+
self.state.cfg["master_agent_enabled"] = enabled
|
|
580
|
+
self.state.save_cfg()
|
|
581
|
+
_save_json(STATUS_FILE, self._status_data)
|
|
582
|
+
return enabled
|
|
583
|
+
|
|
584
|
+
def is_enabled(self):
|
|
585
|
+
# 优先从 status.json 读取,兼容旧配置
|
|
586
|
+
return self._status_data.get("enabled", self.state.cfg.get("master_agent_enabled", False))
|
|
587
|
+
|
|
588
|
+
def status(self):
|
|
589
|
+
"""返回当前状态摘要"""
|
|
590
|
+
total = len(self.memory.get("interactions", []))
|
|
591
|
+
success = sum(1 for i in self.memory.get("interactions", []) if i["success"])
|
|
592
|
+
failure = total - success
|
|
593
|
+
addon = self.evolution.get("prompt_addon", "")[:80]
|
|
594
|
+
return {
|
|
595
|
+
"enabled": self.is_enabled(),
|
|
596
|
+
"total_interactions": total,
|
|
597
|
+
"success": success,
|
|
598
|
+
"failure": failure,
|
|
599
|
+
"evolution_count": self._status_data.get("evolution_count", 0),
|
|
600
|
+
"evolution_addon": addon + "..." if len(addon) > 80 else addon,
|
|
601
|
+
"last_active": self._status_data.get("last_active"),
|
|
602
|
+
"created_at": self._status_data.get("created_at"),
|
|
603
|
+
"current_task": self.session.get("current_task"),
|
|
604
|
+
}
|