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/client.py
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agent API 客户端 —— 支持本地调用和远程 HTTP API 调用
|
|
3
|
+
MasterAgent 通过此模块调用其他独立 Agent(本地或远程)
|
|
4
|
+
"""
|
|
5
|
+
import json
|
|
6
|
+
import urllib.request
|
|
7
|
+
import urllib.error
|
|
8
|
+
|
|
9
|
+
from fr_cli.agent.executor import run_agent
|
|
10
|
+
from fr_cli.agent.remote import list_remote_agents, get_remote_agent, add_remote_agent
|
|
11
|
+
from fr_cli.agent.manager import list_agents as list_local_agents
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def discover_all_agents():
|
|
15
|
+
"""
|
|
16
|
+
发现所有可用 Agent:本地 + 远程
|
|
17
|
+
返回列表: [{"name": str, "type": "local|remote", "description": str}]
|
|
18
|
+
"""
|
|
19
|
+
results = []
|
|
20
|
+
|
|
21
|
+
# 本地 Agent
|
|
22
|
+
for a in list_local_agents():
|
|
23
|
+
results.append({
|
|
24
|
+
"name": a["name"],
|
|
25
|
+
"type": "local",
|
|
26
|
+
"description": f"本地Agent (persona:{a['has_persona']}, memory:{a['has_memory']}, skills:{a['has_skills']})",
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
# 远程 Agent
|
|
30
|
+
for name, cfg in list_remote_agents().items():
|
|
31
|
+
results.append({
|
|
32
|
+
"name": name,
|
|
33
|
+
"type": "remote",
|
|
34
|
+
"description": cfg.get("description", f"远程Agent @ {cfg['host']}:{cfg['port']}"),
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
return results
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def call_agent(name, state, user_input="", **kwargs):
|
|
41
|
+
"""
|
|
42
|
+
统一入口:调用 Agent(自动判断本地或远程)
|
|
43
|
+
返回 (result, error)
|
|
44
|
+
"""
|
|
45
|
+
# 优先检查本地 Agent
|
|
46
|
+
from fr_cli.agent.manager import agent_exists
|
|
47
|
+
if agent_exists(name):
|
|
48
|
+
return run_agent(name, state, user_input=user_input, **kwargs)
|
|
49
|
+
|
|
50
|
+
# 检查远程 Agent
|
|
51
|
+
remote_cfg = get_remote_agent(name)
|
|
52
|
+
if remote_cfg:
|
|
53
|
+
return call_remote_agent(name, user_input, remote_cfg)
|
|
54
|
+
|
|
55
|
+
return None, f"Agent [{name}] 未找到(本地和远程均无此Agent)"
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def call_remote_agent(name, user_input, cfg):
|
|
59
|
+
"""
|
|
60
|
+
通过 HTTP API 调用远程 Agent
|
|
61
|
+
cfg: {"host": str, "port": int, "token": str}
|
|
62
|
+
返回 (result, error)
|
|
63
|
+
"""
|
|
64
|
+
host = cfg.get("host", "127.0.0.1")
|
|
65
|
+
port = cfg.get("port", 17890)
|
|
66
|
+
token = cfg.get("token", "")
|
|
67
|
+
|
|
68
|
+
url = f"http://{host}:{port}/agents/{name}/run"
|
|
69
|
+
payload = json.dumps({"input": user_input, "kwargs": {}}).encode("utf-8")
|
|
70
|
+
|
|
71
|
+
req = urllib.request.Request(
|
|
72
|
+
url,
|
|
73
|
+
data=payload,
|
|
74
|
+
headers={
|
|
75
|
+
"Content-Type": "application/json",
|
|
76
|
+
"Authorization": f"Bearer {token}",
|
|
77
|
+
},
|
|
78
|
+
method="POST",
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
with urllib.request.urlopen(req, timeout=30) as resp:
|
|
83
|
+
data = json.loads(resp.read().decode("utf-8"))
|
|
84
|
+
if data.get("error"):
|
|
85
|
+
return None, f"远程Agent错误: {data['error']}"
|
|
86
|
+
return data.get("result", ""), None
|
|
87
|
+
except urllib.error.HTTPError as e:
|
|
88
|
+
err_body = e.read().decode("utf-8") if hasattr(e, "read") else str(e)
|
|
89
|
+
return None, f"远程Agent HTTP {e.code}: {err_body}"
|
|
90
|
+
except urllib.error.URLError as e:
|
|
91
|
+
return None, f"远程Agent连接失败: {e.reason}"
|
|
92
|
+
except Exception as e:
|
|
93
|
+
return None, f"远程Agent调用异常: {e}"
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def scan_remote_host(host, port, token):
|
|
97
|
+
"""
|
|
98
|
+
扫描远程主机,获取其提供的 Agent 列表和服务能力
|
|
99
|
+
返回 ({"service": ..., "agents": [...]}, error)
|
|
100
|
+
"""
|
|
101
|
+
# 1. 获取能力声明
|
|
102
|
+
cap_url = f"http://{host}:{port}/capabilities"
|
|
103
|
+
req = urllib.request.Request(
|
|
104
|
+
cap_url,
|
|
105
|
+
headers={"Authorization": f"Bearer {token}"},
|
|
106
|
+
)
|
|
107
|
+
try:
|
|
108
|
+
with urllib.request.urlopen(req, timeout=10) as resp:
|
|
109
|
+
caps = json.loads(resp.read().decode("utf-8"))
|
|
110
|
+
except Exception as e:
|
|
111
|
+
return None, f"无法获取远程能力声明: {e}"
|
|
112
|
+
|
|
113
|
+
# 2. 获取 Agent 列表
|
|
114
|
+
agents_url = f"http://{host}:{port}/agents"
|
|
115
|
+
req = urllib.request.Request(
|
|
116
|
+
agents_url,
|
|
117
|
+
headers={"Authorization": f"Bearer {token}"},
|
|
118
|
+
)
|
|
119
|
+
try:
|
|
120
|
+
with urllib.request.urlopen(req, timeout=10) as resp:
|
|
121
|
+
agents_data = json.loads(resp.read().decode("utf-8"))
|
|
122
|
+
except Exception as e:
|
|
123
|
+
return None, f"无法获取远程Agent列表: {e}"
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
"service": caps.get("service", "unknown"),
|
|
127
|
+
"version": caps.get("version", "unknown"),
|
|
128
|
+
"agents": agents_data.get("agents", []),
|
|
129
|
+
"endpoints": caps.get("endpoints", {}),
|
|
130
|
+
"host": host,
|
|
131
|
+
"port": port,
|
|
132
|
+
"token": token,
|
|
133
|
+
}, None
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def import_remote_agents(host, port, token, prefix=""):
|
|
137
|
+
"""
|
|
138
|
+
一键导入远程主机的所有 Agent 到本地配置
|
|
139
|
+
prefix: 可选前缀,避免与本地Agent重名
|
|
140
|
+
返回 (imported_count, errors)
|
|
141
|
+
"""
|
|
142
|
+
info, err = scan_remote_host(host, port, token)
|
|
143
|
+
if err:
|
|
144
|
+
return 0, [err]
|
|
145
|
+
|
|
146
|
+
imported = 0
|
|
147
|
+
errors = []
|
|
148
|
+
for agent in info.get("agents", []):
|
|
149
|
+
name = agent["name"]
|
|
150
|
+
if prefix:
|
|
151
|
+
name = f"{prefix}_{name}"
|
|
152
|
+
try:
|
|
153
|
+
add_remote_agent(
|
|
154
|
+
name,
|
|
155
|
+
host,
|
|
156
|
+
port,
|
|
157
|
+
token,
|
|
158
|
+
description=f"远程Agent [{agent['name']}] @ {host}:{port}",
|
|
159
|
+
)
|
|
160
|
+
imported += 1
|
|
161
|
+
except Exception as e:
|
|
162
|
+
errors.append(f"导入 {name} 失败: {e}")
|
|
163
|
+
|
|
164
|
+
return imported, errors
|
fr_cli/agent/executor.py
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Agent executor
|
|
2
|
+
from fr_cli.agent.manager import (load_persona, load_memory, load_skills, load_agent_module, agent_exists, load_progress)
|
|
3
|
+
from fr_cli.agent.workflow import run_workflow, load_workflow
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def run_agent(name, state, **kwargs):
|
|
7
|
+
if not agent_exists(name):
|
|
8
|
+
return None, "Agent not found. Use /agent_create <name> <description>"
|
|
9
|
+
if load_workflow(name):
|
|
10
|
+
result, err, _ = run_workflow(name, state, user_input=kwargs.get("pipeline_input"), **kwargs)
|
|
11
|
+
return result, err
|
|
12
|
+
persona = load_persona(name)
|
|
13
|
+
memory = load_memory(name)
|
|
14
|
+
skills = load_skills(name)
|
|
15
|
+
mod = load_agent_module(name)
|
|
16
|
+
if mod is None:
|
|
17
|
+
return None, "agent.py not found or load failed"
|
|
18
|
+
if not hasattr(mod, "run"):
|
|
19
|
+
return None, "agent.py missing run(context, **kwargs)"
|
|
20
|
+
progress = load_progress(name)
|
|
21
|
+
latest = progress.get("latest", {})
|
|
22
|
+
context = {
|
|
23
|
+
"persona": persona,
|
|
24
|
+
"memory": memory,
|
|
25
|
+
"skills": skills,
|
|
26
|
+
"client": state.client,
|
|
27
|
+
"model": state.model_name,
|
|
28
|
+
"lang": state.lang,
|
|
29
|
+
"executor": state.executor,
|
|
30
|
+
"state": state,
|
|
31
|
+
"agent_name": name,
|
|
32
|
+
"progress": progress,
|
|
33
|
+
"latest_result": latest.get("result", ""),
|
|
34
|
+
"latest_status": latest.get("status", ""),
|
|
35
|
+
"execution_count": progress.get("counter", 0),
|
|
36
|
+
}
|
|
37
|
+
try:
|
|
38
|
+
result = mod.run(context, **kwargs)
|
|
39
|
+
return result, None
|
|
40
|
+
except Exception as e:
|
|
41
|
+
return None, str(e)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def delegate_to_agent(name, state, pipeline_input=None, **kwargs):
|
|
45
|
+
"""将请求委托给指定 Agent 执行,支持管道输入(pipeline_input)供多 Agent 协作链使用。"""
|
|
46
|
+
if not agent_exists(name):
|
|
47
|
+
return None, f"Agent not found: {name}"
|
|
48
|
+
persona = load_persona(name)
|
|
49
|
+
memory = load_memory(name)
|
|
50
|
+
skills = load_skills(name)
|
|
51
|
+
mod = load_agent_module(name)
|
|
52
|
+
if mod is None:
|
|
53
|
+
return None, "agent.py not found or load failed"
|
|
54
|
+
if not hasattr(mod, "run"):
|
|
55
|
+
return None, "agent.py missing run(context, **kwargs)"
|
|
56
|
+
context = {
|
|
57
|
+
"persona": persona,
|
|
58
|
+
"memory": memory,
|
|
59
|
+
"skills": skills,
|
|
60
|
+
"client": state.client,
|
|
61
|
+
"model": state.model_name,
|
|
62
|
+
"lang": state.lang,
|
|
63
|
+
"executor": state.executor,
|
|
64
|
+
"state": state,
|
|
65
|
+
"agent_name": name,
|
|
66
|
+
"pipeline_input": pipeline_input,
|
|
67
|
+
}
|
|
68
|
+
try:
|
|
69
|
+
result = mod.run(context, **kwargs)
|
|
70
|
+
return result, None
|
|
71
|
+
except Exception as e:
|
|
72
|
+
return None, str(e)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def run_multi_agent(names, state, initial_input=None, **kwargs):
|
|
76
|
+
"""多 Agent 流水线协作 —— 将多个 Agent 串联执行,前一个的输出作为后一个的输入。"""
|
|
77
|
+
pipeline_result = initial_input
|
|
78
|
+
logs = []
|
|
79
|
+
for idx, name in enumerate(names, start=1):
|
|
80
|
+
print(f"[流水线] {idx}/{len(names)}: 运行 Agent {name}")
|
|
81
|
+
result, err = delegate_to_agent(name, state, pipeline_input=pipeline_result, **kwargs)
|
|
82
|
+
if err:
|
|
83
|
+
return None, f"Pipeline step {idx} ('{name}'): {err}"
|
|
84
|
+
logs.append({"agent": name, "result": result})
|
|
85
|
+
pipeline_result = result
|
|
86
|
+
return logs, None
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agent 生成器 —— 分身铸造炉
|
|
3
|
+
利用大模型能力,根据用户需求自动生成完整的 Agent(人设、技能、代码)
|
|
4
|
+
"""
|
|
5
|
+
import sys
|
|
6
|
+
from fr_cli.core.stream import stream_cnt
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
GENERATION_PROMPT_ZH = """你是 Agent 架构师。请根据以下需求,为一个新的 AI Agent 分身设计完整的设定和代码。
|
|
10
|
+
|
|
11
|
+
Agent 名称: {name}
|
|
12
|
+
需求描述: {description}
|
|
13
|
+
|
|
14
|
+
请严格按照以下格式输出(保持三个标记之间的顺序):
|
|
15
|
+
|
|
16
|
+
---PERSONA_START---
|
|
17
|
+
(在这里写人设设定,用 Markdown 格式。包括:角色定位、性格特点、行为准则、语气风格)
|
|
18
|
+
---PERSONA_END---
|
|
19
|
+
|
|
20
|
+
---SKILLS_START---
|
|
21
|
+
(在这里写可用技能,用 Markdown 格式。列出 Agent 可以使用的工具和能力,每项技能包含名称、描述和调用方式)
|
|
22
|
+
---SKILLS_END---
|
|
23
|
+
|
|
24
|
+
---CODE_START---
|
|
25
|
+
```python
|
|
26
|
+
(在这里写完整的 Python 代码)
|
|
27
|
+
```
|
|
28
|
+
---CODE_END---
|
|
29
|
+
|
|
30
|
+
对 Python 代码的要求:
|
|
31
|
+
1. 必须包含 `def run(context, **kwargs):` 作为唯一入口函数
|
|
32
|
+
2. `context` 是一个字典,包含以下键:
|
|
33
|
+
- 'persona': str — 人设文本
|
|
34
|
+
- 'memory': str — 记忆文本
|
|
35
|
+
- 'skills': str — 技能文本
|
|
36
|
+
- 'client': ZhipuAI 实例
|
|
37
|
+
- 'model': str — 模型名称
|
|
38
|
+
- 'lang': str — 语言代码('zh' 或 'en')
|
|
39
|
+
- 'executor': CommandExecutor 实例(可使用 invoke_tool/execute 调用工具)
|
|
40
|
+
- 'state': AppState 实例(可访问 vfs、cfg 等子系统)
|
|
41
|
+
3. `kwargs` 包含用户调用时传入的参数
|
|
42
|
+
4. 函数返回 str 类型的执行结果
|
|
43
|
+
5. 代码要健壮,有异常处理
|
|
44
|
+
6. 使用中文注释
|
|
45
|
+
7. 充分利用 context 中的资源完成用户需求
|
|
46
|
+
8. 如果需要调用 AI,使用 context['client'].chat.completions.create()
|
|
47
|
+
9. 如果需要操作文件,使用 context['state'].vfs
|
|
48
|
+
10. 如果需要调用工具,使用 context['executor'].invoke_tool() 或 context['executor'].execute()
|
|
49
|
+
|
|
50
|
+
请确保输出中包含完整的三个部分(PERSONA、SKILLS、CODE),缺一不可。
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _extract_section(text: str, start_marker: str, end_marker: str) -> str:
|
|
55
|
+
"""从 AI 回复中提取标记之间的内容"""
|
|
56
|
+
s = text.find(start_marker)
|
|
57
|
+
e = text.find(end_marker)
|
|
58
|
+
if s == -1 or e == -1 or e <= s:
|
|
59
|
+
return ""
|
|
60
|
+
return text[s + len(start_marker):e].strip()
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _clean_code_block(code: str) -> str:
|
|
64
|
+
"""去除代码块标记 ```python ... ```"""
|
|
65
|
+
code = code.strip()
|
|
66
|
+
if code.startswith("```python"):
|
|
67
|
+
code = code[len("```python"):].strip()
|
|
68
|
+
elif code.startswith("```"):
|
|
69
|
+
code = code[len("```"):].strip()
|
|
70
|
+
if code.endswith("```"):
|
|
71
|
+
code = code[:-3].strip()
|
|
72
|
+
return code
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def generate_agent(client, model, name: str, description: str, lang: str = "zh") -> dict:
|
|
76
|
+
"""
|
|
77
|
+
调用大模型生成完整的 Agent。
|
|
78
|
+
返回 {"persona": str, "skills": str, "code": str, "raw": str}
|
|
79
|
+
"""
|
|
80
|
+
prompt = GENERATION_PROMPT_ZH.format(name=name, description=description)
|
|
81
|
+
messages = [{"role": "user", "content": prompt}]
|
|
82
|
+
|
|
83
|
+
sys.stdout.write("🧙 正在铸造分身... ")
|
|
84
|
+
sys.stdout.flush()
|
|
85
|
+
raw, _, _ = stream_cnt(client, model, messages, lang, custom_prefix="", max_tokens=4096)
|
|
86
|
+
|
|
87
|
+
persona = _extract_section(raw, "---PERSONA_START---", "---PERSONA_END---")
|
|
88
|
+
skills = _extract_section(raw, "---SKILLS_START---", "---SKILLS_END---")
|
|
89
|
+
code = _extract_section(raw, "---CODE_START---", "---CODE_END---")
|
|
90
|
+
code = _clean_code_block(code)
|
|
91
|
+
|
|
92
|
+
# 如果没有提取到代码,尝试从原始回复中直接找代码块
|
|
93
|
+
if not code and "```python" in raw:
|
|
94
|
+
s = raw.find("```python")
|
|
95
|
+
e = raw.find("```", s + 1)
|
|
96
|
+
if e > s:
|
|
97
|
+
code = _clean_code_block(raw[s:e + 3])
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
"persona": persona,
|
|
101
|
+
"skills": skills,
|
|
102
|
+
"code": code,
|
|
103
|
+
"raw": raw,
|
|
104
|
+
}
|
fr_cli/agent/manager.py
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agent 管理器 —— 分身掌管者
|
|
3
|
+
负责 Agent 目录的创建、删除、列出,以及设定的读写
|
|
4
|
+
"""
|
|
5
|
+
import shutil
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from fr_cli.agent import AGENTS_DIR
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
PERSONA_FILE = "persona.md"
|
|
11
|
+
MEMORY_FILE = "memory.md"
|
|
12
|
+
SKILLS_FILE = "skills.md"
|
|
13
|
+
AGENT_CODE_FILE = "agent.py"
|
|
14
|
+
PROGRESS_FILE = "progress.json"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _agent_dir(name: str) -> Path:
|
|
18
|
+
"""获取指定 Agent 的洞府路径"""
|
|
19
|
+
safe_name = "".join(c for c in name if c.isalnum() or c in ("_", "-"))
|
|
20
|
+
if not safe_name:
|
|
21
|
+
safe_name = "unnamed"
|
|
22
|
+
return AGENTS_DIR / safe_name
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def ensure_agents_dir():
|
|
26
|
+
"""确保 Agents 总洞府存在"""
|
|
27
|
+
AGENTS_DIR.mkdir(parents=True, exist_ok=True)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def agent_exists(name: str) -> bool:
|
|
31
|
+
"""检查分身是否已存在"""
|
|
32
|
+
return _agent_dir(name).exists()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def create_agent_dir(name: str) -> Path:
|
|
36
|
+
"""为新的 Agent 开辟独立洞府"""
|
|
37
|
+
ensure_agents_dir()
|
|
38
|
+
d = _agent_dir(name)
|
|
39
|
+
d.mkdir(parents=True, exist_ok=True)
|
|
40
|
+
return d
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def list_agents() -> list:
|
|
44
|
+
"""列出所有已创建的分身"""
|
|
45
|
+
if not AGENTS_DIR.exists():
|
|
46
|
+
return []
|
|
47
|
+
agents = []
|
|
48
|
+
for d in AGENTS_DIR.iterdir():
|
|
49
|
+
if d.is_dir() and (d / AGENT_CODE_FILE).exists():
|
|
50
|
+
agents.append({
|
|
51
|
+
"name": d.name,
|
|
52
|
+
"path": str(d),
|
|
53
|
+
"has_persona": (d / PERSONA_FILE).exists(),
|
|
54
|
+
"has_memory": (d / MEMORY_FILE).exists(),
|
|
55
|
+
"has_skills": (d / SKILLS_FILE).exists(),
|
|
56
|
+
})
|
|
57
|
+
return agents
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def delete_agent(name: str) -> bool:
|
|
61
|
+
"""彻底抹除一个分身及其所有记忆"""
|
|
62
|
+
d = _agent_dir(name)
|
|
63
|
+
if not d.exists():
|
|
64
|
+
return False
|
|
65
|
+
shutil.rmtree(d)
|
|
66
|
+
return True
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
# ---------- 设定读写 ----------
|
|
70
|
+
|
|
71
|
+
def _read_md(agent_dir: Path, filename: str, default: str = "") -> str:
|
|
72
|
+
f = agent_dir / filename
|
|
73
|
+
if f.exists():
|
|
74
|
+
return f.read_text(encoding="utf-8")
|
|
75
|
+
return default
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _write_md(agent_dir: Path, filename: str, content: str):
|
|
79
|
+
f = agent_dir / filename
|
|
80
|
+
f.write_text(content, encoding="utf-8")
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def load_persona(name: str) -> str:
|
|
84
|
+
return _read_md(_agent_dir(name), PERSONA_FILE, "")
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def save_persona(name: str, content: str):
|
|
88
|
+
_write_md(_agent_dir(name), PERSONA_FILE, content)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def load_memory(name: str) -> str:
|
|
92
|
+
return _read_md(_agent_dir(name), MEMORY_FILE, "")
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def save_memory(name: str, content: str):
|
|
96
|
+
_write_md(_agent_dir(name), MEMORY_FILE, content)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def load_skills(name: str) -> str:
|
|
100
|
+
return _read_md(_agent_dir(name), SKILLS_FILE, "")
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def save_skills(name: str, content: str):
|
|
104
|
+
_write_md(_agent_dir(name), SKILLS_FILE, content)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def load_agent_code(name: str) -> str:
|
|
108
|
+
return _read_md(_agent_dir(name), AGENT_CODE_FILE, "")
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def save_agent_code(name: str, content: str):
|
|
112
|
+
_write_md(_agent_dir(name), AGENT_CODE_FILE, content)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
# ---------- 代码动态加载 ----------
|
|
116
|
+
|
|
117
|
+
def load_agent_module(name: str):
|
|
118
|
+
"""动态加载 Agent 的 Python 模块,返回模块对象"""
|
|
119
|
+
import importlib.util
|
|
120
|
+
agent_dir = _agent_dir(name)
|
|
121
|
+
code_path = agent_dir / AGENT_CODE_FILE
|
|
122
|
+
if not code_path.exists():
|
|
123
|
+
return None
|
|
124
|
+
spec = importlib.util.spec_from_file_location(f"fr_cli_agent_{name}", str(code_path))
|
|
125
|
+
mod = importlib.util.module_from_spec(spec)
|
|
126
|
+
spec.loader.exec_module(mod)
|
|
127
|
+
return mod
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
# ---------- 定时任务进度读写 ----------
|
|
131
|
+
|
|
132
|
+
import json
|
|
133
|
+
from datetime import datetime
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def load_progress(name: str) -> dict:
|
|
137
|
+
"""读取 Agent 的定时任务执行进度"""
|
|
138
|
+
f = _agent_dir(name) / PROGRESS_FILE
|
|
139
|
+
if not f.exists():
|
|
140
|
+
return {}
|
|
141
|
+
try:
|
|
142
|
+
return json.loads(f.read_text(encoding="utf-8"))
|
|
143
|
+
except Exception:
|
|
144
|
+
return {}
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def save_progress(name: str, data: dict):
|
|
148
|
+
"""保存 Agent 的定时任务执行进度"""
|
|
149
|
+
f = _agent_dir(name) / PROGRESS_FILE
|
|
150
|
+
try:
|
|
151
|
+
f.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
152
|
+
except Exception:
|
|
153
|
+
pass
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def append_progress(name: str, result: str, user_input: str = "", status: str = "success", max_history: int = 50):
|
|
157
|
+
"""追加一条执行记录到 Agent 进度文件
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
result: 执行结果摘要
|
|
161
|
+
user_input: 触发此次执行的输入(定时任务的 input 或用户输入)
|
|
162
|
+
status: success / error
|
|
163
|
+
max_history: 保留的最大历史记录数
|
|
164
|
+
"""
|
|
165
|
+
progress = load_progress(name)
|
|
166
|
+
entry = {
|
|
167
|
+
"timestamp": datetime.now().isoformat(),
|
|
168
|
+
"input": user_input,
|
|
169
|
+
"result": result,
|
|
170
|
+
"status": status,
|
|
171
|
+
}
|
|
172
|
+
history = progress.get("history", [])
|
|
173
|
+
history.append(entry)
|
|
174
|
+
# 保留最近 max_history 条
|
|
175
|
+
if len(history) > max_history:
|
|
176
|
+
history = history[-max_history:]
|
|
177
|
+
progress["history"] = history
|
|
178
|
+
progress["latest"] = entry
|
|
179
|
+
progress["counter"] = progress.get("counter", 0) + 1
|
|
180
|
+
save_progress(name, progress)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def get_latest_progress(name: str) -> dict:
|
|
184
|
+
"""获取 Agent 最近一次执行进度"""
|
|
185
|
+
progress = load_progress(name)
|
|
186
|
+
return progress.get("latest", {})
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def get_progress_history(name: str, limit: int = 10) -> list:
|
|
190
|
+
"""获取 Agent 执行历史记录"""
|
|
191
|
+
progress = load_progress(name)
|
|
192
|
+
history = progress.get("history", [])
|
|
193
|
+
return history[-limit:] if history else []
|