@voko/lite 0.3.1
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.
- package/package.json +32 -0
- package/scripts/build-native.js +72 -0
- package/src/bankHeadOffices.js +20543 -0
- package/src/channels/email.js +35 -0
- package/src/channels/feishu.js +31 -0
- package/src/channels/qq-email.js +30 -0
- package/src/channels/registry.js +279 -0
- package/src/channels/telegram.js +28 -0
- package/src/channels/voko-email.js +7 -0
- package/src/channels/wechat.js +35 -0
- package/src/cli.js +120 -0
- package/src/context.js +164 -0
- package/src/core/access-control-api.js +150 -0
- package/src/core/access-control.js +56 -0
- package/src/core/agent-registration.js +319 -0
- package/src/core/api-signature.js +33 -0
- package/src/core/audit.js +133 -0
- package/src/core/database.js +1409 -0
- package/src/core/did-auth.js +54 -0
- package/src/core/hermes-paths.js +57 -0
- package/src/core/invitation.js +49 -0
- package/src/core/lite-bus.js +16 -0
- package/src/core/llm-client.js +1032 -0
- package/src/core/messenger.js +456 -0
- package/src/core/notifier.js +99 -0
- package/src/core/offline-sync.js +150 -0
- package/src/core/payment.js +285 -0
- package/src/core/publish-agent.js +166 -0
- package/src/core/register-capabilities.js +119 -0
- package/src/core/search-capabilities.js +136 -0
- package/src/core/send-message.js +85 -0
- package/src/core/set-agent-status.js +65 -0
- package/src/core/update-agent-profile.js +102 -0
- package/src/core/worker-manager.js +332 -0
- package/src/endpoints.json +21 -0
- package/src/index.js +712 -0
- package/src/mcp/CLAUDE_TEST.md +82 -0
- package/src/mcp/FULL_TEST.md +139 -0
- package/src/mcp/TEST.md +124 -0
- package/src/mcp/TEST_STEPS.md +75 -0
- package/src/mcp/server.js +612 -0
- package/src/mcp/tools.js +1367 -0
- package/src/mcp/transport/http.js +95 -0
- package/src/mcp/transport/stdio.js +20 -0
- package/src/preload.js +27 -0
- package/src/server/agent-email-api.js +120 -0
- package/src/server/agent-manager.js +580 -0
- package/src/server/email-handler.js +329 -0
- package/src/server/feishu-handler.js +249 -0
- package/src/server/hermes-api-client.js +166 -0
- package/src/server/hermes-discovery.js +80 -0
- package/src/server/hermes-handler.js +287 -0
- package/src/server/openclaw-handler-cli.js +131 -0
- package/src/server/openclaw-websocket-handler.js +1290 -0
- package/src/server/oss.js +186 -0
- package/src/server/owner-intervention-notifier.js +320 -0
- package/src/server/release-page.html +204 -0
- package/src/server/telegram-handler.js +208 -0
- package/src/server/voko-email-handler.js +68 -0
- package/src/server/wechat-handler.js +439 -0
- package/src/workers/agent-worker.js +378 -0
- package/src/workers/message-content.js +51 -0
|
@@ -0,0 +1,580 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const { discoverHermes } = require('./hermes-discovery');
|
|
5
|
+
const { getHermesProfilePath } = require('../core/hermes-paths');
|
|
6
|
+
|
|
7
|
+
class AgentManager {
|
|
8
|
+
constructor(databaseAPI) {
|
|
9
|
+
this.databaseAPI = databaseAPI || null;
|
|
10
|
+
this.openclawConfigPath = path.join(os.homedir(), '.openclaw', 'openclaw.json');
|
|
11
|
+
this.channelConfigPath = path.join(__dirname, '..', '..', 'config', 'channel_config.json');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// 读取 openclaw.json 配置
|
|
15
|
+
_readOpenclawConfig() {
|
|
16
|
+
try {
|
|
17
|
+
const content = fs.readFileSync(this.openclawConfigPath, 'utf-8');
|
|
18
|
+
return JSON.parse(content);
|
|
19
|
+
} catch (err) {
|
|
20
|
+
console.error('[AgentManager] 读取 openclaw.json 失败:', err.message);
|
|
21
|
+
return { agents: { list: [] } };
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// 写入 openclaw.json 配置
|
|
26
|
+
_writeOpenclawConfig(config) {
|
|
27
|
+
try {
|
|
28
|
+
fs.writeFileSync(this.openclawConfigPath, JSON.stringify(config, null, 2), 'utf-8');
|
|
29
|
+
return true;
|
|
30
|
+
} catch (err) {
|
|
31
|
+
console.error('[AgentManager] 写入 openclaw.json 失败:', err.message);
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// 读取 OpenClaw 默认模型
|
|
37
|
+
_getDefaultModel() {
|
|
38
|
+
try {
|
|
39
|
+
const config = this._readOpenclawConfig();
|
|
40
|
+
// agents.defaults.model.primary (OpenClaw 常用结构)
|
|
41
|
+
if (config.agents?.defaults?.model?.primary) return config.agents.defaults.model.primary;
|
|
42
|
+
// auth.profiles 中的模型
|
|
43
|
+
const profiles = config.auth?.profiles || {};
|
|
44
|
+
for (const key of Object.keys(profiles)) {
|
|
45
|
+
const profile = profiles[key];
|
|
46
|
+
if (profile.model?.primary) return profile.model.primary;
|
|
47
|
+
if (profile.model) return profile.model;
|
|
48
|
+
}
|
|
49
|
+
// 尝试 agents.defaultModel
|
|
50
|
+
if (config.agents?.defaultModel) return config.agents.defaultModel;
|
|
51
|
+
// 尝试顶层 gateway.model
|
|
52
|
+
if (config.gateway?.model) return config.gateway.model;
|
|
53
|
+
return null;
|
|
54
|
+
} catch (e) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
getDefaultModel() {
|
|
60
|
+
return this._getDefaultModel();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// 读取 channel_config(优先 DB,首次运行从 JSON 迁移)
|
|
64
|
+
_readChannelConfig() {
|
|
65
|
+
try {
|
|
66
|
+
if (this.databaseAPI) {
|
|
67
|
+
const fromDb = this.databaseAPI.getConfigFromDb();
|
|
68
|
+
if (fromDb) return fromDb;
|
|
69
|
+
}
|
|
70
|
+
// fallback: 从 JSON 文件读取(首次迁移)
|
|
71
|
+
const content = fs.readFileSync(this.channelConfigPath, 'utf-8');
|
|
72
|
+
return JSON.parse(content);
|
|
73
|
+
} catch (err) {
|
|
74
|
+
console.error('[AgentManager] 读取 channel_config 失败:', err.message);
|
|
75
|
+
return {};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// 写入 channel_config(仅 DB,不落明文文件)
|
|
80
|
+
_writeChannelConfig(config) {
|
|
81
|
+
try {
|
|
82
|
+
if (this.databaseAPI) this.databaseAPI.saveConfigToDb(config);
|
|
83
|
+
return true;
|
|
84
|
+
} catch (err) {
|
|
85
|
+
console.error('[AgentManager] 写入 channel_config 失败:', err.message);
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// 展开 ~ 为用户家目录
|
|
91
|
+
_expandPath(p) {
|
|
92
|
+
if (p && p.startsWith('~')) {
|
|
93
|
+
return path.join(os.homedir(), p.slice(1));
|
|
94
|
+
}
|
|
95
|
+
return p;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// 获取 routingRules
|
|
99
|
+
getRoutingRules() {
|
|
100
|
+
const config = this._readChannelConfig();
|
|
101
|
+
return config.routingRules || [];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// 获取当前 voko 对接的 agent
|
|
105
|
+
getVokoAgent() {
|
|
106
|
+
const rules = this.getRoutingRules();
|
|
107
|
+
const defaultRule = rules.find(r => r.wsChannel === 'default');
|
|
108
|
+
return defaultRule ? defaultRule.agentId : null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// 设置 voko 对接的 agent
|
|
112
|
+
setVokoAgent(agentId) {
|
|
113
|
+
const config = this._readChannelConfig();
|
|
114
|
+
if (!config.routingRules) {
|
|
115
|
+
config.routingRules = [];
|
|
116
|
+
}
|
|
117
|
+
const idx = config.routingRules.findIndex(r => r.wsChannel === 'default');
|
|
118
|
+
if (idx >= 0) {
|
|
119
|
+
config.routingRules[idx] = { wsChannel: 'default', sessionPrefix: `agent:${agentId}:`, agentId };
|
|
120
|
+
} else {
|
|
121
|
+
config.routingRules.push({ wsChannel: 'default', sessionPrefix: `agent:${agentId}:`, agentId });
|
|
122
|
+
}
|
|
123
|
+
return this._writeChannelConfig(config);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// 列出所有 agent
|
|
127
|
+
listAgents() {
|
|
128
|
+
const config = this._readOpenclawConfig();
|
|
129
|
+
const openclawAgents = config?.agents?.list || [];
|
|
130
|
+
const vokoAgentId = this.getVokoAgent();
|
|
131
|
+
|
|
132
|
+
// 发现 Hermes profiles
|
|
133
|
+
let hermesAgents = [];
|
|
134
|
+
try {
|
|
135
|
+
hermesAgents = discoverHermes();
|
|
136
|
+
} catch (err) {
|
|
137
|
+
console.error('[AgentManager] Hermes 发现失败:', err.message);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// 读取 channel_config 中的 Hermes 端口配置
|
|
141
|
+
const channelCfg = this._readChannelConfig();
|
|
142
|
+
const hermesProfiles = channelCfg?.hermes_config?.profiles || {};
|
|
143
|
+
|
|
144
|
+
const result = {
|
|
145
|
+
agents: [
|
|
146
|
+
// OpenClaw agents
|
|
147
|
+
...openclawAgents.map(a => ({
|
|
148
|
+
id: a.id,
|
|
149
|
+
name: a.name || a.id,
|
|
150
|
+
workspace: a.workspace,
|
|
151
|
+
agentDir: a.agentDir,
|
|
152
|
+
model: a.model,
|
|
153
|
+
skills: a.skills || [],
|
|
154
|
+
tools: a.tools || { allow: [], deny: [] },
|
|
155
|
+
isVoko: a.id === vokoAgentId,
|
|
156
|
+
backend: 'openclaw'
|
|
157
|
+
})),
|
|
158
|
+
// Hermes agents
|
|
159
|
+
...hermesAgents.map(p => ({
|
|
160
|
+
id: p.name,
|
|
161
|
+
name: p.name,
|
|
162
|
+
model: p.model,
|
|
163
|
+
isDefault: p.isDefault,
|
|
164
|
+
backend: 'hermes',
|
|
165
|
+
port: hermesProfiles[p.name]?.port || null
|
|
166
|
+
}))
|
|
167
|
+
],
|
|
168
|
+
openclawFound: !!config?.agents,
|
|
169
|
+
hermesFound: hermesAgents.length > 0
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
// 合并 SQLite agents 表中已发布的 agent(去重)
|
|
173
|
+
try {
|
|
174
|
+
if (this.databaseAPI) {
|
|
175
|
+
const dbRows = this.databaseAPI.query(
|
|
176
|
+
"SELECT agent_id, agent_name, backend_type, publish_status FROM agents"
|
|
177
|
+
);
|
|
178
|
+
const existingIds = new Set(result.agents.map(a => a.id));
|
|
179
|
+
for (const r of dbRows) {
|
|
180
|
+
if (!existingIds.has(r.agent_id)) {
|
|
181
|
+
result.agents.push({
|
|
182
|
+
id: r.agent_id,
|
|
183
|
+
name: r.agent_name || r.agent_id,
|
|
184
|
+
backend: r.backend_type || 'others',
|
|
185
|
+
workspace: null,
|
|
186
|
+
model: null,
|
|
187
|
+
skills: [],
|
|
188
|
+
tools: { allow: [], deny: [] },
|
|
189
|
+
isVoko: false
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
} catch (e) {
|
|
195
|
+
console.error('[AgentManager] 读取 DB agents 失败:', e.message);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return result;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// 读取单个 agent 详情
|
|
202
|
+
getAgentDetail(agentId) {
|
|
203
|
+
// 先查 OpenClaw
|
|
204
|
+
const config = this._readOpenclawConfig();
|
|
205
|
+
const agents = config.agents?.list || [];
|
|
206
|
+
const agent = agents.find(a => a.id === agentId);
|
|
207
|
+
if (agent) {
|
|
208
|
+
let description = '';
|
|
209
|
+
if (agent.workspace) {
|
|
210
|
+
const workspacePath = this._expandPath(agent.workspace);
|
|
211
|
+
const agentsMdPath = path.join(workspacePath, 'AGENTS.md');
|
|
212
|
+
if (fs.existsSync(agentsMdPath)) {
|
|
213
|
+
const content = fs.readFileSync(agentsMdPath, 'utf-8');
|
|
214
|
+
const lines = content.split('\n');
|
|
215
|
+
for (const line of lines) {
|
|
216
|
+
const trimmed = line.trim();
|
|
217
|
+
if (trimmed && !trimmed.startsWith('#')) {
|
|
218
|
+
description = trimmed;
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return {
|
|
225
|
+
id: agent.id,
|
|
226
|
+
name: agent.name || agent.id,
|
|
227
|
+
workspace: agent.workspace,
|
|
228
|
+
model: agent.model,
|
|
229
|
+
skills: agent.skills || [],
|
|
230
|
+
tools: agent.tools || { allow: [], deny: [] },
|
|
231
|
+
description,
|
|
232
|
+
isVoko: agent.id === this.getVokoAgent(),
|
|
233
|
+
backend: 'openclaw'
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// 再查 Hermes
|
|
238
|
+
try {
|
|
239
|
+
const hermesProfiles = discoverHermes();
|
|
240
|
+
const profile = hermesProfiles.find(p => p.name === agentId);
|
|
241
|
+
if (profile) {
|
|
242
|
+
const profilePath = getHermesProfilePath(profile.name);
|
|
243
|
+
// 解析 SOUL.md 获取权限信息
|
|
244
|
+
let hermesTools = { allow: [], deny: [] };
|
|
245
|
+
let hermesSkills = [];
|
|
246
|
+
const soulPath = path.join(profilePath, 'SOUL.md');
|
|
247
|
+
try {
|
|
248
|
+
if (fs.existsSync(soulPath)) {
|
|
249
|
+
const soulContent = fs.readFileSync(soulPath, 'utf-8');
|
|
250
|
+
const permMatch = soulContent.match(/##\s*(权限|Permission|Permissions)[\s\S]*?(?=##|$)/);
|
|
251
|
+
if (permMatch) {
|
|
252
|
+
const permSection = permMatch[0];
|
|
253
|
+
for (const line of permSection.split('\n')) {
|
|
254
|
+
const trimmed = line.trim();
|
|
255
|
+
if (trimmed.includes('✅')) {
|
|
256
|
+
const text = trimmed.replace(/^[-*\s]*✅\s*/, '');
|
|
257
|
+
// 取括号前的内容,然后按 /、, 分割
|
|
258
|
+
const beforeBracket = text.replace(/[((【][\s\S]*$/, '');
|
|
259
|
+
const parts = beforeBracket.split(/[/、,]/);
|
|
260
|
+
for (const part of parts) {
|
|
261
|
+
const name = part.trim();
|
|
262
|
+
if (name && name.length > 1) {
|
|
263
|
+
// 只取第一个词
|
|
264
|
+
const firstWord = name.split(/\s+/)[0];
|
|
265
|
+
// 判断:如果第一个词后紧跟着 "skill" → 技能,否则→工具
|
|
266
|
+
const restAfter = name.substring(firstWord.length).trim();
|
|
267
|
+
const isSkillItem = /^skill/i.test(restAfter);
|
|
268
|
+
if (isSkillItem) {
|
|
269
|
+
if (firstWord && !hermesSkills.includes(firstWord)) hermesSkills.push(firstWord);
|
|
270
|
+
} else if (firstWord.length > 1) {
|
|
271
|
+
if (!hermesTools.allow.includes(firstWord)) hermesTools.allow.push(firstWord);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
if (trimmed.includes('❌')) {
|
|
277
|
+
const text = trimmed.replace(/^[-*\s]*❌\s*/, '');
|
|
278
|
+
const beforeBracket = text.replace(/[((【][\s\S]*$/, '');
|
|
279
|
+
const parts = beforeBracket.split(/[/、,]/);
|
|
280
|
+
for (const part of parts) {
|
|
281
|
+
let name = part.trim();
|
|
282
|
+
// 去掉后面多余的中文描述
|
|
283
|
+
name = name.replace(/\s+(skill|toolset).*$/i, '').trim();
|
|
284
|
+
// 只取第一个词(中文描述前)
|
|
285
|
+
name = name.split(/\s+/)[0];
|
|
286
|
+
if (name && name.length > 1 && !hermesTools.deny.includes(name)) {
|
|
287
|
+
hermesTools.deny.push(name);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
} catch (e) {
|
|
295
|
+
console.error('[AgentManager] 解析 SOUL.md 权限失败:', e.message);
|
|
296
|
+
}
|
|
297
|
+
return {
|
|
298
|
+
id: profile.name,
|
|
299
|
+
name: profile.name,
|
|
300
|
+
model: profile.model,
|
|
301
|
+
description: `Hermes Agent - ${profile.name}${profile.isDefault ? ' (默认)' : ''}`,
|
|
302
|
+
backend: 'hermes',
|
|
303
|
+
profilePath,
|
|
304
|
+
skills: hermesSkills,
|
|
305
|
+
tools: hermesTools
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
} catch (err) {
|
|
309
|
+
console.error('[AgentManager] Hermes 详情查询失败:', err.message);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// 回退:从 SQLite agents 表查询(MCP 注册的 agent)
|
|
313
|
+
try {
|
|
314
|
+
if (this.databaseAPI) {
|
|
315
|
+
const rows = this.databaseAPI.query(
|
|
316
|
+
"SELECT agent_id, agent_name, description, short_description, category, category_label, backend_type FROM agents WHERE agent_id = '" + agentId.replace(/'/g, "''") + "' LIMIT 1"
|
|
317
|
+
);
|
|
318
|
+
if (rows && rows.length > 0) {
|
|
319
|
+
const r = rows[0];
|
|
320
|
+
return {
|
|
321
|
+
id: r.agent_id,
|
|
322
|
+
name: r.agent_name || r.agent_id,
|
|
323
|
+
description: r.description || r.short_description || '',
|
|
324
|
+
backend: r.backend_type || 'others',
|
|
325
|
+
workspace: null,
|
|
326
|
+
model: null,
|
|
327
|
+
skills: [],
|
|
328
|
+
tools: { allow: [], deny: [] },
|
|
329
|
+
isVoko: false
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
} catch (e) {
|
|
334
|
+
console.error('[AgentManager] DB agent 详情查询失败:', e.message);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return null;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// 获取 agent workspace 下的核心文件列表
|
|
341
|
+
getAgentFiles(agentId) {
|
|
342
|
+
const config = this._readOpenclawConfig();
|
|
343
|
+
const agents = config.agents?.list || [];
|
|
344
|
+
const agent = agents.find(a => a.id === agentId);
|
|
345
|
+
if (!agent || !agent.workspace) {
|
|
346
|
+
return [];
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const workspacePath = this._expandPath(agent.workspace);
|
|
350
|
+
const coreFiles = ['AGENTS.md', 'SOUL.md', 'IDENTITY.md', 'TOOLS.md', 'USER.md', 'knowledge.md'];
|
|
351
|
+
|
|
352
|
+
try {
|
|
353
|
+
if (!fs.existsSync(workspacePath)) {
|
|
354
|
+
return [];
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const files = [];
|
|
358
|
+
for (const file of coreFiles) {
|
|
359
|
+
const filePath = path.join(workspacePath, file);
|
|
360
|
+
if (fs.existsSync(filePath)) {
|
|
361
|
+
const stat = fs.statSync(filePath);
|
|
362
|
+
files.push({
|
|
363
|
+
name: file,
|
|
364
|
+
path: filePath,
|
|
365
|
+
size: stat.size,
|
|
366
|
+
modified: stat.mtime
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// 尝试读取 skills 目录
|
|
372
|
+
const skillsDir = path.join(workspacePath, 'skills');
|
|
373
|
+
if (fs.existsSync(skillsDir)) {
|
|
374
|
+
const skillDirs = fs.readdirSync(skillsDir).filter(d => {
|
|
375
|
+
const subPath = path.join(skillsDir, d);
|
|
376
|
+
return fs.statSync(subPath).isDirectory();
|
|
377
|
+
});
|
|
378
|
+
for (const skillDir of skillDirs) {
|
|
379
|
+
const skillFile = path.join(skillsDir, skillDir, 'SKILL.md');
|
|
380
|
+
if (fs.existsSync(skillFile)) {
|
|
381
|
+
const stat = fs.statSync(skillFile);
|
|
382
|
+
files.push({
|
|
383
|
+
name: `skills/${skillDir}/SKILL.md`,
|
|
384
|
+
path: skillFile,
|
|
385
|
+
size: stat.size,
|
|
386
|
+
modified: stat.mtime
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return files;
|
|
393
|
+
} catch (err) {
|
|
394
|
+
console.error('[AgentManager] 获取 agent 文件列表失败:', err.message);
|
|
395
|
+
return [];
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// 读取单个文件内容
|
|
400
|
+
readFile(agentId, filename) {
|
|
401
|
+
const config = this._readOpenclawConfig();
|
|
402
|
+
const agents = config.agents?.list || [];
|
|
403
|
+
const agent = agents.find(a => a.id === agentId);
|
|
404
|
+
if (!agent || !agent.workspace) {
|
|
405
|
+
throw new Error('Agent not found');
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const workspacePath = this._expandPath(agent.workspace);
|
|
409
|
+
const filePath = path.join(workspacePath, filename);
|
|
410
|
+
|
|
411
|
+
// 安全检查:不允许路径遍历
|
|
412
|
+
if (!filePath.startsWith(workspacePath)) {
|
|
413
|
+
throw new Error('Invalid path');
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
try {
|
|
417
|
+
return fs.readFileSync(filePath, 'utf-8');
|
|
418
|
+
} catch (err) {
|
|
419
|
+
console.error('[AgentManager] 读取文件失败:', err.message);
|
|
420
|
+
throw err;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// 写入单个文件
|
|
425
|
+
writeFile(agentId, filename, content) {
|
|
426
|
+
const config = this._readOpenclawConfig();
|
|
427
|
+
const agents = config.agents?.list || [];
|
|
428
|
+
const agent = agents.find(a => a.id === agentId);
|
|
429
|
+
if (!agent || !agent.workspace) {
|
|
430
|
+
throw new Error('Agent not found');
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
const workspacePath = this._expandPath(agent.workspace);
|
|
434
|
+
const filePath = path.join(workspacePath, filename);
|
|
435
|
+
|
|
436
|
+
// 安全检查:不允许路径遍历
|
|
437
|
+
if (!filePath.startsWith(workspacePath)) {
|
|
438
|
+
throw new Error('Invalid path');
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
try {
|
|
442
|
+
fs.writeFileSync(filePath, content, 'utf-8');
|
|
443
|
+
return true;
|
|
444
|
+
} catch (err) {
|
|
445
|
+
console.error('[AgentManager] 写入文件失败:', err.message);
|
|
446
|
+
throw err;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// 创建新 agent
|
|
451
|
+
createAgent(agentConfig) {
|
|
452
|
+
const { id, name, desc, workspace, model } = agentConfig;
|
|
453
|
+
if (!id || !workspace) {
|
|
454
|
+
throw new Error('id 和 workspace 是必填项');
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
const config = this._readOpenclawConfig();
|
|
458
|
+
if (!config.agents) {
|
|
459
|
+
config.agents = { list: [] };
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// 检查是否已存在
|
|
463
|
+
if (config.agents.list.find(a => a.id === id)) {
|
|
464
|
+
throw new Error('Agent ID 已存在');
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// 创建 workspace 目录
|
|
468
|
+
const workspacePath = this._expandPath(workspace);
|
|
469
|
+
fs.mkdirSync(workspacePath, { recursive: true });
|
|
470
|
+
|
|
471
|
+
// 构建 AGENTS.md 内容
|
|
472
|
+
let agentsContent = `# ${name || id} Agent`;
|
|
473
|
+
if (desc) {
|
|
474
|
+
agentsContent += `\n\n> ${desc}`;
|
|
475
|
+
}
|
|
476
|
+
agentsContent += `
|
|
477
|
+
|
|
478
|
+
你是一个有用的 AI 助手。
|
|
479
|
+
|
|
480
|
+
## 核心能力
|
|
481
|
+
- ...
|
|
482
|
+
|
|
483
|
+
## 限制
|
|
484
|
+
- ...
|
|
485
|
+
`;
|
|
486
|
+
|
|
487
|
+
// 创建默认文件
|
|
488
|
+
const defaultFiles = {
|
|
489
|
+
'AGENTS.md': agentsContent,
|
|
490
|
+
'SOUL.md': `# ${name || id} - 灵魂设定
|
|
491
|
+
|
|
492
|
+
## 角色定义
|
|
493
|
+
...
|
|
494
|
+
|
|
495
|
+
## 沟通风格
|
|
496
|
+
...
|
|
497
|
+
`,
|
|
498
|
+
'IDENTITY.md': `# ${name || id}
|
|
499
|
+
|
|
500
|
+
## 基本信息
|
|
501
|
+
- 名称: ${name || id}
|
|
502
|
+
- 角色: AI 助手
|
|
503
|
+
|
|
504
|
+
## 个性
|
|
505
|
+
...
|
|
506
|
+
`,
|
|
507
|
+
'TOOLS.md': `# 工具使用规范
|
|
508
|
+
|
|
509
|
+
## 可用工具
|
|
510
|
+
...
|
|
511
|
+
`,
|
|
512
|
+
'USER.md': `# 用户画像
|
|
513
|
+
|
|
514
|
+
## 目标用户
|
|
515
|
+
...
|
|
516
|
+
`
|
|
517
|
+
};
|
|
518
|
+
|
|
519
|
+
for (const [filename, fileContent] of Object.entries(defaultFiles)) {
|
|
520
|
+
const filePath = path.join(workspacePath, filename);
|
|
521
|
+
fs.writeFileSync(filePath, fileContent, 'utf-8');
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// 添加到 openclaw.json
|
|
525
|
+
config.agents.list.push({
|
|
526
|
+
id,
|
|
527
|
+
name: name || id,
|
|
528
|
+
workspace,
|
|
529
|
+
model: model || null,
|
|
530
|
+
skills: []
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
this._writeOpenclawConfig(config);
|
|
534
|
+
return { id, name: name || id, workspace };
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// 删除 agent
|
|
538
|
+
deleteAgent(agentId) {
|
|
539
|
+
const config = this._readOpenclawConfig();
|
|
540
|
+
const idx = config.agents?.list?.findIndex(a => a.id === agentId);
|
|
541
|
+
if (idx === undefined || idx < 0) {
|
|
542
|
+
throw new Error('Agent not found');
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
const agent = config.agents.list[idx];
|
|
546
|
+
config.agents.list.splice(idx, 1);
|
|
547
|
+
this._writeOpenclawConfig(config);
|
|
548
|
+
|
|
549
|
+
// 从 routingRules 移除
|
|
550
|
+
const channelConfig = this._readChannelConfig();
|
|
551
|
+
if (channelConfig.routingRules) {
|
|
552
|
+
channelConfig.routingRules = channelConfig.routingRules.filter(r => r.agentId !== agentId);
|
|
553
|
+
this._writeChannelConfig(channelConfig);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
return true;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// 引入人工介入接口
|
|
560
|
+
injectOwnerCli(agentId) {
|
|
561
|
+
const config = this._readOpenclawConfig();
|
|
562
|
+
const agents = config.agents?.list || [];
|
|
563
|
+
const agent = agents.find(a => a.id === agentId);
|
|
564
|
+
if (!agent || !agent.workspace) {
|
|
565
|
+
throw new Error('Agent not found');
|
|
566
|
+
}
|
|
567
|
+
const workspacePath = this._expandPath(agent.workspace);
|
|
568
|
+
const scriptsDir = path.join(workspacePath, 'scripts');
|
|
569
|
+
const srcFile = path.join(__dirname, '..', '..', 'scripts', 'owner-intervention-cli.js');
|
|
570
|
+
const dstFile = path.join(scriptsDir, 'owner-intervention-cli.js');
|
|
571
|
+
if (!fs.existsSync(srcFile)) {
|
|
572
|
+
throw new Error('源文件不存在: scripts/owner-intervention-cli.js');
|
|
573
|
+
}
|
|
574
|
+
fs.mkdirSync(scriptsDir, { recursive: true });
|
|
575
|
+
fs.copyFileSync(srcFile, dstFile);
|
|
576
|
+
return true;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
module.exports = AgentManager;
|