evolclaw 2.8.0 → 2.8.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/dist/channels/aun-ops.js +275 -0
- package/dist/channels/aun.js +206 -103
- package/dist/cli.js +360 -1
- package/dist/config.js +1 -1
- package/dist/core/agent-registry.js +164 -0
- package/dist/core/command-handler.js +52 -95
- package/dist/core/evolagent-schema.js +72 -0
- package/dist/core/evolagent.js +66 -0
- package/dist/core/message/message-processor.js +17 -7
- package/dist/index.js +0 -12
- package/dist/paths.js +2 -0
- package/dist/utils/init-channel.js +91 -221
- package/dist/utils/init.js +18 -42
- package/dist/utils/logger.js +58 -2
- package/evolclaw-install-aun.md +48 -7
- package/package.json +1 -1
|
@@ -128,7 +128,6 @@ export class CommandHandler {
|
|
|
128
128
|
permissionGateway;
|
|
129
129
|
interactionRouter;
|
|
130
130
|
statsCollector;
|
|
131
|
-
hotLoadChannel;
|
|
132
131
|
agentMap;
|
|
133
132
|
defaultAgentId;
|
|
134
133
|
/** 按 agentId 获取 agent,回退到默认 */
|
|
@@ -294,9 +293,6 @@ export class CommandHandler {
|
|
|
294
293
|
setMessageQueue(messageQueue) {
|
|
295
294
|
this.messageQueue = messageQueue;
|
|
296
295
|
}
|
|
297
|
-
setHotLoadChannel(fn) {
|
|
298
|
-
this.hotLoadChannel = fn;
|
|
299
|
-
}
|
|
300
296
|
setPermissionGateway(gateway) {
|
|
301
297
|
this.permissionGateway = gateway;
|
|
302
298
|
}
|
|
@@ -442,9 +438,9 @@ export class CommandHandler {
|
|
|
442
438
|
] : []),
|
|
443
439
|
...(isOwner ? [
|
|
444
440
|
{ cmd: '/file', label: '发送项目内文件', desc: '将项目目录内的文件发送给用户' },
|
|
445
|
-
{ cmd: '/aid', label: 'AID
|
|
446
|
-
{ value: 'list', label: '列表', desc: '
|
|
447
|
-
{ value: 'new', label: '创建', desc: '创建新 AID
|
|
441
|
+
{ cmd: '/aid', label: 'AID 身份管理', desc: '管理本地 AID 身份(创建/列表)', next: { type: 'select', items: [
|
|
442
|
+
{ value: 'list', label: '列表', desc: '列出本地所有 AID' },
|
|
443
|
+
{ value: 'new', label: '创建', desc: '创建新 AID 身份', next: { type: 'text' } },
|
|
448
444
|
] } },
|
|
449
445
|
{ cmd: '/agentmd', label: '管理 agent.md', desc: '查看或更新 AUN 网络上的 agent.md 身份文件', next: { type: 'select', items: [
|
|
450
446
|
{ value: 'put', label: '上传当前', desc: '将本地 agent.md 上传到 AUN 网络' },
|
|
@@ -741,7 +737,7 @@ export class CommandHandler {
|
|
|
741
737
|
...(isOwner ? [
|
|
742
738
|
' /restart - 重启服务',
|
|
743
739
|
' /file [channel] <path> - 发送项目内文件',
|
|
744
|
-
' /aid [list|new <aid>] - AID
|
|
740
|
+
' /aid [list|new <aid>] - AID 身份管理',
|
|
745
741
|
' /agentmd [put|set <内容>] - 管理 agent.md',
|
|
746
742
|
] : []),
|
|
747
743
|
'',
|
|
@@ -1299,87 +1295,51 @@ export class CommandHandler {
|
|
|
1299
1295
|
}
|
|
1300
1296
|
return `✓ 推理强度: ${newEffort}`;
|
|
1301
1297
|
}
|
|
1302
|
-
// /aid 命令:AID
|
|
1298
|
+
// /aid 命令:AID 身份管理(list / new)
|
|
1303
1299
|
if (normalizedContent === '/aid' || normalizedContent === '/aid list' || normalizedContent.startsWith('/aid ')) {
|
|
1304
1300
|
if (!isOwner)
|
|
1305
1301
|
return '❌ 无权限:此命令仅限 owner 使用';
|
|
1306
|
-
const adapter = this.adapters.get(channel);
|
|
1307
|
-
const channelType = this.channelTypeMap.get(channel);
|
|
1308
|
-
if (channelType !== 'aun')
|
|
1309
|
-
return '❌ 此命令仅在 AUN 通道中可用';
|
|
1310
1302
|
const arg = normalizedContent.slice(4).trim();
|
|
1311
|
-
|
|
1303
|
+
const { aidList, aidCreate, agentmdPut, buildInitialAgentMd, isValidAid } = await import('../channels/aun-ops.js');
|
|
1304
|
+
// /aid 或 /aid list — 列出本地所有 AID
|
|
1312
1305
|
if (!arg || arg === 'list') {
|
|
1313
|
-
const
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
const
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
const state = connected ? '已连接' : '未连接';
|
|
1326
|
-
lines.push(` ${icon} ${inst.name} ${inst.aid} ${state}`);
|
|
1327
|
-
}
|
|
1306
|
+
const aids = aidList();
|
|
1307
|
+
if (aids.length === 0)
|
|
1308
|
+
return '本地无 AID';
|
|
1309
|
+
const lines = ['本地 AID:'];
|
|
1310
|
+
for (const a of aids) {
|
|
1311
|
+
const icons = [
|
|
1312
|
+
a.hasPrivateKey ? '🔑' : ' ',
|
|
1313
|
+
a.hasAgentMd ? '📄' : ' ',
|
|
1314
|
+
].join('');
|
|
1315
|
+
lines.push(` ${icons} ${a.aid}`);
|
|
1316
|
+
}
|
|
1317
|
+
lines.push('\n🔑=私钥 📄=agent.md');
|
|
1328
1318
|
return lines.join('\n');
|
|
1329
1319
|
}
|
|
1330
|
-
// /aid new <aid> —
|
|
1320
|
+
// /aid new <aid> — 创建 AID(纯身份,不动 config)
|
|
1331
1321
|
if (arg.startsWith('new ')) {
|
|
1332
|
-
const
|
|
1333
|
-
if (!
|
|
1334
|
-
return '用法: /aid new
|
|
1335
|
-
if (!
|
|
1336
|
-
return
|
|
1337
|
-
// Derive full AID: if no dots, append domain from current AID
|
|
1338
|
-
const selfAid = typeof adapter._selfAid === 'function' ? adapter._selfAid() : '';
|
|
1339
|
-
let fullAid = rawName;
|
|
1340
|
-
if (!rawName.includes('.')) {
|
|
1341
|
-
const domain = selfAid.split('.').slice(1).join('.');
|
|
1342
|
-
if (!domain)
|
|
1343
|
-
return '❌ 无法推导 AID 域(当前实例未连接)';
|
|
1344
|
-
fullAid = `${rawName}.${domain}`;
|
|
1345
|
-
}
|
|
1346
|
-
// Validate AID format
|
|
1347
|
-
const { isValidAid } = await import('../utils/init-channel.js');
|
|
1348
|
-
if (!isValidAid(fullAid))
|
|
1349
|
-
return `❌ 无效 AID 格式: ${fullAid}`;
|
|
1350
|
-
// Check instance name conflict
|
|
1351
|
-
const instName = rawName.includes('.') ? rawName.split('.')[0] : rawName;
|
|
1352
|
-
const { normalizeChannelInstances } = await import('../config.js');
|
|
1353
|
-
const existing = normalizeChannelInstances(this.config.channels?.aun, 'aun');
|
|
1354
|
-
if (existing.some(e => e.name === instName)) {
|
|
1355
|
-
return `❌ 实例名 "${instName}" 已存在`;
|
|
1356
|
-
}
|
|
1357
|
-
if (existing.some(e => e.aid === fullAid)) {
|
|
1358
|
-
return `❌ AID ${fullAid} 已在配置中`;
|
|
1359
|
-
}
|
|
1360
|
-
// Create AID (reuse init-channel.ts silent logic)
|
|
1322
|
+
const rawAid = arg.slice(4).trim();
|
|
1323
|
+
if (!rawAid)
|
|
1324
|
+
return '用法: /aid new <完整AID>\n例: /aid new reviewer.agentid.pub';
|
|
1325
|
+
if (!isValidAid(rawAid))
|
|
1326
|
+
return `❌ 无效 AID 格式: ${rawAid}`;
|
|
1361
1327
|
try {
|
|
1362
|
-
const
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
return '❌ 通道实例创建失败';
|
|
1378
|
-
await this.hotLoadChannel(newInstances[0]);
|
|
1379
|
-
// Write config only after successful hot-load
|
|
1380
|
-
appendAunInstance(this.config, { name: instName, aid: fullAid, owner });
|
|
1381
|
-
const verb = createResult.alreadyExisted ? '已存在,现已上线' : '已创建并上线';
|
|
1382
|
-
return `✓ ${fullAid} ${verb}\n 实例名: ${instName}\n 可在 AUN 中搜索该 AID 开始对话`;
|
|
1328
|
+
const result = await aidCreate(rawAid);
|
|
1329
|
+
if (!result.alreadyExisted) {
|
|
1330
|
+
const content = buildInitialAgentMd({ aid: rawAid });
|
|
1331
|
+
try {
|
|
1332
|
+
await agentmdPut(content, { aid: rawAid, client: result.client });
|
|
1333
|
+
}
|
|
1334
|
+
catch { /* non-fatal */ }
|
|
1335
|
+
}
|
|
1336
|
+
try {
|
|
1337
|
+
await result.client.close();
|
|
1338
|
+
}
|
|
1339
|
+
catch { /* ignore */ }
|
|
1340
|
+
const verb = result.alreadyExisted ? '已存在' : '已创建';
|
|
1341
|
+
return `✓ ${rawAid} ${verb}
|
|
1342
|
+
如需上线 AUN 通道,运行 evolclaw init aun`;
|
|
1383
1343
|
}
|
|
1384
1344
|
catch (e) {
|
|
1385
1345
|
return `❌ 创建失败: ${String(e.message || e).slice(0, 200)}`;
|
|
@@ -1387,7 +1347,7 @@ export class CommandHandler {
|
|
|
1387
1347
|
}
|
|
1388
1348
|
return '用法: /aid [list|new <aid>]';
|
|
1389
1349
|
}
|
|
1390
|
-
// /
|
|
1350
|
+
// /agentmd 命令:管理 agent.md 身份文件
|
|
1391
1351
|
if (normalizedContent === '/agentmd' || normalizedContent.startsWith('/agentmd ')) {
|
|
1392
1352
|
if (!isOwner)
|
|
1393
1353
|
return '❌ 无权限:此命令仅限 owner 使用';
|
|
@@ -1396,7 +1356,8 @@ export class CommandHandler {
|
|
|
1396
1356
|
return '❌ 当前通道不支持 agent.md 操作';
|
|
1397
1357
|
const selfAid = typeof adapter._selfAid === 'function' ? adapter._selfAid() : '';
|
|
1398
1358
|
const arg = normalizedContent.slice(9).trim();
|
|
1399
|
-
|
|
1359
|
+
const { agentmdGet, agentmdPut } = await import('../channels/aun-ops.js');
|
|
1360
|
+
// put — read local agent.md and upload to network
|
|
1400
1361
|
if (arg === 'put') {
|
|
1401
1362
|
if (!selfAid)
|
|
1402
1363
|
return '❌ 未连接,无法确定本地 AID';
|
|
@@ -1405,15 +1366,17 @@ export class CommandHandler {
|
|
|
1405
1366
|
const { join } = await import('node:path');
|
|
1406
1367
|
const { homedir } = await import('node:os');
|
|
1407
1368
|
const localPath = join(homedir(), '.aun', 'AIDs', selfAid, 'agent.md');
|
|
1369
|
+
if (!readFileSync)
|
|
1370
|
+
return '❌ 读取失败';
|
|
1408
1371
|
const content = readFileSync(localPath, 'utf-8');
|
|
1409
|
-
await
|
|
1372
|
+
await agentmdPut(content, { aid: selfAid });
|
|
1410
1373
|
return '✅ agent.md 已发布';
|
|
1411
1374
|
}
|
|
1412
1375
|
catch (e) {
|
|
1413
1376
|
return `❌ 发布失败: ${String(e.message || e).slice(0, 100)}`;
|
|
1414
1377
|
}
|
|
1415
1378
|
}
|
|
1416
|
-
// set <content> — upload inline content
|
|
1379
|
+
// set <content> — upload inline content
|
|
1417
1380
|
if (arg.startsWith('set ')) {
|
|
1418
1381
|
const content = arg.slice(4).trim();
|
|
1419
1382
|
if (!content)
|
|
@@ -1421,13 +1384,7 @@ export class CommandHandler {
|
|
|
1421
1384
|
if (!selfAid)
|
|
1422
1385
|
return '❌ 未连接,无法确定本地 AID';
|
|
1423
1386
|
try {
|
|
1424
|
-
await
|
|
1425
|
-
const { writeFileSync, mkdirSync } = await import('node:fs');
|
|
1426
|
-
const { join } = await import('node:path');
|
|
1427
|
-
const { homedir } = await import('node:os');
|
|
1428
|
-
const localDir = join(homedir(), '.aun', 'AIDs', selfAid);
|
|
1429
|
-
mkdirSync(localDir, { recursive: true });
|
|
1430
|
-
writeFileSync(join(localDir, 'agent.md'), content, 'utf-8');
|
|
1387
|
+
await agentmdPut(content, { aid: selfAid });
|
|
1431
1388
|
return '✅ agent.md 已更新并发布到AUN网络';
|
|
1432
1389
|
}
|
|
1433
1390
|
catch (e) {
|
|
@@ -1439,7 +1396,7 @@ export class CommandHandler {
|
|
|
1439
1396
|
if (!aidToView)
|
|
1440
1397
|
return '用法:/agentmd [<aid>] | put | set <内容>';
|
|
1441
1398
|
try {
|
|
1442
|
-
const md = await
|
|
1399
|
+
const md = await agentmdGet(aidToView);
|
|
1443
1400
|
if (!md || !md.trim())
|
|
1444
1401
|
return `ℹ️ ${aidToView} 尚未设置 agent.md`;
|
|
1445
1402
|
return `\`\`\`\n${md.slice(0, 1500)}\n\`\`\``;
|
|
@@ -2530,11 +2487,11 @@ export class CommandHandler {
|
|
|
2530
2487
|
return `❌ 序号超出范围 (1-${visibleSessions.length})\n使用 /s 查看可用会话`;
|
|
2531
2488
|
}
|
|
2532
2489
|
}
|
|
2533
|
-
if (!targetSession && sessionName.length
|
|
2490
|
+
if (!targetSession && sessionName.length >= 8) {
|
|
2534
2491
|
targetSession = await this.sessionManager.getSessionByUuidPrefix(channel, channelId, sessionName);
|
|
2535
2492
|
}
|
|
2536
2493
|
const canImport = policy.canImportCliSession(session?.chatType || 'private', identity.role);
|
|
2537
|
-
if (!targetSession && sessionName.length
|
|
2494
|
+
if (!targetSession && sessionName.length >= 8 && canImport) {
|
|
2538
2495
|
const projectPaths = Object.values(this.projects);
|
|
2539
2496
|
if (session) {
|
|
2540
2497
|
projectPaths.unshift(session.projectPath);
|
|
@@ -2637,7 +2594,7 @@ export class CommandHandler {
|
|
|
2637
2594
|
return `❌ 序号超出范围 (1-${visibleSessions.length})\n使用 /s 查看可用会话`;
|
|
2638
2595
|
}
|
|
2639
2596
|
}
|
|
2640
|
-
if (!targetSession && sessionName.length
|
|
2597
|
+
if (!targetSession && sessionName.length >= 8) {
|
|
2641
2598
|
targetSession = await this.sessionManager.getSessionByUuidPrefix(channel, channelId, sessionName);
|
|
2642
2599
|
}
|
|
2643
2600
|
if (!targetSession) {
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
const VALID_BASEAGENTS = new Set(['claude', 'codex', 'gemini', 'hermes']);
|
|
3
|
+
const VALID_CHANNEL_TYPES = new Set(['feishu', 'aun', 'wechat', 'wecom', 'dingtalk', 'qqbot']);
|
|
4
|
+
const VALID_CHATMODES = new Set(['interactive', 'proactive']);
|
|
5
|
+
export function validateEvolAgentConfig(raw) {
|
|
6
|
+
const errors = [];
|
|
7
|
+
if (!raw || typeof raw !== 'object') {
|
|
8
|
+
return { valid: false, errors: ['config must be an object'] };
|
|
9
|
+
}
|
|
10
|
+
if (typeof raw.name !== 'string' || raw.name.trim() === '') {
|
|
11
|
+
errors.push('name is required and must be a non-empty string');
|
|
12
|
+
}
|
|
13
|
+
if (raw.enabled !== undefined && typeof raw.enabled !== 'boolean') {
|
|
14
|
+
errors.push('enabled must be a boolean if present');
|
|
15
|
+
}
|
|
16
|
+
if (!raw.agents || typeof raw.agents !== 'object') {
|
|
17
|
+
errors.push('agents must be an object with exactly one baseagent block');
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
const keys = Object.keys(raw.agents).filter(k => VALID_BASEAGENTS.has(k));
|
|
21
|
+
const unknownKeys = Object.keys(raw.agents).filter(k => !VALID_BASEAGENTS.has(k));
|
|
22
|
+
if (unknownKeys.length > 0) {
|
|
23
|
+
errors.push(`agents contains unknown baseagent keys: ${unknownKeys.join(', ')}`);
|
|
24
|
+
}
|
|
25
|
+
if (keys.length === 0) {
|
|
26
|
+
errors.push('agents must contain exactly one of: claude | codex | gemini | hermes');
|
|
27
|
+
}
|
|
28
|
+
else if (keys.length > 1) {
|
|
29
|
+
errors.push(`agents must contain exactly one baseagent (single baseagent only), got: ${keys.join(', ')}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (!raw.channels || typeof raw.channels !== 'object') {
|
|
33
|
+
errors.push('channels is required');
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
const channelKeys = Object.keys(raw.channels);
|
|
37
|
+
if (channelKeys.length === 0) {
|
|
38
|
+
errors.push('channels must contain at least one channel type');
|
|
39
|
+
}
|
|
40
|
+
for (const key of channelKeys) {
|
|
41
|
+
if (!VALID_CHANNEL_TYPES.has(key)) {
|
|
42
|
+
errors.push(`unknown channel type: ${key}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (!raw.projects || typeof raw.projects !== 'object') {
|
|
47
|
+
errors.push('projects is required');
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
const p = raw.projects.defaultPath;
|
|
51
|
+
if (typeof p !== 'string' || p === '') {
|
|
52
|
+
errors.push('projects.defaultPath is required');
|
|
53
|
+
}
|
|
54
|
+
else if (!path.isAbsolute(p)) {
|
|
55
|
+
errors.push(`projects.defaultPath must be absolute, got: ${p}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (raw.chatmode !== undefined) {
|
|
59
|
+
if (typeof raw.chatmode !== 'object' || raw.chatmode === null) {
|
|
60
|
+
errors.push('chatmode must be an object if present');
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
for (const key of ['private', 'group']) {
|
|
64
|
+
const val = raw.chatmode[key];
|
|
65
|
+
if (val !== undefined && !VALID_CHATMODES.has(val)) {
|
|
66
|
+
errors.push(`chatmode.${key} must be 'interactive' or 'proactive'`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return { valid: errors.length === 0, errors };
|
|
72
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export class EvolAgent {
|
|
2
|
+
name;
|
|
3
|
+
configPath;
|
|
4
|
+
config;
|
|
5
|
+
isDefault;
|
|
6
|
+
channels = new Map();
|
|
7
|
+
activeSessions = 0;
|
|
8
|
+
lastActivity;
|
|
9
|
+
status;
|
|
10
|
+
error;
|
|
11
|
+
constructor(configPath, config, opts = {}) {
|
|
12
|
+
this.configPath = configPath;
|
|
13
|
+
this.config = config;
|
|
14
|
+
this.name = config.name;
|
|
15
|
+
this.isDefault = opts.isDefault === true;
|
|
16
|
+
this.status = config.enabled === false ? 'disabled' : 'stopped';
|
|
17
|
+
}
|
|
18
|
+
get baseagent() {
|
|
19
|
+
const keys = Object.keys(this.config.agents);
|
|
20
|
+
return keys[0] || 'claude';
|
|
21
|
+
}
|
|
22
|
+
get model() {
|
|
23
|
+
return this.config.agents[this.baseagent]?.model;
|
|
24
|
+
}
|
|
25
|
+
get effort() {
|
|
26
|
+
return this.config.agents[this.baseagent]?.effort;
|
|
27
|
+
}
|
|
28
|
+
get projectPath() {
|
|
29
|
+
return this.config.projects.defaultPath;
|
|
30
|
+
}
|
|
31
|
+
channelInstanceNames() {
|
|
32
|
+
const names = [];
|
|
33
|
+
for (const [type, raw] of Object.entries(this.config.channels || {})) {
|
|
34
|
+
const instances = Array.isArray(raw) ? raw : [raw];
|
|
35
|
+
for (const inst of instances) {
|
|
36
|
+
if (!inst || typeof inst !== 'object')
|
|
37
|
+
continue;
|
|
38
|
+
names.push(inst.name ?? type);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return names;
|
|
42
|
+
}
|
|
43
|
+
getContext(channelName, chatType, globalChatmode) {
|
|
44
|
+
const chatMode = this.resolveChatMode(chatType, globalChatmode);
|
|
45
|
+
return {
|
|
46
|
+
name: this.name,
|
|
47
|
+
isOwned: !this.isDefault,
|
|
48
|
+
baseagent: this.baseagent,
|
|
49
|
+
model: this.model,
|
|
50
|
+
effort: this.effort,
|
|
51
|
+
chatMode,
|
|
52
|
+
projectPath: this.projectPath,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
resolveChatMode(chatType, globalChatmode) {
|
|
56
|
+
const agentCm = this.config.chatmode;
|
|
57
|
+
const key = chatType === 'group' ? 'group' : 'private';
|
|
58
|
+
if (agentCm) {
|
|
59
|
+
return (agentCm[key] || 'interactive');
|
|
60
|
+
}
|
|
61
|
+
if (globalChatmode) {
|
|
62
|
+
return (globalChatmode[key] || 'interactive');
|
|
63
|
+
}
|
|
64
|
+
return 'interactive';
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -318,10 +318,15 @@ export class MessageProcessor {
|
|
|
318
318
|
const modeInfo = isBackground ? ' [\u540e\u53f0]' : '';
|
|
319
319
|
const e2eeInfo = message.replyContext?.metadata?.encrypted != null ? ` encrypt=${message.replyContext.metadata.encrypted}` : '';
|
|
320
320
|
logger.info(`[${message.channel}] ${message.channelId}: ${message.content}${imageInfo}${modeInfo}${e2eeInfo}`);
|
|
321
|
-
|
|
321
|
+
// 构建 peer 标识(优先 peerName,退化到 peerId / channelId)
|
|
322
|
+
const peerName = session.metadata?.peerName ?? message.peerName;
|
|
323
|
+
const peerId = session.metadata?.peerId ?? message.peerId ?? message.channelId;
|
|
324
|
+
const peerShort = peerId ? peerId.split('.')[0].split(':')[0] : '?';
|
|
325
|
+
const peerLabel = peerName && peerName !== peerShort ? `${peerShort}(${peerName})` : peerShort;
|
|
326
|
+
logger.info(`[MessageProcessor] session=${session.id} task=${taskId} peer=${peerLabel} chatType=${session.chatType} sessionMode=${session.sessionMode} agentId=${session.agentId} msgChatType=${message.chatType ?? 'n/a'}`);
|
|
322
327
|
// 记录开始处理
|
|
323
328
|
this.eventBus.publish({ type: 'message:processing', sessionId: session.id });
|
|
324
|
-
adapter.sendProcessingStatus?.(message.channelId, 'start', session.id, taskId,
|
|
329
|
+
adapter.sendProcessingStatus?.(message.channelId, 'start', session.id, taskId, taskReplyContext());
|
|
325
330
|
logger.message({
|
|
326
331
|
msgId: messageId,
|
|
327
332
|
sessionId: session.id,
|
|
@@ -531,8 +536,13 @@ export class MessageProcessor {
|
|
|
531
536
|
for (const match of fileMatches) {
|
|
532
537
|
// 兼容旧格式 (1组) 和新格式 (2组)
|
|
533
538
|
const hasChannelGroup = match.length >= 3;
|
|
534
|
-
|
|
535
|
-
|
|
539
|
+
let targetSpec = hasChannelGroup ? (match[1] ?? undefined) : undefined;
|
|
540
|
+
let filePath = (hasChannelGroup ? match[2] : match[1]).trim();
|
|
541
|
+
// 白名单校验:targetSpec 必须是已注册通道,否则视为路径的一部分(如 Windows 盘符 C:)
|
|
542
|
+
if (targetSpec && !this.channels.has(targetSpec) && !this.channelTypeMap.has(targetSpec)) {
|
|
543
|
+
filePath = `${targetSpec}:${filePath}`;
|
|
544
|
+
targetSpec = undefined;
|
|
545
|
+
}
|
|
536
546
|
if (this.isPlaceholderPath(filePath)) {
|
|
537
547
|
logger.info(`[${adapter.channelName}] Skipped placeholder file marker: [SEND_FILE:${filePath}]`);
|
|
538
548
|
continue;
|
|
@@ -639,7 +649,7 @@ export class MessageProcessor {
|
|
|
639
649
|
const errorSummary = streamResult.errors?.join('; ') || '任务执行失败';
|
|
640
650
|
const rawSubtype = streamResult.subtype || 'agent_error';
|
|
641
651
|
const errorType = prefixErrorType(ERROR_PREFIX.AGENT, rawSubtype);
|
|
642
|
-
adapter.sendProcessingStatus?.(message.channelId, 'error', session.id, taskId,
|
|
652
|
+
adapter.sendProcessingStatus?.(message.channelId, 'error', session.id, taskId, taskReplyContext());
|
|
643
653
|
this.eventBus.publish({
|
|
644
654
|
type: 'message:error',
|
|
645
655
|
sessionId: session.id,
|
|
@@ -667,7 +677,7 @@ export class MessageProcessor {
|
|
|
667
677
|
}
|
|
668
678
|
else {
|
|
669
679
|
// 真正的成功
|
|
670
|
-
adapter.sendProcessingStatus?.(message.channelId, interruptReason ? 'interrupted' : 'done', session.id, taskId,
|
|
680
|
+
adapter.sendProcessingStatus?.(message.channelId, interruptReason ? 'interrupted' : 'done', session.id, taskId, taskReplyContext());
|
|
671
681
|
await this.sessionManager.recordSuccess(session.id);
|
|
672
682
|
this.eventBus.publish({
|
|
673
683
|
type: 'message:completed',
|
|
@@ -722,7 +732,7 @@ export class MessageProcessor {
|
|
|
722
732
|
// 用户主动中断(新消息打断 或 /stop 命令)时静默,不发送中断/错误提示
|
|
723
733
|
if (!isUserInterrupt) {
|
|
724
734
|
try {
|
|
725
|
-
adapter.sendProcessingStatus?.(message.channelId, procStatus, session.id, taskId,
|
|
735
|
+
adapter.sendProcessingStatus?.(message.channelId, procStatus, session.id, taskId, taskReplyContext());
|
|
726
736
|
}
|
|
727
737
|
catch { }
|
|
728
738
|
}
|
package/dist/index.js
CHANGED
|
@@ -348,18 +348,6 @@ async function main() {
|
|
|
348
348
|
for (const inst of channelInstances) {
|
|
349
349
|
registerChannelInstance(inst);
|
|
350
350
|
}
|
|
351
|
-
// ── 设置热加载回调 ──
|
|
352
|
-
cmdHandler.setHotLoadChannel(async (inst) => {
|
|
353
|
-
registerChannelInstance(inst);
|
|
354
|
-
channelInstances.push(inst);
|
|
355
|
-
await inst.connect();
|
|
356
|
-
eventBus.publish({
|
|
357
|
-
type: 'channel:connected',
|
|
358
|
-
channel: (inst.channelType || inst.adapter.channelName).toLowerCase(),
|
|
359
|
-
channelName: inst.adapter.channelName,
|
|
360
|
-
timestamp: Date.now(),
|
|
361
|
-
});
|
|
362
|
-
});
|
|
363
351
|
// ── 连接所有渠道 ──
|
|
364
352
|
const connected = await channelLoader.connectAll(channelInstances);
|
|
365
353
|
// 预填充 Feishu 已知 thread_id(重启后避免误判话题创建)
|
package/dist/paths.js
CHANGED
|
@@ -32,6 +32,7 @@ export function resolvePaths() {
|
|
|
32
32
|
pid: path.join(root, 'logs', 'evolclaw.pid'),
|
|
33
33
|
dataDir: path.join(root, 'data'),
|
|
34
34
|
logs: path.join(root, 'logs'),
|
|
35
|
+
agentsDir: path.join(root, 'agents'),
|
|
35
36
|
lineStats: path.join(root, 'logs', 'line-stats.log'),
|
|
36
37
|
readySignal: path.join(root, 'logs', 'ready.signal'),
|
|
37
38
|
selfHealLog: path.join(root, 'logs', 'self-heal.md'),
|
|
@@ -49,6 +50,7 @@ export function ensureDataDirs() {
|
|
|
49
50
|
const p = resolvePaths();
|
|
50
51
|
fs.mkdirSync(p.dataDir, { recursive: true });
|
|
51
52
|
fs.mkdirSync(p.logs, { recursive: true });
|
|
53
|
+
fs.mkdirSync(p.agentsDir, { recursive: true });
|
|
52
54
|
}
|
|
53
55
|
export function getPackageRoot() {
|
|
54
56
|
// import.meta.dirname is available in Node.js 21.2+ and always returns
|