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.
Files changed (64) hide show
  1. fr_cli/README.md +148 -0
  2. fr_cli/WEAPON.MD +186 -0
  3. fr_cli/__init__.py +4 -0
  4. fr_cli/addon/plugin.py +69 -0
  5. fr_cli/agent/__init__.py +9 -0
  6. fr_cli/agent/builtins/__init__.py +4 -0
  7. fr_cli/agent/builtins/_utils.py +48 -0
  8. fr_cli/agent/builtins/db.py +269 -0
  9. fr_cli/agent/builtins/local.py +105 -0
  10. fr_cli/agent/builtins/rag.py +652 -0
  11. fr_cli/agent/builtins/rag_watcher_daemon.py +156 -0
  12. fr_cli/agent/builtins/remote.py +214 -0
  13. fr_cli/agent/builtins/spider.py +247 -0
  14. fr_cli/agent/client.py +164 -0
  15. fr_cli/agent/executor.py +86 -0
  16. fr_cli/agent/generator.py +104 -0
  17. fr_cli/agent/manager.py +193 -0
  18. fr_cli/agent/master.py +604 -0
  19. fr_cli/agent/master_prompt.py +118 -0
  20. fr_cli/agent/remote.py +70 -0
  21. fr_cli/agent/server.py +279 -0
  22. fr_cli/agent/workflow.py +164 -0
  23. fr_cli/breakthrough/update.py +154 -0
  24. fr_cli/command/__init__.py +4 -0
  25. fr_cli/command/executor.py +276 -0
  26. fr_cli/command/registry.py +1034 -0
  27. fr_cli/command/security.py +30 -0
  28. fr_cli/conf/config.py +126 -0
  29. fr_cli/conf/wizard.py +172 -0
  30. fr_cli/core/chat.py +280 -0
  31. fr_cli/core/core.py +111 -0
  32. fr_cli/core/intent.py +129 -0
  33. fr_cli/core/recommender.py +71 -0
  34. fr_cli/core/stream.py +83 -0
  35. fr_cli/core/sysmon.py +117 -0
  36. fr_cli/core/thinking.py +215 -0
  37. fr_cli/gatekeeper/__init__.py +7 -0
  38. fr_cli/gatekeeper/daemon.py +216 -0
  39. fr_cli/gatekeeper/manager.py +218 -0
  40. fr_cli/lang/i18n.py +827 -0
  41. fr_cli/main.py +329 -0
  42. fr_cli/memory/context.py +119 -0
  43. fr_cli/memory/history.py +96 -0
  44. fr_cli/memory/session.py +134 -0
  45. fr_cli/repl/__init__.py +0 -0
  46. fr_cli/repl/commands.py +1098 -0
  47. fr_cli/security/security.py +46 -0
  48. fr_cli/ui/ui.py +116 -0
  49. fr_cli/weapon/cron.py +217 -0
  50. fr_cli/weapon/dataframe.py +97 -0
  51. fr_cli/weapon/disk.py +141 -0
  52. fr_cli/weapon/fs.py +206 -0
  53. fr_cli/weapon/launcher.py +249 -0
  54. fr_cli/weapon/loader.py +98 -0
  55. fr_cli/weapon/mail.py +227 -0
  56. fr_cli/weapon/mcp.py +204 -0
  57. fr_cli/weapon/vision.py +74 -0
  58. fr_cli/weapon/web.py +88 -0
  59. fr_cli-2.1.0.dist-info/METADATA +227 -0
  60. fr_cli-2.1.0.dist-info/RECORD +64 -0
  61. fr_cli-2.1.0.dist-info/WHEEL +5 -0
  62. fr_cli-2.1.0.dist-info/entry_points.txt +2 -0
  63. fr_cli-2.1.0.dist-info/licenses/LICENSE +21 -0
  64. 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
@@ -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
+ }
@@ -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 []