evolclaw 2.6.1 → 2.6.2
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.js
CHANGED
|
@@ -58,9 +58,16 @@ export class AUNChannel {
|
|
|
58
58
|
const logPath = path.join(resolvePaths().logs, `aun-${today}.log`);
|
|
59
59
|
this.traceStream = fs.createWriteStream(logPath, { flags: 'a' });
|
|
60
60
|
}
|
|
61
|
-
/** 判断 channelId 是否为群组 ID
|
|
61
|
+
/** 判断 channelId 是否为群组 ID
|
|
62
|
+
* - 新格式:group.{issuer}/{group_no|group_name}
|
|
63
|
+
* - 数字群号:{group_no}.{issuer}(如 11117.agentid.pub)
|
|
64
|
+
* - 兼容旧格式:grp_xxx、g-xxx.agentid.pub
|
|
65
|
+
*/
|
|
62
66
|
isGroupId(id) {
|
|
63
|
-
return id.startsWith('
|
|
67
|
+
return (id.startsWith('group.') && id.includes('/'))
|
|
68
|
+
|| /^\d+\./.test(id)
|
|
69
|
+
|| id.startsWith('grp_')
|
|
70
|
+
|| (id.startsWith('g-') && id.includes('.'));
|
|
64
71
|
}
|
|
65
72
|
getShortAid(aid) {
|
|
66
73
|
if (!aid)
|
|
@@ -114,6 +121,7 @@ export class AUNChannel {
|
|
|
114
121
|
this.messageSeqMap.delete(messageId);
|
|
115
122
|
}
|
|
116
123
|
_aid;
|
|
124
|
+
_selfName; // 本地 agent.md 中的 name,首次 connect 时读取
|
|
117
125
|
_chatId = ''; // aid:device_id:slot_id — 多实例回声过滤
|
|
118
126
|
seenMessages = new Map();
|
|
119
127
|
peerInfoCache = new Map();
|
|
@@ -262,6 +270,7 @@ export class AUNChannel {
|
|
|
262
270
|
this._aid = this.client.aid ?? undefined;
|
|
263
271
|
const deviceId = this.client._device_id ?? '';
|
|
264
272
|
this._chatId = this._aid ? `${this._aid}:${deviceId}:` : '';
|
|
273
|
+
this._selfName = this.loadSelfName(aidName);
|
|
265
274
|
this.connected = true;
|
|
266
275
|
this.reconnectAttempt = 0;
|
|
267
276
|
// Workaround: SDK e2ee uses _identity.cert for sender_cert_fingerprint;
|
|
@@ -388,7 +397,21 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
388
397
|
- 所有命令以 \`/\` 开头
|
|
389
398
|
|
|
390
399
|
现在,请先使用 \`/bind\` 命令绑定您的项目目录,然后就可以开始工作了!`;
|
|
391
|
-
|
|
400
|
+
// First contact with Owner races against Owner's async cert fetch from
|
|
401
|
+
// gateway PKI; a 3s pause lets the cert propagate. persist_required asks
|
|
402
|
+
// the gateway to durably store the message so Owner can recover it via
|
|
403
|
+
// pull if the initial E2EE push still arrives before the cert resolves.
|
|
404
|
+
await new Promise(resolve => setTimeout(resolve, 3000));
|
|
405
|
+
if (!this.client) {
|
|
406
|
+
logger.warn('[AUN] Client disconnected before welcome message could be sent');
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
await this.client.call('message.send', {
|
|
410
|
+
to: owner,
|
|
411
|
+
payload: { type: 'text', text: welcomeText },
|
|
412
|
+
encrypt: true,
|
|
413
|
+
persist_required: true,
|
|
414
|
+
});
|
|
392
415
|
logger.info(`[AUN] Welcome message sent to owner: ${owner}`);
|
|
393
416
|
}
|
|
394
417
|
catch (e) {
|
|
@@ -725,7 +748,8 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
725
748
|
const payload = { type: 'text', text: finalText };
|
|
726
749
|
if (context?.threadId)
|
|
727
750
|
payload.thread_id = context.threadId;
|
|
728
|
-
const
|
|
751
|
+
const isGroup = this.isGroupId(channelId);
|
|
752
|
+
const params = { payload, encrypt: !isGroup };
|
|
729
753
|
// Multi-instance routing: channelId may be "aid:device_id:slot_id"
|
|
730
754
|
const colonIdx = channelId.indexOf(':');
|
|
731
755
|
const targetAid = colonIdx > 0 ? channelId.substring(0, colonIdx) : channelId;
|
|
@@ -733,7 +757,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
733
757
|
params.payload.chat_id = channelId;
|
|
734
758
|
}
|
|
735
759
|
try {
|
|
736
|
-
if (
|
|
760
|
+
if (isGroup) {
|
|
737
761
|
params.group_id = channelId;
|
|
738
762
|
this.trace('OUT', 'group.send', params);
|
|
739
763
|
await this.client.call('group.send', params);
|
|
@@ -783,8 +807,14 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
783
807
|
}
|
|
784
808
|
}
|
|
785
809
|
catch (e) {
|
|
786
|
-
|
|
787
|
-
|
|
810
|
+
const err = e;
|
|
811
|
+
this.trace('OUT', 'thought.put.error', {
|
|
812
|
+
channelId,
|
|
813
|
+
errorName: err?.name,
|
|
814
|
+
errorCode: err?.code,
|
|
815
|
+
errorMessage: err?.message,
|
|
816
|
+
});
|
|
817
|
+
logger.debug(`[AUN] thought.put failed to ${channelId}: ${err?.name}(${err?.code})=${err?.message}`);
|
|
788
818
|
}
|
|
789
819
|
}
|
|
790
820
|
async sendFile(channelId, filePath, context) {
|
|
@@ -860,14 +890,15 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
860
890
|
};
|
|
861
891
|
if (context?.threadId)
|
|
862
892
|
filePayload.thread_id = context.threadId;
|
|
863
|
-
const
|
|
893
|
+
const isGroup = this.isGroupId(channelId);
|
|
894
|
+
const params = { payload: filePayload, encrypt: !isGroup };
|
|
864
895
|
// Multi-instance routing
|
|
865
896
|
const fileColonIdx = channelId.indexOf(':');
|
|
866
897
|
const fileTargetAid = fileColonIdx > 0 ? channelId.substring(0, fileColonIdx) : channelId;
|
|
867
898
|
if (fileColonIdx > 0) {
|
|
868
899
|
params.payload.chat_id = channelId;
|
|
869
900
|
}
|
|
870
|
-
if (
|
|
901
|
+
if (isGroup) {
|
|
871
902
|
params.group_id = channelId;
|
|
872
903
|
this.trace('OUT', 'group.send.file', params);
|
|
873
904
|
await this.client.call('group.send', params);
|
|
@@ -909,14 +940,15 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
909
940
|
};
|
|
910
941
|
if (context?.threadId)
|
|
911
942
|
payload.thread_id = context.threadId;
|
|
912
|
-
const
|
|
943
|
+
const isGroup = this.isGroupId(channelId);
|
|
944
|
+
const params = { payload, encrypt: !isGroup };
|
|
913
945
|
// Multi-instance routing
|
|
914
946
|
const statusColonIdx = channelId.indexOf(':');
|
|
915
947
|
const statusTargetAid = statusColonIdx > 0 ? channelId.substring(0, statusColonIdx) : channelId;
|
|
916
948
|
if (statusColonIdx > 0) {
|
|
917
949
|
payload.chat_id = channelId;
|
|
918
950
|
}
|
|
919
|
-
if (
|
|
951
|
+
if (isGroup) {
|
|
920
952
|
params.group_id = channelId;
|
|
921
953
|
this.trace('OUT', 'group.send.status', params);
|
|
922
954
|
this.client.call('group.send', params).catch(e => {
|
|
@@ -1038,6 +1070,27 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1038
1070
|
maxAttempts: AUNChannel.RECONNECT_DELAYS.length,
|
|
1039
1071
|
};
|
|
1040
1072
|
}
|
|
1073
|
+
/** 读取本地 agent.md 中的 name(用于身份上下文展示) */
|
|
1074
|
+
loadSelfName(aid) {
|
|
1075
|
+
try {
|
|
1076
|
+
const aidName = aid.startsWith('@') ? aid.slice(1) : aid;
|
|
1077
|
+
const agentMdPath = path.join(os.homedir(), '.aun', 'AIDs', aidName, 'agent.md');
|
|
1078
|
+
if (!fs.existsSync(agentMdPath))
|
|
1079
|
+
return undefined;
|
|
1080
|
+
const content = fs.readFileSync(agentMdPath, 'utf-8');
|
|
1081
|
+
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
1082
|
+
if (!fmMatch)
|
|
1083
|
+
return undefined;
|
|
1084
|
+
const nameMatch = fmMatch[1].match(/^name:\s*["']?(.+?)["']?\s*$/m);
|
|
1085
|
+
return nameMatch?.[1]?.trim() || undefined;
|
|
1086
|
+
}
|
|
1087
|
+
catch {
|
|
1088
|
+
return undefined;
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
getSelfName() {
|
|
1092
|
+
return this._selfName;
|
|
1093
|
+
}
|
|
1041
1094
|
async fetchPeerInfo(aid) {
|
|
1042
1095
|
const cached = this.peerInfoCache.get(aid);
|
|
1043
1096
|
if (cached !== undefined)
|
|
@@ -1109,6 +1162,7 @@ export class AUNChannelPlugin {
|
|
|
1109
1162
|
downloadAgentMd: (aid) => channel.downloadAgentMd(aid),
|
|
1110
1163
|
putThought: (id, taskId, payload) => channel.sendThought(id, taskId, payload),
|
|
1111
1164
|
_selfAid: () => channel.getStatus().aid,
|
|
1165
|
+
_selfName: () => channel.getSelfName(),
|
|
1112
1166
|
};
|
|
1113
1167
|
const policy = {
|
|
1114
1168
|
canSwitchProject: (chatType, identity) => identity === 'owner' || identity === 'admin',
|
|
@@ -525,10 +525,10 @@ export class CommandHandler {
|
|
|
525
525
|
const isAdmin = identity.role === 'owner' || identity.role === 'admin';
|
|
526
526
|
const activeChatType = activeSession?.chatType || 'private';
|
|
527
527
|
if (normalizedContent.startsWith('/')) {
|
|
528
|
-
const guestGroupCommands = ['/status', '/help', '/check'];
|
|
528
|
+
const guestGroupCommands = ['/status', '/help', '/check', '/chatmode'];
|
|
529
529
|
const userCommands = activeChatType === 'group' && !isAdmin
|
|
530
530
|
? guestGroupCommands
|
|
531
|
-
: ['/slist', '/new', '/session', '/rename', '/name', '/status', '/help', '/del', '/s ', '/check'];
|
|
531
|
+
: ['/slist', '/new', '/session', '/rename', '/name', '/status', '/help', '/del', '/s ', '/check', '/chatmode'];
|
|
532
532
|
const isUserCommand = userCommands.some(cmd => normalizedContent === cmd.trimEnd() || normalizedContent.startsWith(cmd));
|
|
533
533
|
if (!isUserCommand && !isAdmin) {
|
|
534
534
|
return activeChatType === 'group'
|
|
@@ -1386,9 +1386,9 @@ export class CommandHandler {
|
|
|
1386
1386
|
return `✅ 中间输出模式: ${activityArg}(${label})`;
|
|
1387
1387
|
}
|
|
1388
1388
|
// /chatmode 命令:查看/切换 session 会话模式(interactive | proactive)
|
|
1389
|
+
// - 查看:所有人可用
|
|
1390
|
+
// - 设置:单聊任何角色可设置;群聊仅管理员可设置
|
|
1389
1391
|
if (normalizedContent === '/chatmode' || normalizedContent.startsWith('/chatmode ')) {
|
|
1390
|
-
if (!isAdmin)
|
|
1391
|
-
return '❌ 无权限:此命令仅限管理员使用';
|
|
1392
1392
|
if (!activeSession)
|
|
1393
1393
|
return '❌ 当前无活跃会话';
|
|
1394
1394
|
const lockedMode = getChannelSessionMode(this.config, channel);
|
|
@@ -1401,6 +1401,9 @@ export class CommandHandler {
|
|
|
1401
1401
|
if (arg !== 'interactive' && arg !== 'proactive') {
|
|
1402
1402
|
return `❌ 无效模式: ${arg}\n可选: interactive / proactive`;
|
|
1403
1403
|
}
|
|
1404
|
+
if (activeChatType === 'group' && !isAdmin) {
|
|
1405
|
+
return '❌ 无权限:群聊中切换会话模式仅限管理员使用';
|
|
1406
|
+
}
|
|
1404
1407
|
if (lockedMode) {
|
|
1405
1408
|
return `❌ 会话模式由通道配置锁定为 ${lockedMode},无法切换`;
|
|
1406
1409
|
}
|
|
@@ -1551,15 +1554,18 @@ export class CommandHandler {
|
|
|
1551
1554
|
}
|
|
1552
1555
|
}
|
|
1553
1556
|
const lines = [];
|
|
1557
|
+
const sessionMode = session.sessionMode || 'interactive';
|
|
1558
|
+
const lockedMode = getChannelSessionMode(this.config, channel);
|
|
1559
|
+
const chatModeLine = `会话模式: ${sessionMode}${lockedMode ? '(通道锁定)' : ''}`;
|
|
1554
1560
|
if (isAdmin) {
|
|
1555
|
-
lines.push(`📊 ${isThread ? '话题' : '会话'}状态:`, `渠道: ${this.resolveChannelType(channel)} / 项目: ${projectName} / 会话: ${session.name || '(未命名)'}`, `会话ID: ${session.id}`, `项目路径: ${session.projectPath}`, `会话状态: ${sessionStatus}`, `会话轮数: ${sessionTurns}`);
|
|
1561
|
+
lines.push(`📊 ${isThread ? '话题' : '会话'}状态:`, `渠道: ${this.resolveChannelType(channel)} / 项目: ${projectName} / 会话: ${session.name || '(未命名)'}`, `会话ID: ${session.id}`, `项目路径: ${session.projectPath}`, `会话状态: ${sessionStatus}`, chatModeLine, `会话轮数: ${sessionTurns}`);
|
|
1556
1562
|
if (health.consecutiveErrors > 0) {
|
|
1557
1563
|
lines.push(`异常计数: ${health.consecutiveErrors}`);
|
|
1558
1564
|
}
|
|
1559
1565
|
lines.push(`最后成功: ${timeStr}`, `${session.agentId}会话: ${session.agentSessionId || '(未初始化)'}`, `创建时间: ${new Date(session.createdAt).toLocaleString('zh-CN')}`, `更新时间: ${new Date(session.updatedAt).toLocaleString('zh-CN')}`);
|
|
1560
1566
|
}
|
|
1561
1567
|
else {
|
|
1562
|
-
lines.push(`📊 ${isThread ? '话题' : '会话'}状态:`, `渠道: ${channel} / 项目: ${projectName} / ${session.agentId}会话`, `状态: ${sessionStatus}`, `会话轮数: ${sessionTurns}`, `最后活跃: ${timeStr}`);
|
|
1568
|
+
lines.push(`📊 ${isThread ? '话题' : '会话'}状态:`, `渠道: ${channel} / 项目: ${projectName} / ${session.agentId}会话`, `状态: ${sessionStatus}`, chatModeLine, `会话轮数: ${sessionTurns}`, `最后活跃: ${timeStr}`);
|
|
1563
1569
|
}
|
|
1564
1570
|
if (health.lastError) {
|
|
1565
1571
|
lines.push('');
|
|
@@ -389,15 +389,28 @@ export class MessageProcessor {
|
|
|
389
389
|
const peerLabel = session.identity?.role || 'unknown';
|
|
390
390
|
const peerName = message.peerName || session.metadata?.peerName;
|
|
391
391
|
const peerType = message.peerType;
|
|
392
|
+
const peerId = message.peerId;
|
|
393
|
+
const adapterAny = channelInfo.adapter;
|
|
394
|
+
const selfAid = typeof adapterAny._selfAid === 'function' ? adapterAny._selfAid() : undefined;
|
|
395
|
+
const selfName = typeof adapterAny._selfName === 'function' ? adapterAny._selfName() : undefined;
|
|
396
|
+
const formatIdentity = (name, id) => {
|
|
397
|
+
if (name && id)
|
|
398
|
+
return `${name} (${id})`;
|
|
399
|
+
return name || id || undefined;
|
|
400
|
+
};
|
|
401
|
+
const selfIdentity = formatIdentity(selfName, selfAid);
|
|
402
|
+
const peerIdentity = formatIdentity(peerName, peerId);
|
|
392
403
|
const envParts = [
|
|
393
404
|
`会话通道: ${currentChannelType}`,
|
|
394
405
|
`当前项目: ${path.basename(absoluteProjectPath)}`,
|
|
395
406
|
];
|
|
396
407
|
if (session.name)
|
|
397
408
|
envParts.push(`会话名称: ${session.name}`);
|
|
409
|
+
if (selfIdentity)
|
|
410
|
+
envParts.push(`当前名称: ${selfIdentity}`);
|
|
398
411
|
envParts.push(`对端身份: ${peerLabel}`);
|
|
399
|
-
if (
|
|
400
|
-
envParts.push(`对端名称: ${
|
|
412
|
+
if (peerIdentity)
|
|
413
|
+
envParts.push(`对端名称: ${peerIdentity}`);
|
|
401
414
|
if (peerType && peerType !== 'unknown')
|
|
402
415
|
envParts.push(`对端类型: ${peerType}`);
|
|
403
416
|
if (session.chatType)
|
|
@@ -51,7 +51,7 @@ export class ThoughtEmitter {
|
|
|
51
51
|
return null;
|
|
52
52
|
return { type: 'thought', text: event.text, stage: 'thinking' };
|
|
53
53
|
case 'tool_use': {
|
|
54
|
-
const desc = this.summarizeInput(event.input);
|
|
54
|
+
const desc = this.summarizeInput(event.input, event.name);
|
|
55
55
|
return {
|
|
56
56
|
type: 'thought',
|
|
57
57
|
text: desc ? `🔧 ${event.name}: ${desc}` : `🔧 ${event.name}`,
|
|
@@ -109,9 +109,16 @@ export class ThoughtEmitter {
|
|
|
109
109
|
return null;
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
|
-
summarizeInput(input) {
|
|
112
|
+
summarizeInput(input, toolName) {
|
|
113
113
|
if (!input || typeof input !== 'object')
|
|
114
114
|
return '';
|
|
115
|
+
// Bash + ctl send/file: 显示完整命令内容(含发送的消息正文)
|
|
116
|
+
if (toolName === 'Bash' && typeof input.command === 'string') {
|
|
117
|
+
const cmd = input.command;
|
|
118
|
+
if (cmd.includes('evolclaw ctl send') || cmd.includes('evolclaw ctl file')) {
|
|
119
|
+
return cmd;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
115
122
|
return (input.description ||
|
|
116
123
|
input.file_path ||
|
|
117
124
|
input.pattern ||
|
package/package.json
CHANGED