evolclaw 3.1.1 → 3.1.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/CHANGELOG.md +407 -0
- package/README.md +1 -1
- package/SKILLS.md +311 -0
- package/dist/aun/aid/agentmd.js +7 -6
- package/dist/aun/aid/client.js +5 -11
- package/dist/aun/aid/identity.js +32 -13
- 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/channels/aun.js +56 -35
- 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 +130 -35
- package/dist/cli/index.js +76 -21
- package/dist/cli/init-channel.js +4 -2
- package/dist/cli/init.js +42 -20
- 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 +11 -4
- package/dist/core/evolagent-registry.js +45 -9
- package/dist/core/evolagent.js +4 -4
- package/dist/core/message/im-renderer.js +4 -4
- package/dist/core/message/message-bridge.js +26 -1
- package/dist/core/message/message-processor.js +2 -24
- package/dist/core/session/session-fs-store.js +23 -0
- package/dist/core/session/session-manager.js +4 -1
- 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/package.json +5 -2
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 } 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';
|
|
@@ -408,10 +408,10 @@ export class AUNChannel {
|
|
|
408
408
|
}
|
|
409
409
|
return out;
|
|
410
410
|
}
|
|
411
|
-
buildGroupReplyContext(
|
|
411
|
+
buildGroupReplyContext(threadId, senderAid, encrypted, messageId, chatmode) {
|
|
412
412
|
const replyContext = { metadata: { encrypted, chatmode } };
|
|
413
|
-
if (
|
|
414
|
-
replyContext.threadId =
|
|
413
|
+
if (threadId)
|
|
414
|
+
replyContext.threadId = threadId;
|
|
415
415
|
replyContext.peerId = senderAid;
|
|
416
416
|
if (messageId)
|
|
417
417
|
replyContext.replyToMessageId = messageId;
|
|
@@ -731,8 +731,8 @@ export class AUNChannel {
|
|
|
731
731
|
logger.info(`${this.logPrefix()} No owner configured, skipping welcome message (will retry after auto-bind)`);
|
|
732
732
|
return;
|
|
733
733
|
}
|
|
734
|
-
const
|
|
735
|
-
const existingAgentMd = fs.existsSync(
|
|
734
|
+
const agentMdLocalPath = agentMdPathFn(aidName);
|
|
735
|
+
const existingAgentMd = fs.existsSync(agentMdLocalPath) ? fs.readFileSync(agentMdLocalPath, 'utf-8') : '';
|
|
736
736
|
const existingFrontmatterMatch = existingAgentMd.match(/^---\n([\s\S]*?)\n---/);
|
|
737
737
|
const existingFrontmatter = existingFrontmatterMatch?.[1] ?? '';
|
|
738
738
|
// Fetch owner's agent.md to derive name and validate type
|
|
@@ -778,8 +778,8 @@ tags:
|
|
|
778
778
|
EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
779
779
|
`;
|
|
780
780
|
// Write locally
|
|
781
|
-
fs.mkdirSync(path.dirname(
|
|
782
|
-
fs.writeFileSync(
|
|
781
|
+
fs.mkdirSync(path.dirname(agentMdLocalPath), { recursive: true });
|
|
782
|
+
fs.writeFileSync(agentMdLocalPath, newAgentMd, 'utf-8');
|
|
783
783
|
logger.info(`${this.logPrefix()} Updated agent.md for ${aidName}`);
|
|
784
784
|
// Publish to AUN network via auth.uploadAgentMd
|
|
785
785
|
try {
|
|
@@ -908,7 +908,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
908
908
|
const fromAid = msg.from ?? '';
|
|
909
909
|
const payload = msg.payload ?? '';
|
|
910
910
|
const text = this.extractTextPayload(payload, fromAid);
|
|
911
|
-
const
|
|
911
|
+
const threadId = typeof payload === 'object' && payload !== null ? payload.thread_id : undefined;
|
|
912
912
|
const messageId = msg.message_id ?? '';
|
|
913
913
|
const seq = msg.seq;
|
|
914
914
|
// 回声过滤:自己发出的消息会被 gateway fanout 回来,
|
|
@@ -953,7 +953,8 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
953
953
|
// device_id 仅 SDK 内部多实例去重用,evolclaw session 层面跨端共享会话
|
|
954
954
|
const chatId = fromAid;
|
|
955
955
|
// 解析对端身份(30天缓存)
|
|
956
|
-
const
|
|
956
|
+
const selfAgentDir = path.join(resolvePaths().agentsDir, this.config.aid);
|
|
957
|
+
const peerIdentity = await PeerIdentityCache.resolve('aun', fromAid, selfAgentDir, this.client, false);
|
|
957
958
|
const shortAid = this.getShortAid(fromAid);
|
|
958
959
|
const displayName = peerIdentity.name || shortAid;
|
|
959
960
|
// 详细 dispatch 决策日志:记录消息为何被路由到 agent
|
|
@@ -986,8 +987,8 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
986
987
|
const isSystemP2P = p2pPayloadType === 'event';
|
|
987
988
|
this.aidStatsCollector?.recordInbound(this.config.aid, fromAid, Buffer.byteLength(finalText, 'utf-8'), finalText, isSystemP2P, msgEncrypted, msgChatmode);
|
|
988
989
|
const replyContext = { metadata: { encrypted: msgEncrypted, chatmode: msgChatmode } };
|
|
989
|
-
if (
|
|
990
|
-
replyContext.threadId =
|
|
990
|
+
if (threadId)
|
|
991
|
+
replyContext.threadId = threadId;
|
|
991
992
|
this.dispatchMessage({
|
|
992
993
|
channelId: chatId,
|
|
993
994
|
userId: fromAid,
|
|
@@ -995,7 +996,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
995
996
|
chatType: 'private',
|
|
996
997
|
messageId,
|
|
997
998
|
seq,
|
|
998
|
-
|
|
999
|
+
threadId,
|
|
999
1000
|
mentions,
|
|
1000
1001
|
peerName: displayName || undefined,
|
|
1001
1002
|
peerType: peerIdentity.type,
|
|
@@ -1010,7 +1011,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1010
1011
|
const senderAid = msg.sender_aid ?? '';
|
|
1011
1012
|
const payload = msg.payload ?? '';
|
|
1012
1013
|
const text = this.extractTextPayload(payload, groupId, senderAid);
|
|
1013
|
-
const
|
|
1014
|
+
const threadId = typeof payload === 'object' && payload !== null ? payload.thread_id : undefined;
|
|
1014
1015
|
const messageId = msg.message_id ?? '';
|
|
1015
1016
|
const seq = msg.seq;
|
|
1016
1017
|
// Extract structured mentions from payload (e.g. payload.mentions: ["evolai.agentid.pub"])
|
|
@@ -1168,7 +1169,8 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1168
1169
|
finalText = parts.join('\n\n');
|
|
1169
1170
|
}
|
|
1170
1171
|
}
|
|
1171
|
-
const
|
|
1172
|
+
const selfAgentDir = path.join(resolvePaths().agentsDir, this.config.aid);
|
|
1173
|
+
const peerIdentity = await PeerIdentityCache.resolve('aun', senderAid, selfAgentDir, this.client, false);
|
|
1172
1174
|
const shortAid = this.getShortAid(senderAid);
|
|
1173
1175
|
const displayName = peerIdentity.name || shortAid;
|
|
1174
1176
|
// 详细 dispatch 决策日志:记录消息为何被路由到 agent
|
|
@@ -1200,9 +1202,9 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1200
1202
|
chatType: 'group',
|
|
1201
1203
|
messageId,
|
|
1202
1204
|
seq,
|
|
1203
|
-
|
|
1205
|
+
threadId,
|
|
1204
1206
|
mentions,
|
|
1205
|
-
replyContext: this.buildGroupReplyContext(
|
|
1207
|
+
replyContext: this.buildGroupReplyContext(threadId, senderAid, msgEncrypted, messageId, msgChatmode),
|
|
1206
1208
|
});
|
|
1207
1209
|
}
|
|
1208
1210
|
dispatchMessage(event) {
|
|
@@ -1252,8 +1254,8 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1252
1254
|
// Use caller-supplied replyContext (group path builds mentionUserIds);
|
|
1253
1255
|
// fall back to simple threadId-only context for private messages
|
|
1254
1256
|
let replyContext = event.replyContext;
|
|
1255
|
-
if (!replyContext && event.
|
|
1256
|
-
replyContext = { threadId: event.
|
|
1257
|
+
if (!replyContext && event.threadId) {
|
|
1258
|
+
replyContext = { threadId: event.threadId };
|
|
1257
1259
|
}
|
|
1258
1260
|
this.messageHandler({
|
|
1259
1261
|
channelId: event.channelId || '',
|
|
@@ -1266,7 +1268,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1266
1268
|
peerName: event.peerName,
|
|
1267
1269
|
peerType: event.peerType,
|
|
1268
1270
|
messageId: event.messageId,
|
|
1269
|
-
threadId: event.
|
|
1271
|
+
threadId: event.threadId,
|
|
1270
1272
|
mentions: mentionObjects,
|
|
1271
1273
|
replyContext,
|
|
1272
1274
|
}).catch(err => {
|
|
@@ -1707,6 +1709,8 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1707
1709
|
const channelId = entry.channelId;
|
|
1708
1710
|
const finalText = entry.text;
|
|
1709
1711
|
const context = entry.context;
|
|
1712
|
+
// 从 context.metadata.source 读取 source,默认为 'daemon'
|
|
1713
|
+
const source = context?.metadata?.source ?? 'daemon';
|
|
1710
1714
|
const payload = { type: 'text', text: finalText };
|
|
1711
1715
|
if (this.isGroupId(channelId)) {
|
|
1712
1716
|
const extracted = this.extractMentionAidsFromText(finalText);
|
|
@@ -1720,7 +1724,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1720
1724
|
if (context?.metadata?.chatmode)
|
|
1721
1725
|
payload.chatmode = context.metadata.chatmode;
|
|
1722
1726
|
// 诊断日志:记录 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}`);
|
|
1727
|
+
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
1728
|
const isGroup = this.isGroupId(channelId);
|
|
1725
1729
|
const targetAid = channelId;
|
|
1726
1730
|
const encryptTarget = isGroup ? channelId : targetAid;
|
|
@@ -1746,7 +1750,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1746
1750
|
logger.info(`${this.logPrefix()} group.send ok: group=${channelId} mid=${mid} encrypt=${encrypt} text=${finalText.slice(0, 60)}`);
|
|
1747
1751
|
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
1752
|
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);
|
|
1753
|
+
this.appendOutboundJsonl(channelId, finalText, mid, encrypt, context, true, 'text', source);
|
|
1750
1754
|
}
|
|
1751
1755
|
}
|
|
1752
1756
|
else {
|
|
@@ -1759,7 +1763,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1759
1763
|
logger.info(`${this.logPrefix()} message.send ok: to=${this.peerLabel(targetAid)} mid=${result.message_id} encrypt=${encrypt} text=${finalText.slice(0, 60)}`);
|
|
1760
1764
|
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
1765
|
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);
|
|
1766
|
+
this.appendOutboundJsonl(targetAid, finalText, result.message_id, encrypt, context, false, 'text', source);
|
|
1763
1767
|
}
|
|
1764
1768
|
}
|
|
1765
1769
|
return true;
|
|
@@ -1802,7 +1806,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1802
1806
|
}
|
|
1803
1807
|
}
|
|
1804
1808
|
/** 出站消息写入 messages.jsonl(message.send/group.send/thought.put 成功后调用) */
|
|
1805
|
-
appendOutboundJsonl(channelId, text, msgId, encrypt, context, isGroup, msgType = 'text') {
|
|
1809
|
+
appendOutboundJsonl(channelId, text, msgId, encrypt, context, isGroup, msgType = 'text', source = 'daemon') {
|
|
1806
1810
|
try {
|
|
1807
1811
|
const sessionsDir = resolvePaths().sessionsDir;
|
|
1808
1812
|
const selfId = this.config.aid;
|
|
@@ -1822,6 +1826,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1822
1826
|
encrypt,
|
|
1823
1827
|
chatmode,
|
|
1824
1828
|
msgType,
|
|
1829
|
+
source,
|
|
1825
1830
|
}));
|
|
1826
1831
|
}
|
|
1827
1832
|
catch (e) {
|
|
@@ -1854,12 +1859,24 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1854
1859
|
try {
|
|
1855
1860
|
const itemCount = Array.isArray(payload?.items) ? payload.items.length : 0;
|
|
1856
1861
|
const stage = payload?.stage ?? `items=${itemCount}`;
|
|
1857
|
-
// 提取 thought
|
|
1862
|
+
// 提取 thought 文本(只对 kind=text 的 item 写 jsonl,过滤 tool_use/tool_result 等结构化项)
|
|
1858
1863
|
const items = payload?.items;
|
|
1859
1864
|
let thoughtText;
|
|
1860
1865
|
if (Array.isArray(items) && items.length > 0) {
|
|
1861
1866
|
const lastItem = items[items.length - 1];
|
|
1862
|
-
|
|
1867
|
+
// 优先 text 字段(kind=text 的 item),否则 content
|
|
1868
|
+
if (lastItem?.kind === 'text' && lastItem.text) {
|
|
1869
|
+
thoughtText = lastItem.text;
|
|
1870
|
+
}
|
|
1871
|
+
else if (lastItem?.text) {
|
|
1872
|
+
thoughtText = lastItem.text;
|
|
1873
|
+
}
|
|
1874
|
+
else if (lastItem?.content) {
|
|
1875
|
+
thoughtText = lastItem.content;
|
|
1876
|
+
}
|
|
1877
|
+
else if (typeof lastItem === 'string') {
|
|
1878
|
+
thoughtText = lastItem;
|
|
1879
|
+
}
|
|
1863
1880
|
}
|
|
1864
1881
|
if (this.isGroupId(channelId)) {
|
|
1865
1882
|
params.group_id = targetId;
|
|
@@ -1867,7 +1884,10 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1867
1884
|
const tid = putRes?.thought_id;
|
|
1868
1885
|
logger.info(`${this.logPrefix()} thought.put ok group=${targetId} task=${taskId} stage=${stage} encrypt=${encrypt} tid=${tid ?? '?'}`);
|
|
1869
1886
|
this.eventBus?.publish?.({ type: 'message:thought-put', agentName: this.config.aid, channelId, taskId, text: thoughtText });
|
|
1870
|
-
// thought jsonl
|
|
1887
|
+
// 文本类 thought 写入 jsonl(只对有 text 的 item,过滤 tool 等结构化项)
|
|
1888
|
+
if (thoughtText) {
|
|
1889
|
+
this.appendOutboundJsonl(channelId, thoughtText, tid ?? `thought-${Date.now()}`, encrypt, context, true, 'thought', 'daemon');
|
|
1890
|
+
}
|
|
1871
1891
|
}
|
|
1872
1892
|
else {
|
|
1873
1893
|
params.to = targetId;
|
|
@@ -1875,7 +1895,9 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1875
1895
|
const tid = putRes?.thought_id;
|
|
1876
1896
|
logger.info(`${this.logPrefix()} thought.put ok p2p=${this.peerLabel(targetId)} task=${taskId} stage=${stage} encrypt=${encrypt} tid=${tid ?? '?'}`);
|
|
1877
1897
|
this.eventBus?.publish?.({ type: 'message:thought-put', agentName: this.config.aid, channelId, taskId, text: thoughtText });
|
|
1878
|
-
|
|
1898
|
+
if (thoughtText) {
|
|
1899
|
+
this.appendOutboundJsonl(channelId, thoughtText, tid ?? `thought-${Date.now()}`, encrypt, context, false, 'thought', 'daemon');
|
|
1900
|
+
}
|
|
1879
1901
|
}
|
|
1880
1902
|
}
|
|
1881
1903
|
catch (e) {
|
|
@@ -2303,13 +2325,13 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
2303
2325
|
loadSelfName(aid) {
|
|
2304
2326
|
try {
|
|
2305
2327
|
const aidName = aid.startsWith('@') ? aid.slice(1) : aid;
|
|
2306
|
-
const
|
|
2307
|
-
if (!fs.existsSync(
|
|
2328
|
+
const mdPath = agentMdPathFn(aidName);
|
|
2329
|
+
if (!fs.existsSync(mdPath)) {
|
|
2308
2330
|
// 异步拉取,不阻塞连接流程
|
|
2309
2331
|
this.fetchAndCacheSelfName(aidName);
|
|
2310
2332
|
return undefined;
|
|
2311
2333
|
}
|
|
2312
|
-
const content = fs.readFileSync(
|
|
2334
|
+
const content = fs.readFileSync(mdPath, 'utf-8');
|
|
2313
2335
|
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
2314
2336
|
if (!fmMatch)
|
|
2315
2337
|
return undefined;
|
|
@@ -2420,6 +2442,7 @@ export class AUNChannelPlugin {
|
|
|
2420
2442
|
});
|
|
2421
2443
|
const adapter = {
|
|
2422
2444
|
channelName: inst.name,
|
|
2445
|
+
channelKey: inst.name, // channelName 实际上就是 channelKey
|
|
2423
2446
|
capabilities: { file: true, image: true, interaction: true, markdown: true, thought: true, status: true },
|
|
2424
2447
|
send: async (envelope, payload) => {
|
|
2425
2448
|
const ctx = envelope.replyContext;
|
|
@@ -2464,10 +2487,8 @@ export class AUNChannelPlugin {
|
|
|
2464
2487
|
await channel.sendThought(channelId, envelope.taskId, aunPayload, ctx);
|
|
2465
2488
|
}
|
|
2466
2489
|
else {
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
channel.sendStructured(channelId, aunPayload, ctx),
|
|
2470
|
-
]);
|
|
2490
|
+
// interactive 模式不发 thought.put,只写入消息历史
|
|
2491
|
+
await channel.sendStructured(channelId, aunPayload, ctx);
|
|
2471
2492
|
}
|
|
2472
2493
|
return;
|
|
2473
2494
|
}
|
|
@@ -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;
|
package/dist/cli/agent.js
CHANGED
|
@@ -239,22 +239,30 @@ export async function agentShow(aid) {
|
|
|
239
239
|
}
|
|
240
240
|
export async function agentCreateInteractive(opts = {}) {
|
|
241
241
|
const p = resolvePaths();
|
|
242
|
-
const
|
|
242
|
+
const ownRl = !opts.rl;
|
|
243
|
+
const rl = opts.rl ?? readline.createInterface({
|
|
243
244
|
input: opts.stdin || process.stdin,
|
|
244
245
|
output: opts.stdout || process.stdout,
|
|
245
246
|
});
|
|
246
247
|
const ask = (q) => new Promise(r => rl.question(q, r));
|
|
247
248
|
try {
|
|
249
|
+
const { isValidAid, aidCreate } = await import('../aun/aid/index.js');
|
|
248
250
|
const aidPrompt = opts.suggestedName
|
|
249
251
|
? `AID [${opts.suggestedName}]: `
|
|
250
252
|
: 'AID (e.g. mybot.agentid.pub): ';
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
253
|
+
let aid = '';
|
|
254
|
+
while (!aid) {
|
|
255
|
+
const aidInput = (await ask(aidPrompt)).trim();
|
|
256
|
+
const candidate = aidInput || opts.suggestedName;
|
|
257
|
+
if (!candidate) {
|
|
258
|
+
console.log(' ⚠ AID is required.');
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
if (!isValidAid(candidate)) {
|
|
262
|
+
console.log(` ⚠ Invalid AID "${candidate}": must be a valid multi-level domain (e.g. mybot.agentid.pub)`);
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
aid = candidate;
|
|
258
266
|
}
|
|
259
267
|
const agentDirPath = path.join(p.agentsDir, aid);
|
|
260
268
|
const configExists = fs.existsSync(path.join(agentDirPath, 'config.json'));
|
|
@@ -311,26 +319,48 @@ export async function agentCreateInteractive(opts = {}) {
|
|
|
311
319
|
return { ok: false, error: `No baseagent CLI detected on PATH. Install one of: ${BASEAGENT_CANDIDATES.join('/')}` };
|
|
312
320
|
}
|
|
313
321
|
const defaultBa = pickDefaultBaseagent(available);
|
|
314
|
-
let baseagent
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
322
|
+
let baseagent;
|
|
323
|
+
if (available.length === 1) {
|
|
324
|
+
console.log(` Baseagent: ${defaultBa}`);
|
|
325
|
+
baseagent = defaultBa;
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
328
|
+
let chosen = null;
|
|
329
|
+
while (chosen === null) {
|
|
330
|
+
const input = (await ask(`Baseagent (${available.join('/')}) [${defaultBa}]: `)).trim() || defaultBa;
|
|
331
|
+
if (!BASEAGENT_CANDIDATES.includes(input)) {
|
|
332
|
+
console.log(` Invalid choice. Options: ${BASEAGENT_CANDIDATES.join('/')}`);
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
if (!available.includes(input)) {
|
|
336
|
+
console.log(` ${input} not detected on PATH. Available: ${available.join('/')}`);
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
chosen = input;
|
|
340
|
+
}
|
|
341
|
+
baseagent = chosen;
|
|
342
|
+
}
|
|
343
|
+
// Owner
|
|
344
|
+
let owner;
|
|
345
|
+
while (true) {
|
|
346
|
+
const ownerInput = (await ask('Owner AID (leave empty for auto-bind on first message): ')).trim();
|
|
347
|
+
if (!ownerInput) {
|
|
348
|
+
owner = undefined;
|
|
349
|
+
break;
|
|
320
350
|
}
|
|
321
|
-
if (!
|
|
322
|
-
console.log(` ${
|
|
351
|
+
if (!isValidAid(ownerInput)) {
|
|
352
|
+
console.log(` ⚠ Invalid Owner AID "${ownerInput}": must be a valid multi-level domain (e.g. alice.agentid.pub)`);
|
|
323
353
|
continue;
|
|
324
354
|
}
|
|
325
|
-
|
|
355
|
+
owner = ownerInput;
|
|
356
|
+
break;
|
|
326
357
|
}
|
|
327
|
-
// Owner
|
|
328
|
-
const owner = (await ask('Owner AID (leave empty for auto-bind on first message): ')).trim() || undefined;
|
|
329
358
|
// Name + description for agent.md
|
|
330
359
|
const defaultName = aid.split('.')[0];
|
|
331
360
|
const agentName = (await ask(`Display name [${defaultName}]: `)).trim() || defaultName;
|
|
332
361
|
const agentDescription = (await ask('Description (optional): ')).trim() || '';
|
|
333
|
-
|
|
362
|
+
if (ownRl)
|
|
363
|
+
rl.close();
|
|
334
364
|
const agentConfig = {
|
|
335
365
|
$schema_version: CONFIG_SCHEMA_VERSION,
|
|
336
366
|
aid,
|
|
@@ -359,31 +389,64 @@ export async function agentCreateInteractive(opts = {}) {
|
|
|
359
389
|
const agentMdPath = path.join(aunPath, 'AIDs', aid, 'agent.md');
|
|
360
390
|
fs.mkdirSync(path.dirname(agentMdPath), { recursive: true });
|
|
361
391
|
fs.writeFileSync(agentMdPath, content, 'utf-8');
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
392
|
+
// Upload with retry (3 attempts, 2s delay between retries)
|
|
393
|
+
const MAX_ATTEMPTS = 3;
|
|
394
|
+
const RETRY_DELAY_MS = 2000;
|
|
395
|
+
let lastError;
|
|
396
|
+
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
|
|
397
|
+
try {
|
|
398
|
+
if (attempt > 1) {
|
|
399
|
+
process.stdout.write(` ↻ agent.md 上传重试 (${attempt}/${MAX_ATTEMPTS})...\n`);
|
|
400
|
+
await new Promise(r => setTimeout(r, RETRY_DELAY_MS));
|
|
401
|
+
}
|
|
402
|
+
await agentmdPut(content, { aid, aunPath });
|
|
403
|
+
agentmdUploaded = true;
|
|
404
|
+
break;
|
|
405
|
+
}
|
|
406
|
+
catch (e) {
|
|
407
|
+
lastError = e;
|
|
408
|
+
}
|
|
365
409
|
}
|
|
366
|
-
|
|
367
|
-
console.warn(` ⚠ agent.md upload failed: ${
|
|
410
|
+
if (!agentmdUploaded) {
|
|
411
|
+
console.warn(` ⚠ agent.md upload failed: ${lastError?.message || lastError}`);
|
|
368
412
|
console.warn(` → Retry later with: evolclaw aid agentmd put ${aid}`);
|
|
369
413
|
}
|
|
414
|
+
// Yield to allow the SDK WebSocket to fully close before IPC
|
|
415
|
+
await new Promise(r => setTimeout(r, 0));
|
|
370
416
|
}
|
|
371
417
|
catch (e) {
|
|
372
418
|
console.warn(` ⚠ agent.md generation failed: ${e?.message || e}`);
|
|
373
419
|
}
|
|
420
|
+
// Attempt hot-load via IPC (if daemon is running)
|
|
421
|
+
let hotLoaded = false;
|
|
422
|
+
let hotLoadError;
|
|
423
|
+
try {
|
|
424
|
+
const ipcResult = await ipcQuery(p.socket, { type: 'evolagent.load', aid });
|
|
425
|
+
if (ipcResult?.ok) {
|
|
426
|
+
hotLoaded = true;
|
|
427
|
+
}
|
|
428
|
+
else if (ipcResult) {
|
|
429
|
+
hotLoadError = ipcResult.error;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
catch { /* daemon not running */ }
|
|
374
433
|
return {
|
|
375
434
|
ok: true,
|
|
376
435
|
aid,
|
|
377
436
|
configPath: toPosix(path.join(agentDirPath, 'config.json')),
|
|
378
437
|
aidCreated,
|
|
379
438
|
agentmdUploaded,
|
|
439
|
+
hotLoaded,
|
|
440
|
+
hotLoadError,
|
|
380
441
|
};
|
|
381
442
|
}
|
|
382
443
|
finally {
|
|
383
|
-
|
|
384
|
-
|
|
444
|
+
if (ownRl) {
|
|
445
|
+
try {
|
|
446
|
+
rl.close();
|
|
447
|
+
}
|
|
448
|
+
catch { /* ignore */ }
|
|
385
449
|
}
|
|
386
|
-
catch { /* ignore */ }
|
|
387
450
|
}
|
|
388
451
|
}
|
|
389
452
|
export async function agentCreateNonInteractive(opts) {
|
|
@@ -482,24 +545,51 @@ export async function agentCreateNonInteractive(opts) {
|
|
|
482
545
|
const agentMdPath = path.join(aunPath, 'AIDs', opts.aid, 'agent.md');
|
|
483
546
|
fs.mkdirSync(path.dirname(agentMdPath), { recursive: true });
|
|
484
547
|
fs.writeFileSync(agentMdPath, content, 'utf-8');
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
548
|
+
const MAX_ATTEMPTS = 3;
|
|
549
|
+
const RETRY_DELAY_MS = 2000;
|
|
550
|
+
let lastError;
|
|
551
|
+
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
|
|
552
|
+
try {
|
|
553
|
+
if (attempt > 1)
|
|
554
|
+
await new Promise(r => setTimeout(r, RETRY_DELAY_MS));
|
|
555
|
+
await agentmdPut(content, { aid: opts.aid, aunPath });
|
|
556
|
+
agentmdUploaded = true;
|
|
557
|
+
break;
|
|
558
|
+
}
|
|
559
|
+
catch (e) {
|
|
560
|
+
lastError = e;
|
|
561
|
+
}
|
|
488
562
|
}
|
|
489
|
-
|
|
490
|
-
console.warn(`⚠ agent.md upload failed: ${
|
|
563
|
+
if (!agentmdUploaded) {
|
|
564
|
+
console.warn(`⚠ agent.md upload failed: ${lastError?.message || lastError}`);
|
|
491
565
|
console.warn(` Retry later with: evolclaw aid agentmd put ${opts.aid}`);
|
|
492
566
|
}
|
|
567
|
+
await new Promise(r => setTimeout(r, 0));
|
|
493
568
|
}
|
|
494
569
|
catch (e) {
|
|
495
570
|
console.warn(`⚠ agent.md generation failed: ${e?.message || e}`);
|
|
496
571
|
}
|
|
572
|
+
// Attempt hot-load via IPC (if daemon is running)
|
|
573
|
+
let hotLoaded = false;
|
|
574
|
+
let hotLoadError;
|
|
575
|
+
try {
|
|
576
|
+
const ipcResult = await ipcQuery(p.socket, { type: 'evolagent.load', aid: opts.aid });
|
|
577
|
+
if (ipcResult?.ok) {
|
|
578
|
+
hotLoaded = true;
|
|
579
|
+
}
|
|
580
|
+
else if (ipcResult) {
|
|
581
|
+
hotLoadError = ipcResult.error;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
catch { /* daemon not running */ }
|
|
497
585
|
return {
|
|
498
586
|
ok: true,
|
|
499
587
|
aid: opts.aid,
|
|
500
588
|
configPath: toPosix(path.join(agentDirPath, 'config.json')),
|
|
501
589
|
aidCreated,
|
|
502
590
|
agentmdUploaded,
|
|
591
|
+
hotLoaded,
|
|
592
|
+
hotLoadError,
|
|
503
593
|
};
|
|
504
594
|
}
|
|
505
595
|
// ==================== agentSyncAids ====================
|
|
@@ -665,7 +755,12 @@ export async function agentSet(aid, key, rawValue) {
|
|
|
665
755
|
}
|
|
666
756
|
const value = parseJsonValue(rawValue);
|
|
667
757
|
setNestedValue(config, key, value);
|
|
668
|
-
|
|
758
|
+
try {
|
|
759
|
+
saveAgent(config);
|
|
760
|
+
}
|
|
761
|
+
catch (e) {
|
|
762
|
+
return { ok: false, error: e?.message || String(e) };
|
|
763
|
+
}
|
|
669
764
|
// Try hot-reload
|
|
670
765
|
let reloaded = false;
|
|
671
766
|
try {
|
|
@@ -733,7 +828,7 @@ export async function agentChannelUpsert(opts) {
|
|
|
733
828
|
return {
|
|
734
829
|
ok: true,
|
|
735
830
|
aid: opts.aid,
|
|
736
|
-
channelKey: `${opts.
|
|
831
|
+
channelKey: `${opts.channel.type}#${encodeURIComponent(opts.aid)}#${opts.channel.name}`,
|
|
737
832
|
reloaded,
|
|
738
833
|
};
|
|
739
834
|
}
|