evolclaw 3.1.1 → 3.1.3
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/CHANGELOG.md +428 -0
- package/README.md +3 -7
- package/SKILLS.md +311 -0
- package/dist/agents/claude-runner.js +1 -1
- package/dist/agents/codex-runner.js +75 -19
- package/dist/agents/gemini-runner.js +0 -2
- package/dist/agents/kit-renderer.js +59 -10
- package/dist/aun/aid/agentmd.js +50 -27
- package/dist/aun/aid/client.js +5 -11
- package/dist/aun/aid/identity.js +32 -13
- package/dist/aun/aid/index.js +1 -1
- package/dist/aun/msg/group.js +1 -0
- package/dist/aun/msg/p2p.js +15 -2
- package/dist/aun/msg/upload.js +57 -18
- package/dist/aun/rpc/connection.js +3 -0
- package/dist/channels/aun.js +122 -48
- package/dist/channels/dingtalk.js +1 -0
- package/dist/channels/feishu.js +5 -4
- package/dist/channels/qqbot.js +1 -0
- package/dist/channels/wechat.js +1 -0
- package/dist/channels/wecom.js +1 -0
- package/dist/cli/agent.js +142 -40
- package/dist/cli/index.js +103 -58
- package/dist/cli/init-channel.js +4 -2
- package/dist/cli/init.js +55 -26
- package/dist/cli/watch-msg.js +3 -1
- package/dist/config-store.js +22 -1
- package/dist/core/channel-loader.js +4 -4
- package/dist/core/command-handler.js +626 -538
- package/dist/core/evolagent-registry.js +45 -9
- package/dist/core/evolagent.js +35 -4
- package/dist/core/message/im-renderer.js +14 -4
- package/dist/core/message/message-bridge.js +149 -25
- package/dist/core/message/message-processor.js +45 -38
- package/dist/core/session/session-fs-store.js +23 -0
- package/dist/core/session/session-manager.js +188 -42
- package/dist/index.js +15 -17
- package/dist/paths.js +35 -0
- package/dist/utils/cross-platform.js +2 -1
- package/kits/docs/INDEX.md +6 -0
- package/kits/eck_manifest.json +3 -3
- package/kits/rules/02-navigation.md +1 -0
- package/kits/rules/06-channel.md +2 -18
- package/kits/templates/system-fragments/baseagent.md +2 -2
- package/kits/templates/system-fragments/channel.md +18 -9
- package/kits/templates/system-fragments/eckruntime.md +14 -0
- package/kits/templates/system-fragments/identity.md +5 -6
- package/kits/templates/system-fragments/relation.md +7 -5
- package/kits/templates/system-fragments/venue.md +2 -3
- package/package.json +5 -2
- package/kits/templates/system-fragments/runtime.md +0 -19
package/dist/channels/aun.js
CHANGED
|
@@ -6,7 +6,7 @@ import os from 'os';
|
|
|
6
6
|
import { logger, localTimestamp } from '../utils/logger.js';
|
|
7
7
|
import { LogWriter } from '../utils/log-writer.js';
|
|
8
8
|
import { normalizeChannelInstances, getChannelShowActivities } from '../utils/channel-helpers.js';
|
|
9
|
-
import { resolvePaths, getPackageRoot, agentDir as agentDirPath } from '../paths.js';
|
|
9
|
+
import { resolvePaths, getPackageRoot, agentMdPath as agentMdPathFn, agentDir as agentDirPath, resolveRoot } from '../paths.js';
|
|
10
10
|
import { saveToUploads, sanitizeFileName } from '../utils/media-cache.js';
|
|
11
11
|
import { appendAidEvent } from '../utils/instance-registry.js';
|
|
12
12
|
import { appendMessageLog, buildOutboundEntry } from '../core/message/message-log.js';
|
|
@@ -57,6 +57,46 @@ function getEvolclawVersion() {
|
|
|
57
57
|
}
|
|
58
58
|
return _cachedVersion;
|
|
59
59
|
}
|
|
60
|
+
function migrateAunData(targetPath) {
|
|
61
|
+
const legacyPath = path.join(os.homedir(), '.aun');
|
|
62
|
+
if (legacyPath === targetPath)
|
|
63
|
+
return;
|
|
64
|
+
// AIDs 迁移:逐个检查每个 AID,缺少 private 的就从 legacy 复制
|
|
65
|
+
const srcAIDs = path.join(legacyPath, 'AIDs');
|
|
66
|
+
const dstAIDs = path.join(targetPath, 'AIDs');
|
|
67
|
+
if (fs.existsSync(srcAIDs)) {
|
|
68
|
+
fs.mkdirSync(dstAIDs, { recursive: true });
|
|
69
|
+
try {
|
|
70
|
+
for (const entry of fs.readdirSync(srcAIDs, { withFileTypes: true })) {
|
|
71
|
+
if (!entry.isDirectory())
|
|
72
|
+
continue;
|
|
73
|
+
const aidName = entry.name;
|
|
74
|
+
const srcAidDir = path.join(srcAIDs, aidName);
|
|
75
|
+
const dstAidDir = path.join(dstAIDs, aidName);
|
|
76
|
+
const srcPrivate = path.join(srcAidDir, 'private');
|
|
77
|
+
const dstPrivate = path.join(dstAidDir, 'private');
|
|
78
|
+
// 如果目标 AID 目录不存在或缺少 private,从源复制整个 AID 目录
|
|
79
|
+
if (fs.existsSync(srcPrivate) && !fs.existsSync(dstPrivate)) {
|
|
80
|
+
fs.cpSync(srcAidDir, dstAidDir, { recursive: true });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
catch { }
|
|
85
|
+
}
|
|
86
|
+
// CA 迁移
|
|
87
|
+
const srcCA = path.join(legacyPath, 'CA');
|
|
88
|
+
const dstCA = path.join(targetPath, 'CA');
|
|
89
|
+
if (fs.existsSync(srcCA) && !fs.existsSync(dstCA)) {
|
|
90
|
+
fs.cpSync(srcCA, dstCA, { recursive: true });
|
|
91
|
+
}
|
|
92
|
+
for (const file of ['.seed', '.device_id']) {
|
|
93
|
+
const src = path.join(legacyPath, file);
|
|
94
|
+
const dst = path.join(targetPath, file);
|
|
95
|
+
if (fs.existsSync(src) && !fs.existsSync(dst)) {
|
|
96
|
+
fs.copyFileSync(src, dst);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
60
100
|
export class AUNChannel {
|
|
61
101
|
config;
|
|
62
102
|
client = null;
|
|
@@ -408,10 +448,10 @@ export class AUNChannel {
|
|
|
408
448
|
}
|
|
409
449
|
return out;
|
|
410
450
|
}
|
|
411
|
-
buildGroupReplyContext(
|
|
451
|
+
buildGroupReplyContext(threadId, senderAid, encrypted, messageId, chatmode) {
|
|
412
452
|
const replyContext = { metadata: { encrypted, chatmode } };
|
|
413
|
-
if (
|
|
414
|
-
replyContext.threadId =
|
|
453
|
+
if (threadId)
|
|
454
|
+
replyContext.threadId = threadId;
|
|
415
455
|
replyContext.peerId = senderAid;
|
|
416
456
|
if (messageId)
|
|
417
457
|
replyContext.replyToMessageId = messageId;
|
|
@@ -445,6 +485,10 @@ export class AUNChannel {
|
|
|
445
485
|
'text', 'quote', 'image', 'video', 'voice', 'file', 'json',
|
|
446
486
|
'merge', 'link', 'location', 'personal_card',
|
|
447
487
|
]);
|
|
488
|
+
/** Menu protocol 请求类型:自定义消息快速路径,绕过白名单直接分发到 bridge */
|
|
489
|
+
static MENU_REQUEST_TYPES = new Set([
|
|
490
|
+
'menu.list', 'menu.query', 'menu.options', 'menu.update', 'menu.action',
|
|
491
|
+
]);
|
|
448
492
|
// Reconnect state
|
|
449
493
|
// SDK 自己跑无限指数退避(1s → 5min);TS 层只在 SDK 够不到的两类场景下接管:
|
|
450
494
|
// 1. flap:短命 connected 反复出现(SDK 不记忆跨轮 base delay,会从 1s 重新开始)
|
|
@@ -541,9 +585,11 @@ export class AUNChannel {
|
|
|
541
585
|
this.client = null;
|
|
542
586
|
}
|
|
543
587
|
this.connected = false;
|
|
544
|
-
const aunPath = this.config.keystorePath ||
|
|
588
|
+
const aunPath = this.config.keystorePath || resolveRoot();
|
|
545
589
|
const aidName = this.config.aid;
|
|
546
590
|
const encryptionSeed = this.config.encryptionSeed || process.env.AUN_ENCRYPTION_SEED || undefined;
|
|
591
|
+
// Migrate legacy ~/.aun data to EVOLCLAW_HOME on first run
|
|
592
|
+
migrateAunData(aunPath);
|
|
547
593
|
// Gateway URL 解析:优先用配置的 gatewayUrl,否则通过 well-known 自动发现
|
|
548
594
|
let gateway = this.config.gatewayUrl || '';
|
|
549
595
|
if (!gateway) {
|
|
@@ -731,8 +777,8 @@ export class AUNChannel {
|
|
|
731
777
|
logger.info(`${this.logPrefix()} No owner configured, skipping welcome message (will retry after auto-bind)`);
|
|
732
778
|
return;
|
|
733
779
|
}
|
|
734
|
-
const
|
|
735
|
-
const existingAgentMd = fs.existsSync(
|
|
780
|
+
const agentMdLocalPath = agentMdPathFn(aidName);
|
|
781
|
+
const existingAgentMd = fs.existsSync(agentMdLocalPath) ? fs.readFileSync(agentMdLocalPath, 'utf-8') : '';
|
|
736
782
|
const existingFrontmatterMatch = existingAgentMd.match(/^---\n([\s\S]*?)\n---/);
|
|
737
783
|
const existingFrontmatter = existingFrontmatterMatch?.[1] ?? '';
|
|
738
784
|
// Fetch owner's agent.md to derive name and validate type
|
|
@@ -778,12 +824,12 @@ tags:
|
|
|
778
824
|
EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
779
825
|
`;
|
|
780
826
|
// Write locally
|
|
781
|
-
fs.mkdirSync(path.dirname(
|
|
782
|
-
fs.writeFileSync(
|
|
827
|
+
fs.mkdirSync(path.dirname(agentMdLocalPath), { recursive: true });
|
|
828
|
+
fs.writeFileSync(agentMdLocalPath, newAgentMd, 'utf-8');
|
|
783
829
|
logger.info(`${this.logPrefix()} Updated agent.md for ${aidName}`);
|
|
784
|
-
// Publish to AUN network via
|
|
830
|
+
// Publish to AUN network via publishAgentMd (auto-sign)
|
|
785
831
|
try {
|
|
786
|
-
await this.client.
|
|
832
|
+
await this.client.publishAgentMd();
|
|
787
833
|
logger.info(`${this.logPrefix()} Published agent.md to AUN network`);
|
|
788
834
|
}
|
|
789
835
|
catch (e) {
|
|
@@ -800,8 +846,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
800
846
|
2. **查看帮助**:发送 \`/help\` 查看所有可用命令
|
|
801
847
|
3. **切换项目**:发送 \`/project <项目名>\` 切换到其他项目
|
|
802
848
|
4. **查看状态**:发送 \`/status\` 查看当前会话状态
|
|
803
|
-
5.
|
|
804
|
-
6. **会话管理**:发送 \`/session\` 查看和切换会话
|
|
849
|
+
5. **会话管理**:发送 \`/session\` 查看和切换会话
|
|
805
850
|
|
|
806
851
|
💡 **提示**:
|
|
807
852
|
- 直接发送消息即可与 Claude/Codex 对话
|
|
@@ -908,7 +953,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
908
953
|
const fromAid = msg.from ?? '';
|
|
909
954
|
const payload = msg.payload ?? '';
|
|
910
955
|
const text = this.extractTextPayload(payload, fromAid);
|
|
911
|
-
const
|
|
956
|
+
const threadId = typeof payload === 'object' && payload !== null ? payload.thread_id : undefined;
|
|
912
957
|
const messageId = msg.message_id ?? '';
|
|
913
958
|
const seq = msg.seq;
|
|
914
959
|
// 回声过滤:自己发出的消息会被 gateway fanout 回来,
|
|
@@ -953,7 +998,8 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
953
998
|
// device_id 仅 SDK 内部多实例去重用,evolclaw session 层面跨端共享会话
|
|
954
999
|
const chatId = fromAid;
|
|
955
1000
|
// 解析对端身份(30天缓存)
|
|
956
|
-
const
|
|
1001
|
+
const selfAgentDir = path.join(resolvePaths().agentsDir, this.config.aid);
|
|
1002
|
+
const peerIdentity = await PeerIdentityCache.resolve('aun', fromAid, selfAgentDir, this.client, false);
|
|
957
1003
|
const shortAid = this.getShortAid(fromAid);
|
|
958
1004
|
const displayName = peerIdentity.name || shortAid;
|
|
959
1005
|
// 详细 dispatch 决策日志:记录消息为何被路由到 agent
|
|
@@ -962,8 +1008,8 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
962
1008
|
// action_card_reply 已在 extractTextPayload 中消费,不分发给 agent
|
|
963
1009
|
if (p2pPayloadType === 'action_card_reply')
|
|
964
1010
|
return;
|
|
965
|
-
// menu
|
|
966
|
-
if (p2pPayloadType
|
|
1011
|
+
// menu.* 协议:自定义消息快速路径,需要原始 payload JSON 传递给 bridge
|
|
1012
|
+
if (AUNChannel.MENU_REQUEST_TYPES.has(p2pPayloadType)) {
|
|
967
1013
|
this.acknowledgeImmediately(messageId, seq);
|
|
968
1014
|
this.dispatchMessage({
|
|
969
1015
|
channelId: chatId, userId: fromAid,
|
|
@@ -986,8 +1032,8 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
986
1032
|
const isSystemP2P = p2pPayloadType === 'event';
|
|
987
1033
|
this.aidStatsCollector?.recordInbound(this.config.aid, fromAid, Buffer.byteLength(finalText, 'utf-8'), finalText, isSystemP2P, msgEncrypted, msgChatmode);
|
|
988
1034
|
const replyContext = { metadata: { encrypted: msgEncrypted, chatmode: msgChatmode } };
|
|
989
|
-
if (
|
|
990
|
-
replyContext.threadId =
|
|
1035
|
+
if (threadId)
|
|
1036
|
+
replyContext.threadId = threadId;
|
|
991
1037
|
this.dispatchMessage({
|
|
992
1038
|
channelId: chatId,
|
|
993
1039
|
userId: fromAid,
|
|
@@ -995,7 +1041,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
995
1041
|
chatType: 'private',
|
|
996
1042
|
messageId,
|
|
997
1043
|
seq,
|
|
998
|
-
|
|
1044
|
+
threadId,
|
|
999
1045
|
mentions,
|
|
1000
1046
|
peerName: displayName || undefined,
|
|
1001
1047
|
peerType: peerIdentity.type,
|
|
@@ -1010,7 +1056,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1010
1056
|
const senderAid = msg.sender_aid ?? '';
|
|
1011
1057
|
const payload = msg.payload ?? '';
|
|
1012
1058
|
const text = this.extractTextPayload(payload, groupId, senderAid);
|
|
1013
|
-
const
|
|
1059
|
+
const threadId = typeof payload === 'object' && payload !== null ? payload.thread_id : undefined;
|
|
1014
1060
|
const messageId = msg.message_id ?? '';
|
|
1015
1061
|
const seq = msg.seq;
|
|
1016
1062
|
// Extract structured mentions from payload (e.g. payload.mentions: ["evolai.agentid.pub"])
|
|
@@ -1068,8 +1114,8 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1068
1114
|
{
|
|
1069
1115
|
const payloadObj = (payload && typeof payload === 'object') ? payload : null;
|
|
1070
1116
|
const payloadType = payloadObj?.type ?? '';
|
|
1071
|
-
// menu
|
|
1072
|
-
if (payloadType
|
|
1117
|
+
// menu.* 协议:自定义消息快速路径
|
|
1118
|
+
if (AUNChannel.MENU_REQUEST_TYPES.has(payloadType)) {
|
|
1073
1119
|
this.acknowledgeImmediately(messageId, seq);
|
|
1074
1120
|
this.dispatchMessage({
|
|
1075
1121
|
channelId: groupId, userId: senderAid,
|
|
@@ -1168,7 +1214,8 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1168
1214
|
finalText = parts.join('\n\n');
|
|
1169
1215
|
}
|
|
1170
1216
|
}
|
|
1171
|
-
const
|
|
1217
|
+
const selfAgentDir = path.join(resolvePaths().agentsDir, this.config.aid);
|
|
1218
|
+
const peerIdentity = await PeerIdentityCache.resolve('aun', senderAid, selfAgentDir, this.client, false);
|
|
1172
1219
|
const shortAid = this.getShortAid(senderAid);
|
|
1173
1220
|
const displayName = peerIdentity.name || shortAid;
|
|
1174
1221
|
// 详细 dispatch 决策日志:记录消息为何被路由到 agent
|
|
@@ -1200,9 +1247,9 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1200
1247
|
chatType: 'group',
|
|
1201
1248
|
messageId,
|
|
1202
1249
|
seq,
|
|
1203
|
-
|
|
1250
|
+
threadId,
|
|
1204
1251
|
mentions,
|
|
1205
|
-
replyContext: this.buildGroupReplyContext(
|
|
1252
|
+
replyContext: this.buildGroupReplyContext(threadId, senderAid, msgEncrypted, messageId, msgChatmode),
|
|
1206
1253
|
});
|
|
1207
1254
|
}
|
|
1208
1255
|
dispatchMessage(event) {
|
|
@@ -1252,8 +1299,8 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1252
1299
|
// Use caller-supplied replyContext (group path builds mentionUserIds);
|
|
1253
1300
|
// fall back to simple threadId-only context for private messages
|
|
1254
1301
|
let replyContext = event.replyContext;
|
|
1255
|
-
if (!replyContext && event.
|
|
1256
|
-
replyContext = { threadId: event.
|
|
1302
|
+
if (!replyContext && event.threadId) {
|
|
1303
|
+
replyContext = { threadId: event.threadId };
|
|
1257
1304
|
}
|
|
1258
1305
|
this.messageHandler({
|
|
1259
1306
|
channelId: event.channelId || '',
|
|
@@ -1266,7 +1313,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1266
1313
|
peerName: event.peerName,
|
|
1267
1314
|
peerType: event.peerType,
|
|
1268
1315
|
messageId: event.messageId,
|
|
1269
|
-
threadId: event.
|
|
1316
|
+
threadId: event.threadId,
|
|
1270
1317
|
mentions: mentionObjects,
|
|
1271
1318
|
replyContext,
|
|
1272
1319
|
}).catch(err => {
|
|
@@ -1707,6 +1754,8 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1707
1754
|
const channelId = entry.channelId;
|
|
1708
1755
|
const finalText = entry.text;
|
|
1709
1756
|
const context = entry.context;
|
|
1757
|
+
// 从 context.metadata.source 读取 source,默认为 'daemon'
|
|
1758
|
+
const source = context?.metadata?.source ?? 'daemon';
|
|
1710
1759
|
const payload = { type: 'text', text: finalText };
|
|
1711
1760
|
if (this.isGroupId(channelId)) {
|
|
1712
1761
|
const extracted = this.extractMentionAidsFromText(finalText);
|
|
@@ -1720,7 +1769,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1720
1769
|
if (context?.metadata?.chatmode)
|
|
1721
1770
|
payload.chatmode = context.metadata.chatmode;
|
|
1722
1771
|
// 诊断日志:记录 payload 构造结果(含 task_id / thread_id / chatmode)
|
|
1723
|
-
logger.info(`${this.logPrefix()} deliverTextEntry: channelId=${channelId} thread_id=${payload.thread_id ?? 'none'} task_id=${payload.task_id ?? 'none'} chatmode=${payload.chatmode ?? 'none'} textLen=${finalText.length}`);
|
|
1772
|
+
logger.info(`${this.logPrefix()} deliverTextEntry: channelId=${channelId} thread_id=${payload.thread_id ?? 'none'} task_id=${payload.task_id ?? 'none'} chatmode=${payload.chatmode ?? 'none'} source=${source} textLen=${finalText.length}`);
|
|
1724
1773
|
const isGroup = this.isGroupId(channelId);
|
|
1725
1774
|
const targetAid = channelId;
|
|
1726
1775
|
const encryptTarget = isGroup ? channelId : targetAid;
|
|
@@ -1746,7 +1795,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1746
1795
|
logger.info(`${this.logPrefix()} group.send ok: group=${channelId} mid=${mid} encrypt=${encrypt} text=${finalText.slice(0, 60)}`);
|
|
1747
1796
|
appendAidEvent({ ts: Date.now(), iso: new Date().toISOString(), event: 'message_out', aid: this.config.aid, to: channelId, msgId: mid, kind: 'text', len: finalText.length, groupId: channelId });
|
|
1748
1797
|
this.aidStatsCollector?.recordOutbound(this.config.aid, channelId, Buffer.byteLength(finalText, 'utf-8'), finalText, false, encrypt, context?.metadata?.chatmode);
|
|
1749
|
-
this.appendOutboundJsonl(channelId, finalText, mid, encrypt, context, true);
|
|
1798
|
+
this.appendOutboundJsonl(channelId, finalText, mid, encrypt, context, true, 'text', source);
|
|
1750
1799
|
}
|
|
1751
1800
|
}
|
|
1752
1801
|
else {
|
|
@@ -1759,7 +1808,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1759
1808
|
logger.info(`${this.logPrefix()} message.send ok: to=${this.peerLabel(targetAid)} mid=${result.message_id} encrypt=${encrypt} text=${finalText.slice(0, 60)}`);
|
|
1760
1809
|
appendAidEvent({ ts: Date.now(), iso: new Date().toISOString(), event: 'message_out', aid: this.config.aid, to: targetAid, msgId: result.message_id, kind: 'text', len: finalText.length });
|
|
1761
1810
|
this.aidStatsCollector?.recordOutbound(this.config.aid, targetAid, Buffer.byteLength(finalText, 'utf-8'), finalText, false, encrypt, context?.metadata?.chatmode);
|
|
1762
|
-
this.appendOutboundJsonl(targetAid, finalText, result.message_id, encrypt, context, false);
|
|
1811
|
+
this.appendOutboundJsonl(targetAid, finalText, result.message_id, encrypt, context, false, 'text', source);
|
|
1763
1812
|
}
|
|
1764
1813
|
}
|
|
1765
1814
|
return true;
|
|
@@ -1802,7 +1851,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1802
1851
|
}
|
|
1803
1852
|
}
|
|
1804
1853
|
/** 出站消息写入 messages.jsonl(message.send/group.send/thought.put 成功后调用) */
|
|
1805
|
-
appendOutboundJsonl(channelId, text, msgId, encrypt, context, isGroup, msgType = 'text') {
|
|
1854
|
+
appendOutboundJsonl(channelId, text, msgId, encrypt, context, isGroup, msgType = 'text', source = 'daemon') {
|
|
1806
1855
|
try {
|
|
1807
1856
|
const sessionsDir = resolvePaths().sessionsDir;
|
|
1808
1857
|
const selfId = this.config.aid;
|
|
@@ -1822,6 +1871,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1822
1871
|
encrypt,
|
|
1823
1872
|
chatmode,
|
|
1824
1873
|
msgType,
|
|
1874
|
+
source,
|
|
1825
1875
|
}));
|
|
1826
1876
|
}
|
|
1827
1877
|
catch (e) {
|
|
@@ -1854,12 +1904,24 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1854
1904
|
try {
|
|
1855
1905
|
const itemCount = Array.isArray(payload?.items) ? payload.items.length : 0;
|
|
1856
1906
|
const stage = payload?.stage ?? `items=${itemCount}`;
|
|
1857
|
-
// 提取 thought
|
|
1907
|
+
// 提取 thought 文本(只对 kind=text 的 item 写 jsonl,过滤 tool_use/tool_result 等结构化项)
|
|
1858
1908
|
const items = payload?.items;
|
|
1859
1909
|
let thoughtText;
|
|
1860
1910
|
if (Array.isArray(items) && items.length > 0) {
|
|
1861
1911
|
const lastItem = items[items.length - 1];
|
|
1862
|
-
|
|
1912
|
+
// 优先 text 字段(kind=text 的 item),否则 content
|
|
1913
|
+
if (lastItem?.kind === 'text' && lastItem.text) {
|
|
1914
|
+
thoughtText = lastItem.text;
|
|
1915
|
+
}
|
|
1916
|
+
else if (lastItem?.text) {
|
|
1917
|
+
thoughtText = lastItem.text;
|
|
1918
|
+
}
|
|
1919
|
+
else if (lastItem?.content) {
|
|
1920
|
+
thoughtText = lastItem.content;
|
|
1921
|
+
}
|
|
1922
|
+
else if (typeof lastItem === 'string') {
|
|
1923
|
+
thoughtText = lastItem;
|
|
1924
|
+
}
|
|
1863
1925
|
}
|
|
1864
1926
|
if (this.isGroupId(channelId)) {
|
|
1865
1927
|
params.group_id = targetId;
|
|
@@ -1867,7 +1929,10 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1867
1929
|
const tid = putRes?.thought_id;
|
|
1868
1930
|
logger.info(`${this.logPrefix()} thought.put ok group=${targetId} task=${taskId} stage=${stage} encrypt=${encrypt} tid=${tid ?? '?'}`);
|
|
1869
1931
|
this.eventBus?.publish?.({ type: 'message:thought-put', agentName: this.config.aid, channelId, taskId, text: thoughtText });
|
|
1870
|
-
// thought jsonl
|
|
1932
|
+
// 文本类 thought 写入 jsonl(只对有 text 的 item,过滤 tool 等结构化项)
|
|
1933
|
+
if (thoughtText) {
|
|
1934
|
+
this.appendOutboundJsonl(channelId, thoughtText, tid ?? `thought-${Date.now()}`, encrypt, context, true, 'thought', 'daemon');
|
|
1935
|
+
}
|
|
1871
1936
|
}
|
|
1872
1937
|
else {
|
|
1873
1938
|
params.to = targetId;
|
|
@@ -1875,7 +1940,9 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1875
1940
|
const tid = putRes?.thought_id;
|
|
1876
1941
|
logger.info(`${this.logPrefix()} thought.put ok p2p=${this.peerLabel(targetId)} task=${taskId} stage=${stage} encrypt=${encrypt} tid=${tid ?? '?'}`);
|
|
1877
1942
|
this.eventBus?.publish?.({ type: 'message:thought-put', agentName: this.config.aid, channelId, taskId, text: thoughtText });
|
|
1878
|
-
|
|
1943
|
+
if (thoughtText) {
|
|
1944
|
+
this.appendOutboundJsonl(channelId, thoughtText, tid ?? `thought-${Date.now()}`, encrypt, context, false, 'thought', 'daemon');
|
|
1945
|
+
}
|
|
1879
1946
|
}
|
|
1880
1947
|
}
|
|
1881
1948
|
catch (e) {
|
|
@@ -2303,13 +2370,13 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
2303
2370
|
loadSelfName(aid) {
|
|
2304
2371
|
try {
|
|
2305
2372
|
const aidName = aid.startsWith('@') ? aid.slice(1) : aid;
|
|
2306
|
-
const
|
|
2307
|
-
if (!fs.existsSync(
|
|
2373
|
+
const mdPath = agentMdPathFn(aidName);
|
|
2374
|
+
if (!fs.existsSync(mdPath)) {
|
|
2308
2375
|
// 异步拉取,不阻塞连接流程
|
|
2309
2376
|
this.fetchAndCacheSelfName(aidName);
|
|
2310
2377
|
return undefined;
|
|
2311
2378
|
}
|
|
2312
|
-
const content = fs.readFileSync(
|
|
2379
|
+
const content = fs.readFileSync(mdPath, 'utf-8');
|
|
2313
2380
|
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
2314
2381
|
if (!fmMatch)
|
|
2315
2382
|
return undefined;
|
|
@@ -2351,7 +2418,9 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
2351
2418
|
if (!this.client)
|
|
2352
2419
|
return { type: null };
|
|
2353
2420
|
try {
|
|
2354
|
-
const
|
|
2421
|
+
const { agentmdSync } = await import('../aun/aid/agentmd.js');
|
|
2422
|
+
const result = await agentmdSync(aid, { client: this.client });
|
|
2423
|
+
const md = result.content ?? '';
|
|
2355
2424
|
const typeMatch = md.match(/^type:\s*["']?(\w+)["']?/m);
|
|
2356
2425
|
const nameMatch = md.match(/^name:\s*["']?(.+?)["']?\s*$/m);
|
|
2357
2426
|
const type = typeMatch?.[1] === 'human' ? 'human' : 'ai';
|
|
@@ -2363,7 +2432,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
2363
2432
|
}
|
|
2364
2433
|
catch (e) {
|
|
2365
2434
|
logger.debug(`${this.logPrefix()} fetchPeerInfo failed for ${aid}: ${e}`);
|
|
2366
|
-
return { type: null };
|
|
2435
|
+
return { type: null };
|
|
2367
2436
|
}
|
|
2368
2437
|
}
|
|
2369
2438
|
/** 同步取 peerInfo 缓存,未命中返回 undefined,不发起任何网络请求。 */
|
|
@@ -2379,12 +2448,18 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
2379
2448
|
async uploadAgentMd(content) {
|
|
2380
2449
|
if (!this.client)
|
|
2381
2450
|
throw new Error('not connected');
|
|
2382
|
-
await
|
|
2451
|
+
const { agentMdPath } = await import('../paths.js');
|
|
2452
|
+
const localPath = agentMdPath(this.config.aid);
|
|
2453
|
+
fs.mkdirSync(path.dirname(localPath), { recursive: true });
|
|
2454
|
+
fs.writeFileSync(localPath, content, 'utf-8');
|
|
2455
|
+
await this.client.publishAgentMd();
|
|
2383
2456
|
}
|
|
2384
2457
|
async downloadAgentMd(aid) {
|
|
2385
2458
|
if (!this.client)
|
|
2386
2459
|
throw new Error('not connected');
|
|
2387
|
-
|
|
2460
|
+
const { agentmdSync } = await import('../aun/aid/agentmd.js');
|
|
2461
|
+
const result = await agentmdSync(aid, { client: this.client });
|
|
2462
|
+
return result.content ?? '';
|
|
2388
2463
|
}
|
|
2389
2464
|
}
|
|
2390
2465
|
// Plugin implementation
|
|
@@ -2420,6 +2495,7 @@ export class AUNChannelPlugin {
|
|
|
2420
2495
|
});
|
|
2421
2496
|
const adapter = {
|
|
2422
2497
|
channelName: inst.name,
|
|
2498
|
+
channelKey: inst.name, // channelName 实际上就是 channelKey
|
|
2423
2499
|
capabilities: { file: true, image: true, interaction: true, markdown: true, thought: true, status: true },
|
|
2424
2500
|
send: async (envelope, payload) => {
|
|
2425
2501
|
const ctx = envelope.replyContext;
|
|
@@ -2464,10 +2540,8 @@ export class AUNChannelPlugin {
|
|
|
2464
2540
|
await channel.sendThought(channelId, envelope.taskId, aunPayload, ctx);
|
|
2465
2541
|
}
|
|
2466
2542
|
else {
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
channel.sendStructured(channelId, aunPayload, ctx),
|
|
2470
|
-
]);
|
|
2543
|
+
// interactive 模式不发 thought.put,只写入消息历史
|
|
2544
|
+
await channel.sendStructured(channelId, aunPayload, ctx);
|
|
2471
2545
|
}
|
|
2472
2546
|
return;
|
|
2473
2547
|
}
|
|
@@ -447,6 +447,7 @@ export class DingtalkChannelPlugin {
|
|
|
447
447
|
});
|
|
448
448
|
const adapter = {
|
|
449
449
|
channelName: inst.name,
|
|
450
|
+
channelKey: inst.name,
|
|
450
451
|
capabilities: { file: true, image: true, interaction: false, markdown: true, thought: false, status: false },
|
|
451
452
|
send: async (envelope, payload) => {
|
|
452
453
|
const ctx = envelope.replyContext;
|
package/dist/channels/feishu.js
CHANGED
|
@@ -329,12 +329,11 @@ export class FeishuChannel {
|
|
|
329
329
|
}
|
|
330
330
|
logger.info(`[Feishu] CommandCard trigger: command=${value._command}, operator=${operatorId}`);
|
|
331
331
|
if (this.messageHandler) {
|
|
332
|
-
//
|
|
333
|
-
|
|
332
|
+
// 卡片回调不传 chatType——oc_ 前缀不区分群聊/单聊,
|
|
333
|
+
// 由 ensureSession 从已有 session 中继承正确的 chatType
|
|
334
334
|
await this.messageHandler({
|
|
335
335
|
channelId: chatId,
|
|
336
336
|
content: value._command,
|
|
337
|
-
chatType,
|
|
338
337
|
peerId: operatorId,
|
|
339
338
|
messageId: `card-trigger-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
340
339
|
source: 'card-trigger',
|
|
@@ -1327,6 +1326,7 @@ export class FeishuChannelPlugin {
|
|
|
1327
1326
|
});
|
|
1328
1327
|
const adapter = {
|
|
1329
1328
|
channelName: inst.name,
|
|
1329
|
+
channelKey: inst.name,
|
|
1330
1330
|
capabilities: { file: true, image: true, interaction: true, markdown: true, thought: false, status: true },
|
|
1331
1331
|
send: async (envelope, payload) => {
|
|
1332
1332
|
const ctx = envelope.replyContext;
|
|
@@ -1424,7 +1424,8 @@ export class FeishuChannelPlugin {
|
|
|
1424
1424
|
registerBridge(bridge, channelType) {
|
|
1425
1425
|
bridge.register(adapter.channelName, (handler) => channel.onMessage(async ({ channelId: chatId, content, images, peerId, peerName, messageId, mentions, threadId, rootId, chatType, source }) => {
|
|
1426
1426
|
await handler({
|
|
1427
|
-
channel: adapter.channelName, channelType, channelId: chatId, content, images,
|
|
1427
|
+
channel: adapter.channelName, channelType, channelId: chatId, content, images,
|
|
1428
|
+
chatType: chatType || 'private',
|
|
1428
1429
|
peerId: peerId || '', peerName, messageId, mentions, threadId,
|
|
1429
1430
|
replyContext: threadId ? { replyToMessageId: rootId ?? threadId, replyInThread: true } : undefined,
|
|
1430
1431
|
source,
|
package/dist/channels/qqbot.js
CHANGED
|
@@ -334,6 +334,7 @@ export class QQBotChannelPlugin {
|
|
|
334
334
|
});
|
|
335
335
|
const adapter = {
|
|
336
336
|
channelName: inst.name,
|
|
337
|
+
channelKey: inst.name,
|
|
337
338
|
capabilities: { file: true, image: true, interaction: false, markdown: true, thought: false, status: false },
|
|
338
339
|
send: async (envelope, payload) => {
|
|
339
340
|
const ctx = envelope.replyContext;
|
package/dist/channels/wechat.js
CHANGED
|
@@ -730,6 +730,7 @@ export class WechatChannelPlugin {
|
|
|
730
730
|
});
|
|
731
731
|
const adapter = {
|
|
732
732
|
channelName: inst.name,
|
|
733
|
+
channelKey: inst.name,
|
|
733
734
|
capabilities: { file: false, image: false, interaction: false, markdown: false, thought: false, status: true },
|
|
734
735
|
send: async (envelope, payload) => {
|
|
735
736
|
const channelId = envelope.channelId;
|
package/dist/channels/wecom.js
CHANGED
|
@@ -490,6 +490,7 @@ export class WecomChannelPlugin {
|
|
|
490
490
|
});
|
|
491
491
|
const adapter = {
|
|
492
492
|
channelName: inst.name,
|
|
493
|
+
channelKey: inst.name,
|
|
493
494
|
capabilities: { file: true, image: true, interaction: false, markdown: true, thought: false, status: false },
|
|
494
495
|
send: async (envelope, payload) => {
|
|
495
496
|
const ctx = envelope.replyContext;
|