fr-cli 2.2.9__tar.gz → 2.3.1__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.9/fr_cli.egg-info → fr_cli-2.3.1}/PKG-INFO +1 -1
- fr_cli-2.3.1/fr_cli/agent/context_files.py +159 -0
- fr_cli-2.3.1/fr_cli/agent/gateway.py +233 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/agent/hermes.py +10 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/agent/master.py +16 -0
- fr_cli-2.3.1/fr_cli/agent/personality.py +236 -0
- fr_cli-2.3.1/fr_cli/agent/skills.py +226 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1/fr_cli.egg-info}/PKG-INFO +1 -1
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli.egg-info/SOURCES.txt +4 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/pyproject.toml +1 -1
- {fr_cli-2.2.9 → fr_cli-2.3.1}/LICENSE +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/MANIFEST.in +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/README.md +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/README.md +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/WEAPON.MD +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/__init__.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/addon/plugin.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/agent/__init__.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/agent/a2a.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/agent/acp.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/agent/builtins/__init__.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/agent/builtins/_utils.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/agent/builtins/db.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/agent/builtins/local.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/agent/builtins/powerful_agent_template.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/agent/builtins/rag.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/agent/builtins/rag_watcher_daemon.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/agent/builtins/remote.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/agent/builtins/spider.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/agent/client.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/agent/coding_helper.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/agent/executor.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/agent/generator.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/agent/image_and_parallel.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/agent/manager.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/agent/master_prompt.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/agent/plugin_system.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/agent/remote.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/agent/server.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/agent/shell_mode.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/agent/workflow.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/agent/workflow_system.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/breakthrough/update.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/command/__init__.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/command/executor.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/command/registry.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/command/security.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/conf/config.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/conf/wizard.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/core/chat.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/core/core.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/core/intent.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/core/llm.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/core/model_factory.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/core/recommender.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/core/stream.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/core/sysmon.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/core/thinking.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/gatekeeper/__init__.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/gatekeeper/daemon.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/gatekeeper/manager.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/lang/i18n.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/main.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/memory/context.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/memory/history.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/memory/session.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/repl/__init__.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/repl/commands.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/security/security.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/ui/ui.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/weapon/cron.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/weapon/dataframe.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/weapon/disk.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/weapon/fs.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/weapon/launcher.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/weapon/loader.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/weapon/mail.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/weapon/mcp.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/weapon/vision.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli/weapon/web.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli.egg-info/dependency_links.txt +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli.egg-info/entry_points.txt +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli.egg-info/requires.txt +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/fr_cli.egg-info/top_level.txt +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/setup.cfg +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/tests/test_a2a_and_providers.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/tests/test_integration_real.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/tests/test_master_prompt_fix.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/tests/test_model_config.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/tests/test_new_features.py +0 -0
- {fr_cli-2.2.9 → fr_cli-2.3.1}/tests/test_new_providers.py +0 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Context Files 系统 - 参考 Hermes Agent 实现
|
|
3
|
+
项目上下文文件,塑造每次对话
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import glob
|
|
8
|
+
from typing import List, Dict, Optional
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class ContextFile:
|
|
14
|
+
"""上下文文件"""
|
|
15
|
+
path: str
|
|
16
|
+
content: str = ""
|
|
17
|
+
size: int = 0
|
|
18
|
+
loaded: bool = False
|
|
19
|
+
|
|
20
|
+
def load_content(self):
|
|
21
|
+
"""加载文件内容"""
|
|
22
|
+
if not self.loaded and os.path.exists(self.path):
|
|
23
|
+
try:
|
|
24
|
+
with open(self.path, 'r', encoding='utf-8') as f:
|
|
25
|
+
self.content = f.read(100000) # 限制 100KB
|
|
26
|
+
self.size = len(self.content)
|
|
27
|
+
self.loaded = True
|
|
28
|
+
except Exception:
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ContextFilesManager:
|
|
33
|
+
"""上下文文件管理器"""
|
|
34
|
+
|
|
35
|
+
def __init__(self, config_dir: str = None):
|
|
36
|
+
if config_dir is None:
|
|
37
|
+
config_dir = os.path.expanduser("~/.fr_cli")
|
|
38
|
+
self.config_dir = config_dir
|
|
39
|
+
self.context_file = os.path.join(config_dir, "context_files.json")
|
|
40
|
+
self.patterns: List[str] = []
|
|
41
|
+
self.exclude_patterns: List[str] = [".git/*", "node_modules/*", "__pycache__/*"]
|
|
42
|
+
self._load_config()
|
|
43
|
+
|
|
44
|
+
def _load_config(self):
|
|
45
|
+
"""加载配置"""
|
|
46
|
+
if os.path.exists(self.context_file):
|
|
47
|
+
try:
|
|
48
|
+
import json
|
|
49
|
+
with open(self.context_file, 'r', encoding='utf-8') as f:
|
|
50
|
+
data = json.load(f)
|
|
51
|
+
self.patterns = data.get("patterns", [])
|
|
52
|
+
self.exclude_patterns = data.get("exclude", self.exclude_patterns)
|
|
53
|
+
except Exception:
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
def _save_config(self):
|
|
57
|
+
"""保存配置"""
|
|
58
|
+
import json
|
|
59
|
+
os.makedirs(os.path.dirname(self.context_file), exist_ok=True)
|
|
60
|
+
with open(self.context_file, 'w', encoding='utf-8') as f:
|
|
61
|
+
json.dump({
|
|
62
|
+
"patterns": self.patterns,
|
|
63
|
+
"exclude": self.exclude_patterns
|
|
64
|
+
}, f, indent=2)
|
|
65
|
+
|
|
66
|
+
def add_pattern(self, pattern: str):
|
|
67
|
+
"""添加匹配模式"""
|
|
68
|
+
if pattern not in self.patterns:
|
|
69
|
+
self.patterns.append(pattern)
|
|
70
|
+
self._save_config()
|
|
71
|
+
|
|
72
|
+
def remove_pattern(self, pattern: str):
|
|
73
|
+
"""移除匹配模式"""
|
|
74
|
+
if pattern in self.patterns:
|
|
75
|
+
self.patterns.remove(pattern)
|
|
76
|
+
self._save_config()
|
|
77
|
+
|
|
78
|
+
def add_exclude(self, pattern: str):
|
|
79
|
+
"""添加排除模式"""
|
|
80
|
+
if pattern not in self.exclude_patterns:
|
|
81
|
+
self.exclude_patterns.append(pattern)
|
|
82
|
+
self._save_config()
|
|
83
|
+
|
|
84
|
+
def get_matching_files(self, root_dir: str = ".") -> List[str]:
|
|
85
|
+
"""获取匹配的文件列表"""
|
|
86
|
+
files = set()
|
|
87
|
+
|
|
88
|
+
for pattern in self.patterns:
|
|
89
|
+
full_pattern = os.path.join(root_dir, pattern)
|
|
90
|
+
matched = glob.glob(full_pattern, recursive=True)
|
|
91
|
+
files.update(matched)
|
|
92
|
+
|
|
93
|
+
# 应用排除
|
|
94
|
+
excluded = set()
|
|
95
|
+
for exclude in self.exclude_patterns:
|
|
96
|
+
full_exclude = os.path.join(root_dir, exclude)
|
|
97
|
+
matched = glob.glob(full_exclude, recursive=True)
|
|
98
|
+
excluded.update(matched)
|
|
99
|
+
|
|
100
|
+
return [f for f in files if f not in excluded]
|
|
101
|
+
|
|
102
|
+
def load_context_files(self, root_dir: str = ".") -> List[ContextFile]:
|
|
103
|
+
"""加载所有上下文文件"""
|
|
104
|
+
files = self.get_matching_files(root_dir)
|
|
105
|
+
context_files = []
|
|
106
|
+
|
|
107
|
+
for filepath in files:
|
|
108
|
+
cf = ContextFile(path=filepath)
|
|
109
|
+
cf.load_content()
|
|
110
|
+
if cf.loaded:
|
|
111
|
+
context_files.append(cf)
|
|
112
|
+
|
|
113
|
+
return context_files
|
|
114
|
+
|
|
115
|
+
def build_context_prompt(self, root_dir: str = ".") -> str:
|
|
116
|
+
"""构建上下文提示"""
|
|
117
|
+
context_files = self.load_context_files(root_dir)
|
|
118
|
+
if not context_files:
|
|
119
|
+
return ""
|
|
120
|
+
|
|
121
|
+
parts = ["\n\n# 项目上下文\n"]
|
|
122
|
+
|
|
123
|
+
for cf in context_files:
|
|
124
|
+
parts.append(f"\n## {os.path.relpath(cf.path, root_dir)}")
|
|
125
|
+
parts.append(f"```\n{cf.content}\n```")
|
|
126
|
+
|
|
127
|
+
return "\n".join(parts)
|
|
128
|
+
|
|
129
|
+
def list_patterns(self) -> Dict:
|
|
130
|
+
"""列出所有模式"""
|
|
131
|
+
return {
|
|
132
|
+
"include": self.patterns,
|
|
133
|
+
"exclude": self.exclude_patterns
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
def set_patterns(self, patterns: List[str]):
|
|
137
|
+
"""设置包含模式"""
|
|
138
|
+
self.patterns = patterns
|
|
139
|
+
self._save_config()
|
|
140
|
+
|
|
141
|
+
def set_exclude_patterns(self, patterns: List[str]):
|
|
142
|
+
"""设置排除模式"""
|
|
143
|
+
self.exclude_patterns = patterns
|
|
144
|
+
self._save_config()
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
# 全局实例
|
|
148
|
+
_context_manager = None
|
|
149
|
+
|
|
150
|
+
def get_context_manager() -> ContextFilesManager:
|
|
151
|
+
global _context_manager
|
|
152
|
+
if _context_manager is None:
|
|
153
|
+
_context_manager = ContextFilesManager()
|
|
154
|
+
return _context_manager
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def build_project_context(root_dir: str = ".") -> str:
|
|
158
|
+
"""构建项目上下文"""
|
|
159
|
+
return get_context_manager().build_context_prompt(root_dir)
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Gateway 功能 - 多平台消息网关
|
|
3
|
+
参考 Hermes Agent 实现
|
|
4
|
+
支持 Telegram、Discord、Slack 等平台
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import asyncio
|
|
9
|
+
import json
|
|
10
|
+
from typing import Dict, Optional
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from enum import Enum
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Platform(Enum):
|
|
16
|
+
"""支持的平台"""
|
|
17
|
+
TELEGRAM = "telegram"
|
|
18
|
+
DISCORD = "discord"
|
|
19
|
+
SLACK = "slack"
|
|
20
|
+
CLI = "cli"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class GatewayConfig:
|
|
25
|
+
"""网关配置"""
|
|
26
|
+
platform: Platform
|
|
27
|
+
enabled: bool = False
|
|
28
|
+
bot_token: Optional[str] = None
|
|
29
|
+
api_key: Optional[str] = None
|
|
30
|
+
chat_id: Optional[str] = None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class GatewayManager:
|
|
34
|
+
"""网关管理器"""
|
|
35
|
+
|
|
36
|
+
def __init__(self):
|
|
37
|
+
self.config_file = os.path.expanduser("~/.fr_cli/gateway.json")
|
|
38
|
+
self.platforms: Dict[str, GatewayConfig] = {}
|
|
39
|
+
self._load_config()
|
|
40
|
+
self._running = False
|
|
41
|
+
|
|
42
|
+
def _load_config(self):
|
|
43
|
+
"""加载配置"""
|
|
44
|
+
if os.path.exists(self.config_file):
|
|
45
|
+
try:
|
|
46
|
+
with open(self.config_file) as f:
|
|
47
|
+
data = json.load(f)
|
|
48
|
+
for name, cfg in data.items():
|
|
49
|
+
self.platforms[name] = GatewayConfig(
|
|
50
|
+
platform=Platform(cfg.get("platform", "cli")),
|
|
51
|
+
enabled=cfg.get("enabled", False),
|
|
52
|
+
bot_token=cfg.get("bot_token"),
|
|
53
|
+
api_key=cfg.get("api_key"),
|
|
54
|
+
chat_id=cfg.get("chat_id")
|
|
55
|
+
)
|
|
56
|
+
except Exception:
|
|
57
|
+
pass
|
|
58
|
+
|
|
59
|
+
def _save_config(self):
|
|
60
|
+
"""保存配置"""
|
|
61
|
+
os.makedirs(os.path.dirname(self.config_file), exist_ok=True)
|
|
62
|
+
data = {}
|
|
63
|
+
for name, cfg in self.platforms.items():
|
|
64
|
+
data[name] = {
|
|
65
|
+
"platform": cfg.platform.value,
|
|
66
|
+
"enabled": cfg.enabled,
|
|
67
|
+
"bot_token": cfg.bot_token,
|
|
68
|
+
"api_key": cfg.api_key,
|
|
69
|
+
"chat_id": cfg.chat_id
|
|
70
|
+
}
|
|
71
|
+
with open(self.config_file, 'w') as f:
|
|
72
|
+
json.dump(data, f, indent=2)
|
|
73
|
+
|
|
74
|
+
def configure_telegram(self, bot_token: str, allowed_chat_ids: list = None):
|
|
75
|
+
"""配置 Telegram"""
|
|
76
|
+
self.platforms["telegram"] = GatewayConfig(
|
|
77
|
+
platform=Platform.TELEGRAM,
|
|
78
|
+
enabled=True,
|
|
79
|
+
bot_token=bot_token,
|
|
80
|
+
chat_id=json.dumps(allowed_chat_ids or [])
|
|
81
|
+
)
|
|
82
|
+
self._save_config()
|
|
83
|
+
print("✅ Telegram 配置完成")
|
|
84
|
+
print(" 使用 /gateway start telegram 启动")
|
|
85
|
+
|
|
86
|
+
def configure_discord(self, bot_token: str, guild_id: str = None):
|
|
87
|
+
"""配置 Discord"""
|
|
88
|
+
self.platforms["discord"] = GatewayConfig(
|
|
89
|
+
platform=Platform.DISCORD,
|
|
90
|
+
enabled=True,
|
|
91
|
+
bot_token=bot_token,
|
|
92
|
+
chat_id=guild_id
|
|
93
|
+
)
|
|
94
|
+
self._save_config()
|
|
95
|
+
print("✅ Discord 配置完成")
|
|
96
|
+
print(" 使用 /gateway start discord 启动")
|
|
97
|
+
|
|
98
|
+
def list_platforms(self):
|
|
99
|
+
"""列出已配置的平台"""
|
|
100
|
+
print("\n📱 已配置的消息平台:")
|
|
101
|
+
if not self.platforms:
|
|
102
|
+
print(" (无)")
|
|
103
|
+
return
|
|
104
|
+
|
|
105
|
+
for name, cfg in self.platforms.items():
|
|
106
|
+
status = "✅ 启用" if cfg.enabled else "❌ 禁用"
|
|
107
|
+
print(f" {name}: {status}")
|
|
108
|
+
print()
|
|
109
|
+
|
|
110
|
+
async def start_telegram(self):
|
|
111
|
+
"""启动 Telegram Bot"""
|
|
112
|
+
cfg = self.platforms.get("telegram")
|
|
113
|
+
if not cfg or not cfg.enabled:
|
|
114
|
+
print("❌ Telegram 未配置")
|
|
115
|
+
return
|
|
116
|
+
|
|
117
|
+
try:
|
|
118
|
+
from telegram import Bot
|
|
119
|
+
from telegram.ext import Application, CommandHandler, MessageHandler, filters
|
|
120
|
+
|
|
121
|
+
bot = Bot(token=cfg.bot_token)
|
|
122
|
+
|
|
123
|
+
async def handle_message(update, context):
|
|
124
|
+
from fr_cli.core.core import ask
|
|
125
|
+
message = update.message.text
|
|
126
|
+
result, _ = await asyncio.to_thread(ask, message)
|
|
127
|
+
await update.message.reply_text(result)
|
|
128
|
+
|
|
129
|
+
app = Application.builder().token(cfg.bot_token).build()
|
|
130
|
+
app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_message))
|
|
131
|
+
print("🤖 Telegram Bot 已启动!")
|
|
132
|
+
await app.run_polling()
|
|
133
|
+
|
|
134
|
+
except ImportError:
|
|
135
|
+
print("❌ 需要安装 python-telegram-bot: pip install python-telegram-bot")
|
|
136
|
+
except Exception as e:
|
|
137
|
+
print(f"❌ Telegram 启动失败: {e}")
|
|
138
|
+
|
|
139
|
+
async def start_discord(self):
|
|
140
|
+
"""启动 Discord Bot"""
|
|
141
|
+
cfg = self.platforms.get("discord")
|
|
142
|
+
if not cfg or not cfg.enabled:
|
|
143
|
+
print("❌ Discord 未配置")
|
|
144
|
+
return
|
|
145
|
+
|
|
146
|
+
try:
|
|
147
|
+
import discord
|
|
148
|
+
|
|
149
|
+
intents = discord.Intents.default()
|
|
150
|
+
intents.message_content = True
|
|
151
|
+
client = discord.Client(intents=intents)
|
|
152
|
+
|
|
153
|
+
@client.event
|
|
154
|
+
async def on_message(message):
|
|
155
|
+
if message.author == client.user:
|
|
156
|
+
return
|
|
157
|
+
|
|
158
|
+
from fr_cli.core.core import ask
|
|
159
|
+
result, _ = await asyncio.to_thread(ask, message.content)
|
|
160
|
+
await message.channel.send(result)
|
|
161
|
+
|
|
162
|
+
print("📢 Discord Bot 已启动!")
|
|
163
|
+
await client.start(cfg.bot_token)
|
|
164
|
+
|
|
165
|
+
except ImportError:
|
|
166
|
+
print("❌ 需要安装 discord.py: pip install discord.py")
|
|
167
|
+
except Exception as e:
|
|
168
|
+
print(f"❌ Discord 启动失败: {e}")
|
|
169
|
+
|
|
170
|
+
async def start_all(self):
|
|
171
|
+
"""启动所有已配置的平台"""
|
|
172
|
+
tasks = []
|
|
173
|
+
if "telegram" in self.platforms and self.platforms["telegram"].enabled:
|
|
174
|
+
tasks.append(self.start_telegram())
|
|
175
|
+
if "discord" in self.platforms and self.platforms["discord"].enabled:
|
|
176
|
+
tasks.append(self.start_discord())
|
|
177
|
+
|
|
178
|
+
if tasks:
|
|
179
|
+
await asyncio.gather(*tasks)
|
|
180
|
+
else:
|
|
181
|
+
print("❌ 没有已启用的平台")
|
|
182
|
+
|
|
183
|
+
def stop(self):
|
|
184
|
+
"""停止网关"""
|
|
185
|
+
self._running = False
|
|
186
|
+
print("🛑 网关已停止")
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def run_gateway(platform: str = None):
|
|
190
|
+
"""运行网关"""
|
|
191
|
+
manager = GatewayManager()
|
|
192
|
+
|
|
193
|
+
if platform:
|
|
194
|
+
if platform == "telegram":
|
|
195
|
+
asyncio.run(manager.start_telegram())
|
|
196
|
+
elif platform == "discord":
|
|
197
|
+
asyncio.run(manager.start_discord())
|
|
198
|
+
else:
|
|
199
|
+
print(f"❌ 不支持的平台: {platform}")
|
|
200
|
+
else:
|
|
201
|
+
asyncio.run(manager.start_all())
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
# CLI 命令
|
|
205
|
+
if __name__ == "__main__":
|
|
206
|
+
import sys
|
|
207
|
+
if len(sys.argv) > 1:
|
|
208
|
+
if sys.argv[1] == "start":
|
|
209
|
+
platform = sys.argv[2] if len(sys.argv) > 2 else None
|
|
210
|
+
run_gateway(platform)
|
|
211
|
+
elif sys.argv[1] == "setup":
|
|
212
|
+
manager = GatewayManager()
|
|
213
|
+
manager.list_platforms()
|
|
214
|
+
else:
|
|
215
|
+
print("用法: fr gateway [start|setup] [platform]")
|
|
216
|
+
else:
|
|
217
|
+
print("""
|
|
218
|
+
╔════════════════════════════════════════════════════╗
|
|
219
|
+
║ fr-cli Gateway 消息网关 ║
|
|
220
|
+
╚════════════════════════════════════════════════════╝
|
|
221
|
+
|
|
222
|
+
用法:
|
|
223
|
+
fr gateway setup 查看已配置的平台
|
|
224
|
+
fr gateway start telegram 启动 Telegram Bot
|
|
225
|
+
fr gateway start discord 启动 Discord Bot
|
|
226
|
+
fr gateway start 启动所有平台
|
|
227
|
+
|
|
228
|
+
配置示例:
|
|
229
|
+
from fr_cli.agent.gateway import GatewayManager
|
|
230
|
+
gm = GatewayManager()
|
|
231
|
+
gm.configure_telegram("your-bot-token")
|
|
232
|
+
gm.configure_discord("your-bot-token")
|
|
233
|
+
""")
|
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Hermes 功能模块
|
|
3
3
|
参考 NousResearch/hermes-agent 实现核心功能
|
|
4
|
+
|
|
5
|
+
整合:
|
|
6
|
+
- TaskManager: 任务管理
|
|
7
|
+
- Analytics: 分析统计
|
|
8
|
+
- GoalTracker: 目标追踪
|
|
9
|
+
- ConfigManager: 配置管理
|
|
10
|
+
- CronScheduler: 定时任务
|
|
11
|
+
- SkillManager: 技能系统 (见 skills.py)
|
|
12
|
+
- PersonalityManager: 个性系统 (见 personality.py)
|
|
13
|
+
- ContextFilesManager: 上下文文件 (见 context_files.py)
|
|
4
14
|
"""
|
|
5
15
|
|
|
6
16
|
import os
|
|
@@ -378,9 +378,25 @@ class MasterAgent:
|
|
|
378
378
|
# 4. Hermes 自动学习更新
|
|
379
379
|
try:
|
|
380
380
|
from fr_cli.agent.hermes import get_analytics, get_task_manager
|
|
381
|
+
from fr_cli.agent.skills import get_skill_manager
|
|
382
|
+
from fr_cli.agent.personality import get_personality_manager
|
|
383
|
+
|
|
381
384
|
analytics = get_analytics()
|
|
382
385
|
task_mgr = get_task_manager()
|
|
386
|
+
skill_mgr = get_skill_manager()
|
|
387
|
+
personality_mgr = get_personality_manager()
|
|
388
|
+
|
|
383
389
|
analytics.record_request(self.state.model_name, 100, 0.001)
|
|
390
|
+
|
|
391
|
+
if any(kw in user_input for kw in ["学习", "记住", "技能", "skill"]):
|
|
392
|
+
skill_mgr.learn_from_task(user_input, final_answer)
|
|
393
|
+
|
|
394
|
+
if any(kw in user_input for kw in ["切换", "模式", "角色", "个性"]):
|
|
395
|
+
for p in ["coder", "teacher", "creative", "expert"]:
|
|
396
|
+
if p in user_input.lower():
|
|
397
|
+
personality_mgr.set_personality(p)
|
|
398
|
+
break
|
|
399
|
+
|
|
384
400
|
except Exception:
|
|
385
401
|
pass
|
|
386
402
|
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Personality 系统 - 参考 Hermes Agent 实现
|
|
3
|
+
支持多种 AI 个性切换
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import json
|
|
8
|
+
from typing import Dict, Optional
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class Personality:
|
|
14
|
+
"""AI 个性"""
|
|
15
|
+
name: str
|
|
16
|
+
description: str
|
|
17
|
+
identity: str
|
|
18
|
+
system_prompt: str
|
|
19
|
+
examples: list = None
|
|
20
|
+
|
|
21
|
+
def __post_init__(self):
|
|
22
|
+
if self.examples is None:
|
|
23
|
+
self.examples = []
|
|
24
|
+
|
|
25
|
+
def to_dict(self) -> Dict:
|
|
26
|
+
return {
|
|
27
|
+
"name": self.name,
|
|
28
|
+
"description": self.description,
|
|
29
|
+
"identity": self.identity,
|
|
30
|
+
"system_prompt": self.system_prompt,
|
|
31
|
+
"examples": self.examples
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@classmethod
|
|
35
|
+
def from_dict(cls, data: Dict) -> 'Personality':
|
|
36
|
+
return cls(**data)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class PersonalityManager:
|
|
40
|
+
"""个性管理器"""
|
|
41
|
+
|
|
42
|
+
def __init__(self, config_dir: str = None):
|
|
43
|
+
if config_dir is None:
|
|
44
|
+
config_dir = os.path.expanduser("~/.fr_cli")
|
|
45
|
+
self.config_dir = config_dir
|
|
46
|
+
self.personalities: Dict[str, Personality] = {}
|
|
47
|
+
self.current_personality: Optional[Personality] = None
|
|
48
|
+
self._load_personalities()
|
|
49
|
+
|
|
50
|
+
def _get_personalities_file(self) -> str:
|
|
51
|
+
return os.path.join(self.config_dir, "personalities.json")
|
|
52
|
+
|
|
53
|
+
def _load_personalities(self):
|
|
54
|
+
"""加载内置和用户个性"""
|
|
55
|
+
self._register_builtin_personalities()
|
|
56
|
+
|
|
57
|
+
config_file = self._get_personalities_file()
|
|
58
|
+
if os.path.exists(config_file):
|
|
59
|
+
try:
|
|
60
|
+
with open(config_file, 'r', encoding='utf-8') as f:
|
|
61
|
+
data = json.load(f)
|
|
62
|
+
for name, cfg in data.items():
|
|
63
|
+
self.personalities[name] = Personality.from_dict(cfg)
|
|
64
|
+
except Exception:
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
if not self.current_personality and self.personalities:
|
|
68
|
+
self.current_personality = list(self.personalities.values())[0]
|
|
69
|
+
|
|
70
|
+
def _register_builtin_personalities(self):
|
|
71
|
+
"""注册内置个性"""
|
|
72
|
+
self.personalities = {
|
|
73
|
+
"default": Personality(
|
|
74
|
+
name="default",
|
|
75
|
+
description="默认助手",
|
|
76
|
+
identity="你是一个有帮助的 AI 助手。",
|
|
77
|
+
system_prompt="你是一个有帮助的 AI 助手,擅长回答各种问题。"
|
|
78
|
+
),
|
|
79
|
+
"coder": Personality(
|
|
80
|
+
name="coder",
|
|
81
|
+
description="编程专家",
|
|
82
|
+
identity="你是一个专业的程序员。",
|
|
83
|
+
system_prompt="""你是一个专业的程序员,擅长:
|
|
84
|
+
- Python, JavaScript, TypeScript, Go, Rust 等语言
|
|
85
|
+
- 代码审查和优化
|
|
86
|
+
- 调试和错误修复
|
|
87
|
+
- 设计模式和架构建议
|
|
88
|
+
- 编写测试和文档""",
|
|
89
|
+
examples=["帮我写一个排序算法", "这段代码有什么问题?"]
|
|
90
|
+
),
|
|
91
|
+
"reviewer": Personality(
|
|
92
|
+
name="reviewer",
|
|
93
|
+
description="代码审查员",
|
|
94
|
+
identity="你是一个严格的代码审查员。",
|
|
95
|
+
system_prompt="""你是一个严格的代码审查员,关注:
|
|
96
|
+
- 代码质量和可读性
|
|
97
|
+
- 性能和效率
|
|
98
|
+
- 安全漏洞
|
|
99
|
+
- 最佳实践
|
|
100
|
+
- 潜在的 bug
|
|
101
|
+
|
|
102
|
+
给出具体、可操作的改进建议。""",
|
|
103
|
+
examples=["审查这段代码", "有什么安全问题?"]
|
|
104
|
+
),
|
|
105
|
+
"teacher": Personality(
|
|
106
|
+
name="teacher",
|
|
107
|
+
description="编程教师",
|
|
108
|
+
identity="你是一个耐心的编程教师。",
|
|
109
|
+
system_prompt="""你是一个耐心的编程教师,特点:
|
|
110
|
+
- 解释清晰易懂
|
|
111
|
+
- 提供例子和类比
|
|
112
|
+
- 循序渐进
|
|
113
|
+
- 鼓励学习者
|
|
114
|
+
- 耐心回答问题""",
|
|
115
|
+
examples=["解释什么是闭包", "面向对象是什么?"]
|
|
116
|
+
),
|
|
117
|
+
"expert": Personality(
|
|
118
|
+
name="expert",
|
|
119
|
+
description="领域专家",
|
|
120
|
+
identity="你是一个各个领域的专家。",
|
|
121
|
+
system_prompt="""你是一个广博的领域专家,可以提供:
|
|
122
|
+
- 深入的技术分析
|
|
123
|
+
- 行业洞察
|
|
124
|
+
- 战略建议
|
|
125
|
+
- 最佳实践
|
|
126
|
+
- 前沿趋势""",
|
|
127
|
+
examples=["分析这个架构的优缺点", "给我一些战略建议"]
|
|
128
|
+
),
|
|
129
|
+
"creative": Personality(
|
|
130
|
+
name="creative",
|
|
131
|
+
description="创意助手",
|
|
132
|
+
identity="你是一个富有创意的助手。",
|
|
133
|
+
system_prompt="""你是一个富有创意的助手,擅长:
|
|
134
|
+
- 头脑风暴
|
|
135
|
+
- 生成创意点子
|
|
136
|
+
- 写作和内容创作
|
|
137
|
+
- 解决棘手问题
|
|
138
|
+
- 提供新颖的解决方案""",
|
|
139
|
+
examples=["给我一些创意点子", "怎么让这个产品更有趣?"]
|
|
140
|
+
)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
def _save_personalities(self):
|
|
144
|
+
"""保存用户个性"""
|
|
145
|
+
config_file = self._get_personalities_file()
|
|
146
|
+
os.makedirs(os.path.dirname(config_file), exist_ok=True)
|
|
147
|
+
data = {
|
|
148
|
+
name: p.to_dict()
|
|
149
|
+
for name, p in self.personalities.items()
|
|
150
|
+
if name not in ["default", "coder", "reviewer", "teacher", "expert", "creative"]
|
|
151
|
+
}
|
|
152
|
+
if data:
|
|
153
|
+
with open(config_file, 'w', encoding='utf-8') as f:
|
|
154
|
+
json.dump(data, f, ensure_ascii=False, indent=2)
|
|
155
|
+
|
|
156
|
+
def set_personality(self, name: str) -> bool:
|
|
157
|
+
"""设置当前个性"""
|
|
158
|
+
if name in self.personalities:
|
|
159
|
+
self.current_personality = self.personalities[name]
|
|
160
|
+
return True
|
|
161
|
+
return False
|
|
162
|
+
|
|
163
|
+
def get_current(self) -> Optional[Personality]:
|
|
164
|
+
"""获取当前个性"""
|
|
165
|
+
return self.current_personality
|
|
166
|
+
|
|
167
|
+
def add_personality(self, personality: Personality):
|
|
168
|
+
"""添加用户个性"""
|
|
169
|
+
self.personalities[personality.name] = personality
|
|
170
|
+
self._save_personalities()
|
|
171
|
+
|
|
172
|
+
def remove_personality(self, name: str) -> bool:
|
|
173
|
+
"""删除用户个性"""
|
|
174
|
+
if name in ["default", "coder", "reviewer", "teacher", "expert", "creative"]:
|
|
175
|
+
return False
|
|
176
|
+
if name in self.personalities:
|
|
177
|
+
del self.personalities[name]
|
|
178
|
+
if self.current_personality and self.current_personality.name == name:
|
|
179
|
+
self.current_personality = self.personalities.get("default")
|
|
180
|
+
self._save_personalities()
|
|
181
|
+
return True
|
|
182
|
+
return False
|
|
183
|
+
|
|
184
|
+
def list_personalities(self) -> list:
|
|
185
|
+
"""列出所有个性"""
|
|
186
|
+
return [
|
|
187
|
+
{
|
|
188
|
+
"name": p.name,
|
|
189
|
+
"description": p.description,
|
|
190
|
+
"current": p == self.current_personality
|
|
191
|
+
}
|
|
192
|
+
for p in self.personalities.values()
|
|
193
|
+
]
|
|
194
|
+
|
|
195
|
+
def build_system_prompt(self) -> str:
|
|
196
|
+
"""构建当前个性的系统提示"""
|
|
197
|
+
if not self.current_personality:
|
|
198
|
+
return ""
|
|
199
|
+
|
|
200
|
+
p = self.current_personality
|
|
201
|
+
parts = [
|
|
202
|
+
f"# 身份: {p.identity}",
|
|
203
|
+
f"\n{p.system_prompt}"
|
|
204
|
+
]
|
|
205
|
+
|
|
206
|
+
if p.examples:
|
|
207
|
+
parts.append("\n## 示例对话:")
|
|
208
|
+
for ex in p.examples:
|
|
209
|
+
parts.append(f"- {ex}")
|
|
210
|
+
|
|
211
|
+
return "\n".join(parts)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
# 全局实例
|
|
215
|
+
_personality_manager = None
|
|
216
|
+
|
|
217
|
+
def get_personality_manager() -> PersonalityManager:
|
|
218
|
+
global _personality_manager
|
|
219
|
+
if _personality_manager is None:
|
|
220
|
+
_personality_manager = PersonalityManager()
|
|
221
|
+
return _personality_manager
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def set_personality(name: str) -> bool:
|
|
225
|
+
"""设置当前个性"""
|
|
226
|
+
return get_personality_manager().set_personality(name)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def get_current_personality() -> Optional[Personality]:
|
|
230
|
+
"""获取当前个性"""
|
|
231
|
+
return get_personality_manager().get_current()
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def list_personalities() -> list:
|
|
235
|
+
"""列出所有个性"""
|
|
236
|
+
return get_personality_manager().list_personalities()
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Skills 系统 - 参考 Hermes Agent 实现
|
|
3
|
+
技能是 AI 自我学习的核心机制
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import json
|
|
8
|
+
import time
|
|
9
|
+
from typing import Dict, List, Optional, Callable
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class Skill:
|
|
15
|
+
"""技能"""
|
|
16
|
+
name: str
|
|
17
|
+
description: str
|
|
18
|
+
content: str
|
|
19
|
+
author: str = "system"
|
|
20
|
+
created_at: float = field(default_factory=time.time)
|
|
21
|
+
updated_at: float = field(default_factory=time.time)
|
|
22
|
+
usage_count: int = 0
|
|
23
|
+
success_count: int = 0
|
|
24
|
+
tags: List[str] = field(default_factory=list)
|
|
25
|
+
enabled: bool = True
|
|
26
|
+
|
|
27
|
+
def to_dict(self) -> Dict:
|
|
28
|
+
return {
|
|
29
|
+
"name": self.name,
|
|
30
|
+
"description": self.description,
|
|
31
|
+
"content": self.content,
|
|
32
|
+
"author": self.author,
|
|
33
|
+
"created_at": self.created_at,
|
|
34
|
+
"updated_at": self.updated_at,
|
|
35
|
+
"usage_count": self.usage_count,
|
|
36
|
+
"success_count": self.success_count,
|
|
37
|
+
"tags": self.tags,
|
|
38
|
+
"enabled": self.enabled
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@classmethod
|
|
42
|
+
def from_dict(cls, data: Dict) -> 'Skill':
|
|
43
|
+
return cls(
|
|
44
|
+
name=data["name"],
|
|
45
|
+
description=data.get("description", ""),
|
|
46
|
+
content=data["content"],
|
|
47
|
+
author=data.get("author", "system"),
|
|
48
|
+
created_at=data.get("created_at", time.time()),
|
|
49
|
+
updated_at=data.get("updated_at", time.time()),
|
|
50
|
+
usage_count=data.get("usage_count", 0),
|
|
51
|
+
success_count=data.get("success_count", 0),
|
|
52
|
+
tags=data.get("tags", []),
|
|
53
|
+
enabled=data.get("enabled", True)
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class SkillManager:
|
|
58
|
+
"""技能管理器"""
|
|
59
|
+
|
|
60
|
+
def __init__(self, skills_dir: str = None):
|
|
61
|
+
if skills_dir is None:
|
|
62
|
+
skills_dir = os.path.expanduser("~/.fr_cli/skills")
|
|
63
|
+
self.skills_dir = skills_dir
|
|
64
|
+
self.skills: Dict[str, Skill] = {}
|
|
65
|
+
self._load_skills()
|
|
66
|
+
self._ensure_dir()
|
|
67
|
+
|
|
68
|
+
def _ensure_dir(self):
|
|
69
|
+
"""确保目录存在"""
|
|
70
|
+
os.makedirs(self.skills_dir, exist_ok=True)
|
|
71
|
+
|
|
72
|
+
def _load_skills(self):
|
|
73
|
+
"""加载所有技能"""
|
|
74
|
+
if not os.path.exists(self.skills_dir):
|
|
75
|
+
return
|
|
76
|
+
|
|
77
|
+
for filename in os.listdir(self.skills_dir):
|
|
78
|
+
if filename.endswith('.json'):
|
|
79
|
+
skill_path = os.path.join(self.skills_dir, filename)
|
|
80
|
+
try:
|
|
81
|
+
with open(skill_path, 'r', encoding='utf-8') as f:
|
|
82
|
+
data = json.load(f)
|
|
83
|
+
skill = Skill.from_dict(data)
|
|
84
|
+
self.skills[skill.name] = skill
|
|
85
|
+
except Exception:
|
|
86
|
+
pass
|
|
87
|
+
|
|
88
|
+
def _save_skill(self, skill: Skill):
|
|
89
|
+
"""保存技能"""
|
|
90
|
+
self._ensure_dir()
|
|
91
|
+
filename = f"{skill.name}.json".replace("/", "_")
|
|
92
|
+
filepath = os.path.join(self.skills_dir, filename)
|
|
93
|
+
with open(filepath, 'w', encoding='utf-8') as f:
|
|
94
|
+
json.dump(skill.to_dict(), f, ensure_ascii=False, indent=2)
|
|
95
|
+
|
|
96
|
+
def create_skill(self, name: str, description: str, content: str,
|
|
97
|
+
author: str = "user", tags: List[str] = None) -> Skill:
|
|
98
|
+
"""创建技能"""
|
|
99
|
+
skill = Skill(
|
|
100
|
+
name=name,
|
|
101
|
+
description=description,
|
|
102
|
+
content=content,
|
|
103
|
+
author=author,
|
|
104
|
+
tags=tags or []
|
|
105
|
+
)
|
|
106
|
+
self.skills[name] = skill
|
|
107
|
+
self._save_skill(skill)
|
|
108
|
+
return skill
|
|
109
|
+
|
|
110
|
+
def get_skill(self, name: str) -> Optional[Skill]:
|
|
111
|
+
"""获取技能"""
|
|
112
|
+
return self.skills.get(name)
|
|
113
|
+
|
|
114
|
+
def update_skill(self, name: str, **kwargs):
|
|
115
|
+
"""更新技能"""
|
|
116
|
+
if name in self.skills:
|
|
117
|
+
skill = self.skills[name]
|
|
118
|
+
for key, value in kwargs.items():
|
|
119
|
+
if hasattr(skill, key):
|
|
120
|
+
setattr(skill, key, value)
|
|
121
|
+
skill.updated_at = time.time()
|
|
122
|
+
self._save_skill(skill)
|
|
123
|
+
return skill
|
|
124
|
+
return None
|
|
125
|
+
|
|
126
|
+
def delete_skill(self, name: str) -> bool:
|
|
127
|
+
"""删除技能"""
|
|
128
|
+
if name in self.skills:
|
|
129
|
+
del self.skills[name]
|
|
130
|
+
filename = f"{name}.json".replace("/", "_")
|
|
131
|
+
filepath = os.path.join(self.skills_dir, filename)
|
|
132
|
+
if os.path.exists(filepath):
|
|
133
|
+
os.remove(filepath)
|
|
134
|
+
return True
|
|
135
|
+
return False
|
|
136
|
+
|
|
137
|
+
def list_skills(self, enabled_only: bool = True) -> List[Skill]:
|
|
138
|
+
"""列出技能"""
|
|
139
|
+
skills = list(self.skills.values())
|
|
140
|
+
if enabled_only:
|
|
141
|
+
skills = [s for s in skills if s.enabled]
|
|
142
|
+
return skills
|
|
143
|
+
|
|
144
|
+
def search_skills(self, query: str) -> List[Skill]:
|
|
145
|
+
"""搜索技能"""
|
|
146
|
+
query_lower = query.lower()
|
|
147
|
+
results = []
|
|
148
|
+
for skill in self.skills.values():
|
|
149
|
+
if (query_lower in skill.name.lower() or
|
|
150
|
+
query_lower in skill.description.lower() or
|
|
151
|
+
any(query_lower in tag.lower() for tag in skill.tags)):
|
|
152
|
+
results.append(skill)
|
|
153
|
+
return results
|
|
154
|
+
|
|
155
|
+
def record_usage(self, name: str, success: bool = True):
|
|
156
|
+
"""记录技能使用"""
|
|
157
|
+
if name in self.skills:
|
|
158
|
+
skill = self.skills[name]
|
|
159
|
+
skill.usage_count += 1
|
|
160
|
+
if success:
|
|
161
|
+
skill.success_count += 1
|
|
162
|
+
self._save_skill(skill)
|
|
163
|
+
|
|
164
|
+
def build_system_prompt(self) -> str:
|
|
165
|
+
"""构建技能系统提示"""
|
|
166
|
+
skills = self.list_skills()
|
|
167
|
+
if not skills:
|
|
168
|
+
return ""
|
|
169
|
+
|
|
170
|
+
lines = ["\n\n# 可用技能"]
|
|
171
|
+
for skill in skills:
|
|
172
|
+
lines.append(f"\n## {skill.name}")
|
|
173
|
+
lines.append(f"描述: {skill.description}")
|
|
174
|
+
lines.append(f"内容:\n{skill.content}")
|
|
175
|
+
|
|
176
|
+
return "\n".join(lines)
|
|
177
|
+
|
|
178
|
+
def learn_from_task(self, task: str, result: str, context: str = ""):
|
|
179
|
+
"""从任务中学习并创建技能"""
|
|
180
|
+
skill = Skill(
|
|
181
|
+
name=f"skill_{int(time.time())}",
|
|
182
|
+
description=f"从任务 '{task[:50]}...' 学习",
|
|
183
|
+
content=f"任务: {task}\n\n结果: {result}\n\n上下文: {context}",
|
|
184
|
+
author="auto"
|
|
185
|
+
)
|
|
186
|
+
self.skills[skill.name] = skill
|
|
187
|
+
self._save_skill(skill)
|
|
188
|
+
return skill
|
|
189
|
+
|
|
190
|
+
def get_stats(self) -> Dict:
|
|
191
|
+
"""获取统计"""
|
|
192
|
+
skills = list(self.skills.values())
|
|
193
|
+
return {
|
|
194
|
+
"total": len(skills),
|
|
195
|
+
"enabled": sum(1 for s in skills if s.enabled),
|
|
196
|
+
"total_usage": sum(s.usage_count for s in skills),
|
|
197
|
+
"success_rate": (
|
|
198
|
+
sum(s.success_count for s in skills) /
|
|
199
|
+
max(1, sum(s.usage_count for s in skills))
|
|
200
|
+
)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
# 全局实例
|
|
205
|
+
_skill_manager = None
|
|
206
|
+
|
|
207
|
+
def get_skill_manager() -> SkillManager:
|
|
208
|
+
global _skill_manager
|
|
209
|
+
if _skill_manager is None:
|
|
210
|
+
_skill_manager = SkillManager()
|
|
211
|
+
return _skill_manager
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def create_skill(name: str, description: str, content: str) -> Skill:
|
|
215
|
+
"""创建技能快捷方法"""
|
|
216
|
+
return get_skill_manager().create_skill(name, description, content)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def list_skills() -> List[Skill]:
|
|
220
|
+
"""列出所有技能"""
|
|
221
|
+
return get_skill_manager().list_skills()
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def search_skills(query: str) -> List[Skill]:
|
|
225
|
+
"""搜索技能"""
|
|
226
|
+
return get_skill_manager().search_skills(query)
|
|
@@ -18,17 +18,21 @@ fr_cli/agent/a2a.py
|
|
|
18
18
|
fr_cli/agent/acp.py
|
|
19
19
|
fr_cli/agent/client.py
|
|
20
20
|
fr_cli/agent/coding_helper.py
|
|
21
|
+
fr_cli/agent/context_files.py
|
|
21
22
|
fr_cli/agent/executor.py
|
|
23
|
+
fr_cli/agent/gateway.py
|
|
22
24
|
fr_cli/agent/generator.py
|
|
23
25
|
fr_cli/agent/hermes.py
|
|
24
26
|
fr_cli/agent/image_and_parallel.py
|
|
25
27
|
fr_cli/agent/manager.py
|
|
26
28
|
fr_cli/agent/master.py
|
|
27
29
|
fr_cli/agent/master_prompt.py
|
|
30
|
+
fr_cli/agent/personality.py
|
|
28
31
|
fr_cli/agent/plugin_system.py
|
|
29
32
|
fr_cli/agent/remote.py
|
|
30
33
|
fr_cli/agent/server.py
|
|
31
34
|
fr_cli/agent/shell_mode.py
|
|
35
|
+
fr_cli/agent/skills.py
|
|
32
36
|
fr_cli/agent/workflow.py
|
|
33
37
|
fr_cli/agent/workflow_system.py
|
|
34
38
|
fr_cli/agent/builtins/__init__.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|