evolclaw 2.7.3 → 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/agents/claude-runner.js +91 -48
- package/dist/channels/aun-ops.js +275 -0
- package/dist/channels/aun.js +285 -125
- 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 +147 -97
- package/dist/core/evolagent-schema.js +72 -0
- package/dist/core/evolagent.js +66 -0
- package/dist/core/message/message-bridge.js +14 -2
- package/dist/core/message/message-processor.js +24 -10
- package/dist/core/message/thought-emitter.js +4 -2
- package/dist/core/session/session-manager.js +22 -3
- package/dist/index.js +8 -12
- package/dist/paths.js +2 -0
- package/dist/templates/prompts.md +1 -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 +2 -2
|
@@ -104,7 +104,7 @@ function formatIdleTime(ms) {
|
|
|
104
104
|
return '刚刚';
|
|
105
105
|
}
|
|
106
106
|
// 支持的命令列表
|
|
107
|
-
const commands = ['/new', '/pwd', '/plist', '/project', '/bind', '/help', '/status', '/restart', '/model', '/effort', '/agent', '/slist', '/session', '/rename', '/stop', '/clear', '/compact', '/repair', '/safe', '/fork', '/del', '/perm', '/file', '/check', '/rewind', '/activity', '/aid', '/agentmd', '/chatmode'];
|
|
107
|
+
const commands = ['/new', '/pwd', '/plist', '/project', '/bind', '/help', '/status', '/restart', '/model', '/effort', '/agent', '/slist', '/session', '/rename', '/stop', '/clear', '/compact', '/repair', '/safe', '/fork', '/del', '/perm', '/file', '/check', '/rewind', '/activity', '/aid', '/agentmd', '/chatmode', '/ask'];
|
|
108
108
|
// 命令别名映射
|
|
109
109
|
const aliases = {
|
|
110
110
|
'/p': '/project',
|
|
@@ -113,7 +113,7 @@ const aliases = {
|
|
|
113
113
|
'/rw': '/rewind'
|
|
114
114
|
};
|
|
115
115
|
// 命令快速路径前缀(所有命令都不进入消息队列)
|
|
116
|
-
const quickCommandPrefixes = ['/new', '/pwd', '/plist', '/project', '/bind', '/help', '/status', '/restart', '/model', '/effort', '/agent', '/slist', '/session', '/rename', '/repair', '/fork', '/stop', '/clear', '/compact', '/safe', '/del', '/perm', '/file', '/check', '/p ', '/s ', '/name', '/rewind', '/rw', '/rw ', '/activity', '/chatmode', '/aid', '/agentmd'];
|
|
116
|
+
const quickCommandPrefixes = ['/new', '/pwd', '/plist', '/project', '/bind', '/help', '/status', '/restart', '/model', '/effort', '/agent', '/slist', '/session', '/rename', '/repair', '/fork', '/stop', '/clear', '/compact', '/safe', '/del', '/perm', '/file', '/check', '/p ', '/s ', '/name', '/rewind', '/rw', '/rw ', '/activity', '/chatmode', '/aid', '/agentmd', '/ask'];
|
|
117
117
|
export class CommandHandler {
|
|
118
118
|
sessionManager;
|
|
119
119
|
config;
|
|
@@ -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
|
}
|
|
@@ -401,6 +397,10 @@ export class CommandHandler {
|
|
|
401
397
|
{ value: 'high', label: 'High' },
|
|
402
398
|
{ value: 'max', label: 'Max' },
|
|
403
399
|
] } },
|
|
400
|
+
{ cmd: '/chatmode', label: '切换会话模式', desc: '控制 Agent 主动性(被动响应或主动推进)', next: { type: 'select', items: [
|
|
401
|
+
{ value: 'interactive', label: '交互模式', desc: '仅在收到消息时响应' },
|
|
402
|
+
{ value: 'proactive', label: '主动模式', desc: 'Agent 可主动推进任务' },
|
|
403
|
+
] } },
|
|
404
404
|
]
|
|
405
405
|
});
|
|
406
406
|
items.push({
|
|
@@ -438,9 +438,9 @@ export class CommandHandler {
|
|
|
438
438
|
] : []),
|
|
439
439
|
...(isOwner ? [
|
|
440
440
|
{ cmd: '/file', label: '发送项目内文件', desc: '将项目目录内的文件发送给用户' },
|
|
441
|
-
{ cmd: '/aid', label: 'AID
|
|
442
|
-
{ value: 'list', label: '列表', desc: '
|
|
443
|
-
{ 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' } },
|
|
444
444
|
] } },
|
|
445
445
|
{ cmd: '/agentmd', label: '管理 agent.md', desc: '查看或更新 AUN 网络上的 agent.md 身份文件', next: { type: 'select', items: [
|
|
446
446
|
{ value: 'put', label: '上传当前', desc: '将本地 agent.md 上传到 AUN 网络' },
|
|
@@ -515,6 +515,57 @@ export class CommandHandler {
|
|
|
515
515
|
}
|
|
516
516
|
return null;
|
|
517
517
|
}
|
|
518
|
+
/** 菜单 exec 模式:查询状态或执行命令,返回结构化数据 */
|
|
519
|
+
async execMenu(cmd, mode, channel, channelId, userId) {
|
|
520
|
+
const session = await this.sessionManager.getActiveSession(channel, channelId);
|
|
521
|
+
if (!session)
|
|
522
|
+
return { error: '当前无活跃会话' };
|
|
523
|
+
const trimmed = cmd.trim();
|
|
524
|
+
const cmdBase = trimmed.split(' ')[0];
|
|
525
|
+
if (!cmdBase)
|
|
526
|
+
return { error: '缺少命令' };
|
|
527
|
+
const arg = trimmed.slice(cmdBase.length).trim();
|
|
528
|
+
if (cmdBase === '/perm') {
|
|
529
|
+
const currentMode = session.metadata?.permissionMode ?? DEFAULT_PERMISSION_MODE;
|
|
530
|
+
if (mode === 'query') {
|
|
531
|
+
return { data: { mode: currentMode } };
|
|
532
|
+
}
|
|
533
|
+
// update
|
|
534
|
+
if (!arg)
|
|
535
|
+
return { error: '缺少目标模式' };
|
|
536
|
+
const identity = this.sessionManager.resolveIdentity(channel, userId);
|
|
537
|
+
if (identity.role !== 'owner')
|
|
538
|
+
return { error: '无权限' };
|
|
539
|
+
const permAgent = this.getAgent(session.agentId);
|
|
540
|
+
const validModes = hasPermissionController(permAgent)
|
|
541
|
+
? permAgent.listModes().filter(m => m.available).map(m => m.key)
|
|
542
|
+
: ['auto', 'bypass', 'plan', 'edit', 'request', 'noask'];
|
|
543
|
+
if (!validModes.includes(arg))
|
|
544
|
+
return { error: `无效模式: ${arg}` };
|
|
545
|
+
const metadata = { ...(session.metadata || {}), permissionMode: arg };
|
|
546
|
+
await this.sessionManager.updateSession(session.id, { metadata });
|
|
547
|
+
return { data: { mode: arg } };
|
|
548
|
+
}
|
|
549
|
+
if (cmdBase === '/chatmode') {
|
|
550
|
+
const currentMode = session.sessionMode || 'interactive';
|
|
551
|
+
if (mode === 'query') {
|
|
552
|
+
return { data: { mode: currentMode } };
|
|
553
|
+
}
|
|
554
|
+
// update
|
|
555
|
+
if (!arg)
|
|
556
|
+
return { error: '缺少目标模式' };
|
|
557
|
+
if (arg !== 'interactive' && arg !== 'proactive')
|
|
558
|
+
return { error: `无效模式: ${arg}` };
|
|
559
|
+
const identity = this.sessionManager.resolveIdentity(channel, userId);
|
|
560
|
+
const chatType = session.chatType || 'private';
|
|
561
|
+
if (chatType === 'group' && identity.role !== 'owner' && identity.role !== 'admin') {
|
|
562
|
+
return { error: '无权限:群聊中仅管理员可切换' };
|
|
563
|
+
}
|
|
564
|
+
await this.sessionManager.updateSession(session.id, { sessionMode: arg });
|
|
565
|
+
return { data: { mode: arg } };
|
|
566
|
+
}
|
|
567
|
+
return { error: `不支持 exec 模式: ${cmdBase}` };
|
|
568
|
+
}
|
|
518
569
|
isCommand(content) {
|
|
519
570
|
return content === '/p' || content === '/s' || quickCommandPrefixes.some(cmd => content.startsWith(cmd));
|
|
520
571
|
}
|
|
@@ -686,7 +737,7 @@ export class CommandHandler {
|
|
|
686
737
|
...(isOwner ? [
|
|
687
738
|
' /restart - 重启服务',
|
|
688
739
|
' /file [channel] <path> - 发送项目内文件',
|
|
689
|
-
' /aid [list|new <aid>] - AID
|
|
740
|
+
' /aid [list|new <aid>] - AID 身份管理',
|
|
690
741
|
' /agentmd [put|set <内容>] - 管理 agent.md',
|
|
691
742
|
] : []),
|
|
692
743
|
'',
|
|
@@ -817,6 +868,31 @@ export class CommandHandler {
|
|
|
817
868
|
const allModeKeys = hasPermissionController(permAgent) ? permAgent.listModes().map(m => m.key).join('|') : 'auto|bypass|request|edit|plan|noask';
|
|
818
869
|
return `❌ 未知参数: ${args}\n用法: /perm <${allModeKeys}> 或 /perm allow|always|deny`;
|
|
819
870
|
}
|
|
871
|
+
// /ask 命令:回答 AskUserQuestion / ExitPlanMode 的交互式问题
|
|
872
|
+
if (normalizedContent.startsWith('/ask')) {
|
|
873
|
+
const args = normalizedContent.slice(4).trim();
|
|
874
|
+
if (!args) {
|
|
875
|
+
// 无参数:列出当前 pending 的交互请求
|
|
876
|
+
const askResult = await this.ensureSession(channel, channelId, threadId);
|
|
877
|
+
if ('error' in askResult)
|
|
878
|
+
return askResult.error;
|
|
879
|
+
const pendingIds = this.interactionRouter?.getPending(askResult.session.id) || [];
|
|
880
|
+
if (pendingIds.length === 0)
|
|
881
|
+
return '当前没有待回答的问题';
|
|
882
|
+
return `当前有 ${pendingIds.length} 个待回答问题,请回复 /ask <选项>`;
|
|
883
|
+
}
|
|
884
|
+
const askResult = await this.ensureSession(channel, channelId, threadId);
|
|
885
|
+
if ('error' in askResult)
|
|
886
|
+
return askResult.error;
|
|
887
|
+
const { session: askSession } = askResult;
|
|
888
|
+
const pendingIds = this.interactionRouter?.getPending(askSession.id) || [];
|
|
889
|
+
if (pendingIds.length === 0)
|
|
890
|
+
return '❌ 当前没有待回答的问题';
|
|
891
|
+
// 路由到最早的 pending interaction
|
|
892
|
+
const targetId = pendingIds[0];
|
|
893
|
+
this.interactionRouter.handle({ type: 'interaction.response', id: targetId, action: args, operatorId: userId });
|
|
894
|
+
return `✓ 已回答`;
|
|
895
|
+
}
|
|
820
896
|
// /agent 命令:查看或切换 Agent 后端
|
|
821
897
|
if (normalizedContent === '/agent' || normalizedContent.startsWith('/agent ')) {
|
|
822
898
|
const args = normalizedContent.slice(6).trim();
|
|
@@ -1219,87 +1295,51 @@ export class CommandHandler {
|
|
|
1219
1295
|
}
|
|
1220
1296
|
return `✓ 推理强度: ${newEffort}`;
|
|
1221
1297
|
}
|
|
1222
|
-
// /aid 命令:AID
|
|
1298
|
+
// /aid 命令:AID 身份管理(list / new)
|
|
1223
1299
|
if (normalizedContent === '/aid' || normalizedContent === '/aid list' || normalizedContent.startsWith('/aid ')) {
|
|
1224
1300
|
if (!isOwner)
|
|
1225
1301
|
return '❌ 无权限:此命令仅限 owner 使用';
|
|
1226
|
-
const adapter = this.adapters.get(channel);
|
|
1227
|
-
const channelType = this.channelTypeMap.get(channel);
|
|
1228
|
-
if (channelType !== 'aun')
|
|
1229
|
-
return '❌ 此命令仅在 AUN 通道中可用';
|
|
1230
1302
|
const arg = normalizedContent.slice(4).trim();
|
|
1231
|
-
|
|
1303
|
+
const { aidList, aidCreate, agentmdPut, buildInitialAgentMd, isValidAid } = await import('../channels/aun-ops.js');
|
|
1304
|
+
// /aid 或 /aid list — 列出本地所有 AID
|
|
1232
1305
|
if (!arg || arg === 'list') {
|
|
1233
|
-
const
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
const
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
const state = connected ? '已连接' : '未连接';
|
|
1246
|
-
lines.push(` ${icon} ${inst.name} ${inst.aid} ${state}`);
|
|
1247
|
-
}
|
|
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');
|
|
1248
1318
|
return lines.join('\n');
|
|
1249
1319
|
}
|
|
1250
|
-
// /aid new <aid> —
|
|
1320
|
+
// /aid new <aid> — 创建 AID(纯身份,不动 config)
|
|
1251
1321
|
if (arg.startsWith('new ')) {
|
|
1252
|
-
const
|
|
1253
|
-
if (!
|
|
1254
|
-
return '用法: /aid new
|
|
1255
|
-
if (!
|
|
1256
|
-
return
|
|
1257
|
-
// Derive full AID: if no dots, append domain from current AID
|
|
1258
|
-
const selfAid = typeof adapter._selfAid === 'function' ? adapter._selfAid() : '';
|
|
1259
|
-
let fullAid = rawName;
|
|
1260
|
-
if (!rawName.includes('.')) {
|
|
1261
|
-
const domain = selfAid.split('.').slice(1).join('.');
|
|
1262
|
-
if (!domain)
|
|
1263
|
-
return '❌ 无法推导 AID 域(当前实例未连接)';
|
|
1264
|
-
fullAid = `${rawName}.${domain}`;
|
|
1265
|
-
}
|
|
1266
|
-
// Validate AID format
|
|
1267
|
-
const { isValidAid } = await import('../utils/init-channel.js');
|
|
1268
|
-
if (!isValidAid(fullAid))
|
|
1269
|
-
return `❌ 无效 AID 格式: ${fullAid}`;
|
|
1270
|
-
// Check instance name conflict
|
|
1271
|
-
const instName = rawName.includes('.') ? rawName.split('.')[0] : rawName;
|
|
1272
|
-
const { normalizeChannelInstances } = await import('../config.js');
|
|
1273
|
-
const existing = normalizeChannelInstances(this.config.channels?.aun, 'aun');
|
|
1274
|
-
if (existing.some(e => e.name === instName)) {
|
|
1275
|
-
return `❌ 实例名 "${instName}" 已存在`;
|
|
1276
|
-
}
|
|
1277
|
-
if (existing.some(e => e.aid === fullAid)) {
|
|
1278
|
-
return `❌ AID ${fullAid} 已在配置中`;
|
|
1279
|
-
}
|
|
1280
|
-
// 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}`;
|
|
1281
1327
|
try {
|
|
1282
|
-
const
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
return '❌ 通道实例创建失败';
|
|
1298
|
-
await this.hotLoadChannel(newInstances[0]);
|
|
1299
|
-
// Write config only after successful hot-load
|
|
1300
|
-
appendAunInstance(this.config, { name: instName, aid: fullAid, owner });
|
|
1301
|
-
const verb = createResult.alreadyExisted ? '已存在,现已上线' : '已创建并上线';
|
|
1302
|
-
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`;
|
|
1303
1343
|
}
|
|
1304
1344
|
catch (e) {
|
|
1305
1345
|
return `❌ 创建失败: ${String(e.message || e).slice(0, 200)}`;
|
|
@@ -1307,7 +1347,7 @@ export class CommandHandler {
|
|
|
1307
1347
|
}
|
|
1308
1348
|
return '用法: /aid [list|new <aid>]';
|
|
1309
1349
|
}
|
|
1310
|
-
// /
|
|
1350
|
+
// /agentmd 命令:管理 agent.md 身份文件
|
|
1311
1351
|
if (normalizedContent === '/agentmd' || normalizedContent.startsWith('/agentmd ')) {
|
|
1312
1352
|
if (!isOwner)
|
|
1313
1353
|
return '❌ 无权限:此命令仅限 owner 使用';
|
|
@@ -1316,7 +1356,8 @@ export class CommandHandler {
|
|
|
1316
1356
|
return '❌ 当前通道不支持 agent.md 操作';
|
|
1317
1357
|
const selfAid = typeof adapter._selfAid === 'function' ? adapter._selfAid() : '';
|
|
1318
1358
|
const arg = normalizedContent.slice(9).trim();
|
|
1319
|
-
|
|
1359
|
+
const { agentmdGet, agentmdPut } = await import('../channels/aun-ops.js');
|
|
1360
|
+
// put — read local agent.md and upload to network
|
|
1320
1361
|
if (arg === 'put') {
|
|
1321
1362
|
if (!selfAid)
|
|
1322
1363
|
return '❌ 未连接,无法确定本地 AID';
|
|
@@ -1325,15 +1366,17 @@ export class CommandHandler {
|
|
|
1325
1366
|
const { join } = await import('node:path');
|
|
1326
1367
|
const { homedir } = await import('node:os');
|
|
1327
1368
|
const localPath = join(homedir(), '.aun', 'AIDs', selfAid, 'agent.md');
|
|
1369
|
+
if (!readFileSync)
|
|
1370
|
+
return '❌ 读取失败';
|
|
1328
1371
|
const content = readFileSync(localPath, 'utf-8');
|
|
1329
|
-
await
|
|
1372
|
+
await agentmdPut(content, { aid: selfAid });
|
|
1330
1373
|
return '✅ agent.md 已发布';
|
|
1331
1374
|
}
|
|
1332
1375
|
catch (e) {
|
|
1333
1376
|
return `❌ 发布失败: ${String(e.message || e).slice(0, 100)}`;
|
|
1334
1377
|
}
|
|
1335
1378
|
}
|
|
1336
|
-
// set <content> — upload inline content
|
|
1379
|
+
// set <content> — upload inline content
|
|
1337
1380
|
if (arg.startsWith('set ')) {
|
|
1338
1381
|
const content = arg.slice(4).trim();
|
|
1339
1382
|
if (!content)
|
|
@@ -1341,13 +1384,7 @@ export class CommandHandler {
|
|
|
1341
1384
|
if (!selfAid)
|
|
1342
1385
|
return '❌ 未连接,无法确定本地 AID';
|
|
1343
1386
|
try {
|
|
1344
|
-
await
|
|
1345
|
-
const { writeFileSync, mkdirSync } = await import('node:fs');
|
|
1346
|
-
const { join } = await import('node:path');
|
|
1347
|
-
const { homedir } = await import('node:os');
|
|
1348
|
-
const localDir = join(homedir(), '.aun', 'AIDs', selfAid);
|
|
1349
|
-
mkdirSync(localDir, { recursive: true });
|
|
1350
|
-
writeFileSync(join(localDir, 'agent.md'), content, 'utf-8');
|
|
1387
|
+
await agentmdPut(content, { aid: selfAid });
|
|
1351
1388
|
return '✅ agent.md 已更新并发布到AUN网络';
|
|
1352
1389
|
}
|
|
1353
1390
|
catch (e) {
|
|
@@ -1359,7 +1396,7 @@ export class CommandHandler {
|
|
|
1359
1396
|
if (!aidToView)
|
|
1360
1397
|
return '用法:/agentmd [<aid>] | put | set <内容>';
|
|
1361
1398
|
try {
|
|
1362
|
-
const md = await
|
|
1399
|
+
const md = await agentmdGet(aidToView);
|
|
1363
1400
|
if (!md || !md.trim())
|
|
1364
1401
|
return `ℹ️ ${aidToView} 尚未设置 agent.md`;
|
|
1365
1402
|
return `\`\`\`\n${md.slice(0, 1500)}\n\`\`\``;
|
|
@@ -2450,11 +2487,11 @@ export class CommandHandler {
|
|
|
2450
2487
|
return `❌ 序号超出范围 (1-${visibleSessions.length})\n使用 /s 查看可用会话`;
|
|
2451
2488
|
}
|
|
2452
2489
|
}
|
|
2453
|
-
if (!targetSession && sessionName.length
|
|
2490
|
+
if (!targetSession && sessionName.length >= 8) {
|
|
2454
2491
|
targetSession = await this.sessionManager.getSessionByUuidPrefix(channel, channelId, sessionName);
|
|
2455
2492
|
}
|
|
2456
2493
|
const canImport = policy.canImportCliSession(session?.chatType || 'private', identity.role);
|
|
2457
|
-
if (!targetSession && sessionName.length
|
|
2494
|
+
if (!targetSession && sessionName.length >= 8 && canImport) {
|
|
2458
2495
|
const projectPaths = Object.values(this.projects);
|
|
2459
2496
|
if (session) {
|
|
2460
2497
|
projectPaths.unshift(session.projectPath);
|
|
@@ -2557,7 +2594,7 @@ export class CommandHandler {
|
|
|
2557
2594
|
return `❌ 序号超出范围 (1-${visibleSessions.length})\n使用 /s 查看可用会话`;
|
|
2558
2595
|
}
|
|
2559
2596
|
}
|
|
2560
|
-
if (!targetSession && sessionName.length
|
|
2597
|
+
if (!targetSession && sessionName.length >= 8) {
|
|
2561
2598
|
targetSession = await this.sessionManager.getSessionByUuidPrefix(channel, channelId, sessionName);
|
|
2562
2599
|
}
|
|
2563
2600
|
if (!targetSession) {
|
|
@@ -2809,6 +2846,7 @@ export class CommandHandler {
|
|
|
2809
2846
|
* 从 session 恢复 ReplyContext,用于 ctl send 主动发送文本时的路由
|
|
2810
2847
|
* - 群聊话题:metadata.replyContext.{threadId,peerId}
|
|
2811
2848
|
* - 私聊:metadata.peerId
|
|
2849
|
+
* - taskId/chatmode:从 processing_state 和 sessionMode 注入
|
|
2812
2850
|
*/
|
|
2813
2851
|
buildCtlReplyContext(session) {
|
|
2814
2852
|
const ctx = {};
|
|
@@ -2819,6 +2857,18 @@ export class CommandHandler {
|
|
|
2819
2857
|
ctx.peerId = meta.replyContext.peerId;
|
|
2820
2858
|
if (!ctx.peerId && meta?.peerId)
|
|
2821
2859
|
ctx.peerId = meta.peerId;
|
|
2860
|
+
const taskId = this.sessionManager.getActiveTaskId(session.id);
|
|
2861
|
+
const chatmode = session.sessionMode || 'interactive';
|
|
2862
|
+
const encrypted = this.sessionManager.getSessionEncrypt(session.id);
|
|
2863
|
+
if (taskId || chatmode !== 'interactive' || encrypted != null) {
|
|
2864
|
+
ctx.metadata = {};
|
|
2865
|
+
if (taskId)
|
|
2866
|
+
ctx.metadata.taskId = taskId;
|
|
2867
|
+
if (chatmode !== 'interactive')
|
|
2868
|
+
ctx.metadata.chatmode = chatmode;
|
|
2869
|
+
if (encrypted != null)
|
|
2870
|
+
ctx.metadata.encrypted = encrypted;
|
|
2871
|
+
}
|
|
2822
2872
|
return Object.keys(ctx).length > 0 ? ctx : undefined;
|
|
2823
2873
|
}
|
|
2824
2874
|
/**
|
|
@@ -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
|
+
}
|
|
@@ -151,8 +151,19 @@ export class MessageBridge {
|
|
|
151
151
|
if (!parsed || typeof parsed !== 'object' || !parsed.type)
|
|
152
152
|
return false;
|
|
153
153
|
if (parsed.type === 'menu.query') {
|
|
154
|
-
|
|
155
|
-
|
|
154
|
+
if (parsed.cmd && (parsed.mode === 'query' || parsed.mode === 'update')) {
|
|
155
|
+
// exec 模式:查询状态或执行命令
|
|
156
|
+
const result = await this.cmdHandler.execMenu(parsed.cmd, parsed.mode, channel, msg.channelId, msg.peerId);
|
|
157
|
+
const base = { type: 'menu.response', cmd: parsed.cmd };
|
|
158
|
+
const response = JSON.stringify('error' in result ? { ...base, error: result.error } : { ...base, data: result.data });
|
|
159
|
+
if (adapter?.sendCustomPayload) {
|
|
160
|
+
adapter.sendCustomPayload(msg.channelId, response);
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
await sendReply(msg.channelId, response);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
else if (parsed.cmd) {
|
|
156
167
|
// 动态子菜单查询
|
|
157
168
|
const items = await this.cmdHandler.getSubMenuItems(parsed.cmd, channel, msg.channelId, msg.peerId);
|
|
158
169
|
const response = JSON.stringify({ type: 'menu.response', cmd: parsed.cmd, items: items ?? [] });
|
|
@@ -165,6 +176,7 @@ export class MessageBridge {
|
|
|
165
176
|
}
|
|
166
177
|
else {
|
|
167
178
|
// 全量菜单
|
|
179
|
+
const identity = this.sessionManager.resolveIdentity(channel, msg.peerId);
|
|
168
180
|
const items = this.cmdHandler.getMenuItems(identity.role, msg.chatType || 'private');
|
|
169
181
|
const response = JSON.stringify({ type: 'menu.response', items });
|
|
170
182
|
if (adapter?.sendCustomPayload) {
|