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.
Files changed (51) hide show
  1. package/CHANGELOG.md +428 -0
  2. package/README.md +3 -7
  3. package/SKILLS.md +311 -0
  4. package/dist/agents/claude-runner.js +1 -1
  5. package/dist/agents/codex-runner.js +75 -19
  6. package/dist/agents/gemini-runner.js +0 -2
  7. package/dist/agents/kit-renderer.js +59 -10
  8. package/dist/aun/aid/agentmd.js +50 -27
  9. package/dist/aun/aid/client.js +5 -11
  10. package/dist/aun/aid/identity.js +32 -13
  11. package/dist/aun/aid/index.js +1 -1
  12. package/dist/aun/msg/group.js +1 -0
  13. package/dist/aun/msg/p2p.js +15 -2
  14. package/dist/aun/msg/upload.js +57 -18
  15. package/dist/aun/rpc/connection.js +3 -0
  16. package/dist/channels/aun.js +122 -48
  17. package/dist/channels/dingtalk.js +1 -0
  18. package/dist/channels/feishu.js +5 -4
  19. package/dist/channels/qqbot.js +1 -0
  20. package/dist/channels/wechat.js +1 -0
  21. package/dist/channels/wecom.js +1 -0
  22. package/dist/cli/agent.js +142 -40
  23. package/dist/cli/index.js +103 -58
  24. package/dist/cli/init-channel.js +4 -2
  25. package/dist/cli/init.js +55 -26
  26. package/dist/cli/watch-msg.js +3 -1
  27. package/dist/config-store.js +22 -1
  28. package/dist/core/channel-loader.js +4 -4
  29. package/dist/core/command-handler.js +626 -538
  30. package/dist/core/evolagent-registry.js +45 -9
  31. package/dist/core/evolagent.js +35 -4
  32. package/dist/core/message/im-renderer.js +14 -4
  33. package/dist/core/message/message-bridge.js +149 -25
  34. package/dist/core/message/message-processor.js +45 -38
  35. package/dist/core/session/session-fs-store.js +23 -0
  36. package/dist/core/session/session-manager.js +188 -42
  37. package/dist/index.js +15 -17
  38. package/dist/paths.js +35 -0
  39. package/dist/utils/cross-platform.js +2 -1
  40. package/kits/docs/INDEX.md +6 -0
  41. package/kits/eck_manifest.json +3 -3
  42. package/kits/rules/02-navigation.md +1 -0
  43. package/kits/rules/06-channel.md +2 -18
  44. package/kits/templates/system-fragments/baseagent.md +2 -2
  45. package/kits/templates/system-fragments/channel.md +18 -9
  46. package/kits/templates/system-fragments/eckruntime.md +14 -0
  47. package/kits/templates/system-fragments/identity.md +5 -6
  48. package/kits/templates/system-fragments/relation.md +7 -5
  49. package/kits/templates/system-fragments/venue.md +2 -3
  50. package/package.json +5 -2
  51. package/kits/templates/system-fragments/runtime.md +0 -19
@@ -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(taskId, senderAid, encrypted, messageId, chatmode) {
451
+ buildGroupReplyContext(threadId, senderAid, encrypted, messageId, chatmode) {
412
452
  const replyContext = { metadata: { encrypted, chatmode } };
413
- if (taskId)
414
- replyContext.threadId = taskId;
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 || path.join(os.homedir(), '.aun');
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 agentMdPath = path.join(os.homedir(), '.aun', 'AIDs', aidName, 'agent.md');
735
- const existingAgentMd = fs.existsSync(agentMdPath) ? fs.readFileSync(agentMdPath, 'utf-8') : '';
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(agentMdPath), { recursive: true });
782
- fs.writeFileSync(agentMdPath, newAgentMd, 'utf-8');
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 auth.uploadAgentMd
830
+ // Publish to AUN network via publishAgentMd (auto-sign)
785
831
  try {
786
- await this.client.auth.uploadAgentMd(newAgentMd);
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. **查看 Agent 信息**:发送 \`/agentmd\` 查看 agent.md 内容
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 taskId = typeof payload === 'object' && payload !== null ? payload.thread_id : undefined;
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 peerIdentity = await PeerIdentityCache.resolve('aun', fromAid, this.agentDir, this.client, false);
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.query:自定义消息快速路径,需要原始 payload JSON 传递给 bridge
966
- if (p2pPayloadType === 'menu.query') {
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 (taskId)
990
- replyContext.threadId = taskId;
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
- taskId,
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 taskId = typeof payload === 'object' && payload !== null ? payload.thread_id : undefined;
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.query:自定义消息快速路径
1072
- if (payloadType === 'menu.query') {
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 peerIdentity = await PeerIdentityCache.resolve('aun', senderAid, this.agentDir, this.client, false);
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
- taskId,
1250
+ threadId,
1204
1251
  mentions,
1205
- replyContext: this.buildGroupReplyContext(taskId, senderAid, msgEncrypted, messageId, msgChatmode),
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.taskId) {
1256
- replyContext = { threadId: event.taskId };
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.taskId,
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 文本(取最后一项的 text content 字段)
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
- thoughtText = lastItem?.text || lastItem?.content || (typeof lastItem === 'string' ? lastItem : undefined);
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 写入已改为按 LLM 调用次数统计(在 complete 事件处写入),此处不再写
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
- // thought jsonl 写入已改为按 LLM 调用次数统计(在 complete 事件处写入),此处不再写
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 agentMdPath = path.join(os.homedir(), '.aun', 'AIDs', aidName, 'agent.md');
2307
- if (!fs.existsSync(agentMdPath)) {
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(agentMdPath, 'utf-8');
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 md = await this.client.auth.downloadAgentMd(aid);
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 }; // no agent.md → unknown
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 this.client.auth.uploadAgentMd(content);
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
- return this.client.auth.downloadAgentMd(aid);
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
- await Promise.all([
2468
- channel.sendThought(channelId, envelope.taskId, aunPayload, ctx),
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;
@@ -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
- // Feishu chatId 前缀:oc_ = group chat,ou_ = private user open_id
333
- const chatType = typeof chatId === 'string' && chatId.startsWith('oc_') ? 'group' : 'private';
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, chatType,
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,
@@ -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;
@@ -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;
@@ -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;