evolclaw 3.1.11 → 3.2.0
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 +24 -0
- package/README.md +26 -0
- package/dist/agents/kit-renderer.js +5 -1
- package/dist/agents/manifest-engine.js +108 -35
- package/dist/agents/message-renderer.js +2 -0
- package/dist/aun/aid/control-aid.js +67 -0
- package/dist/aun/aid/identity.js +20 -7
- package/dist/aun/aid/store.js +2 -2
- package/dist/channels/aun.js +161 -61
- package/dist/channels/feishu.js +3 -3
- package/dist/cli/agent.js +38 -10
- package/dist/cli/index.js +31 -3
- package/dist/cli/init-channel.js +38 -148
- package/dist/cli/init.js +162 -82
- package/dist/config-store.js +38 -7
- package/dist/core/cache/file-cache.js +216 -0
- package/dist/core/command-handler.js +291 -68
- package/dist/core/evolagent-registry.js +3 -0
- package/dist/core/evolagent.js +28 -23
- package/dist/core/message/command-handler-agent-control.js +153 -0
- package/dist/core/message/create-status.js +67 -0
- package/dist/core/message/message-bridge.js +5 -3
- package/dist/core/message/message-processor.js +44 -36
- package/dist/core/message/message-queue.js +13 -6
- package/dist/core/model/model-scope.js +39 -6
- package/dist/core/session/adapters/claude-session-file-adapter.js +48 -5
- package/dist/evolclaw-config.js +11 -0
- package/dist/index.js +57 -2
- package/dist/ipc.js +6 -0
- package/dist/paths.js +7 -3
- package/kits/templates/message-fragments/item.md +1 -1
- package/package.json +1 -1
package/dist/channels/aun.js
CHANGED
|
@@ -58,6 +58,37 @@ function getEvolclawVersion() {
|
|
|
58
58
|
}
|
|
59
59
|
return _cachedVersion;
|
|
60
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* 把 AUNChannel 投递的 opts 映射成渠道无关的 InboundMessage。
|
|
63
|
+
*
|
|
64
|
+
* registerBridge 适配回调用它替代手抄字段——历史上手抄漏掉了
|
|
65
|
+
* sameDevice/sameNetwork/sameEgressIp,proximity 在此被吞,eck-debug 永远 false。
|
|
66
|
+
* 抽成纯函数后可单测锁字段,杜绝再漏。
|
|
67
|
+
*/
|
|
68
|
+
export function aunOptsToInbound(opts, channel, channelType) {
|
|
69
|
+
return {
|
|
70
|
+
channel,
|
|
71
|
+
channelType,
|
|
72
|
+
channelId: opts.channelId,
|
|
73
|
+
selfAID: opts.selfAID,
|
|
74
|
+
groupId: opts.groupId,
|
|
75
|
+
content: opts.content,
|
|
76
|
+
chatType: opts.chatType || 'private',
|
|
77
|
+
peerId: opts.peerId || '',
|
|
78
|
+
peerName: opts.peerName,
|
|
79
|
+
peerType: opts.peerType,
|
|
80
|
+
sameDevice: opts.sameDevice,
|
|
81
|
+
sameNetwork: opts.sameNetwork,
|
|
82
|
+
sameEgressIp: opts.sameEgressIp,
|
|
83
|
+
messageId: opts.messageId,
|
|
84
|
+
mentions: opts.mentions,
|
|
85
|
+
mentionAids: opts.mentionAids,
|
|
86
|
+
threadId: opts.threadId,
|
|
87
|
+
replyContext: opts.replyContext,
|
|
88
|
+
source: opts.source,
|
|
89
|
+
images: opts.images,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
61
92
|
export class AUNChannel {
|
|
62
93
|
config;
|
|
63
94
|
client = null;
|
|
@@ -427,6 +458,7 @@ export class AUNChannel {
|
|
|
427
458
|
_selfName; // 本地 agent.md 中的 name,首次 connect 时读取
|
|
428
459
|
_chatId = ''; // aid:device_id:slot_id — 多实例回声过滤
|
|
429
460
|
seenMessages = new Map();
|
|
461
|
+
groupNameCache = new Map(); // groupId → 群显示名(进程内缓存,群名极少变)
|
|
430
462
|
peerInfoCache = new Map();
|
|
431
463
|
messageSeqMap = new Map(); // messageId → seq (for ack)
|
|
432
464
|
sentCount = new Map(); // channelId → 已发消息计数(用于判断最终回复)
|
|
@@ -591,13 +623,16 @@ export class AUNChannel {
|
|
|
591
623
|
logger.debug(`${this.logPrefix()}[DIAG] message.received: kind=${kind} keys=${keys}`);
|
|
592
624
|
this.handleIncomingPrivateMessage(data);
|
|
593
625
|
});
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
626
|
+
// pureIdentity(控制 AID):协议层不接群消息,不注册 group 创建监听
|
|
627
|
+
if (!this.config.pureIdentity) {
|
|
628
|
+
client.on('group.message_created', (data) => {
|
|
629
|
+
this.trace('IN', 'group.message_created', data);
|
|
630
|
+
const gid = (data && typeof data === 'object') ? data.group_id ?? '' : '';
|
|
631
|
+
const sender = (data && typeof data === 'object') ? data.sender_aid ?? '' : '';
|
|
632
|
+
logger.debug(`${this.logPrefix()}[DIAG] group.message_created: group_id=${gid} sender=${sender}`);
|
|
633
|
+
this.handleIncomingGroupMessage(data);
|
|
634
|
+
});
|
|
635
|
+
}
|
|
601
636
|
client.on('connection.state', (data) => {
|
|
602
637
|
// trace is handled inside handleConnectionState with throttling
|
|
603
638
|
this.handleConnectionState(data);
|
|
@@ -626,11 +661,14 @@ export class AUNChannel {
|
|
|
626
661
|
const d = data;
|
|
627
662
|
logger.warn(`${this.logPrefix()} Message undecryptable: from=${d.from} mid=${d.message_id} err=${d._decrypt_error}`);
|
|
628
663
|
});
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
664
|
+
// pureIdentity(控制 AID):不注册 group 解密失败监听
|
|
665
|
+
if (!this.config.pureIdentity) {
|
|
666
|
+
client.on('group.message_undecryptable', (data) => {
|
|
667
|
+
this.trace('IN', 'group.message_undecryptable', data);
|
|
668
|
+
const d = data;
|
|
669
|
+
logger.warn(`${this.logPrefix()} Group message undecryptable: group=${d.group_id} from=${d.from} mid=${d.message_id} err=${d._decrypt_error}`);
|
|
670
|
+
});
|
|
671
|
+
}
|
|
634
672
|
// Authenticate(拿权威 gateway 用于日志/状态;connect 内部也会复用 token)
|
|
635
673
|
try {
|
|
636
674
|
logger.info(`${this.logPrefix()} Authenticating as ${aidName}...`);
|
|
@@ -676,7 +714,8 @@ export class AUNChannel {
|
|
|
676
714
|
this._aid = this.client.aid ?? undefined;
|
|
677
715
|
const deviceId = this.client._device_id ?? '';
|
|
678
716
|
this._chatId = this._aid ? `${this._aid}:${deviceId}:` : '';
|
|
679
|
-
|
|
717
|
+
// pureIdentity(控制 AID):无 agent.md,跳过自身 agent.md 拉取,省一次 404
|
|
718
|
+
this._selfName = this.config.pureIdentity ? undefined : this.loadSelfName(aidName);
|
|
680
719
|
if (this._selfName && this.aidStatsCollector)
|
|
681
720
|
this.aidStatsCollector.setSelfName(this.config.aid, this._selfName);
|
|
682
721
|
this.connected = true;
|
|
@@ -697,7 +736,10 @@ export class AUNChannel {
|
|
|
697
736
|
appendAidEvent({ ts: Date.now(), iso: new Date().toISOString(), event: 'connected', aid: this.config.aid, gateway: this.gatewayUrl });
|
|
698
737
|
appendAidLifecycle({ ts: Date.now(), iso: new Date().toISOString(), event: 'connected', aid: this.config.aid, gateway: this.gatewayUrl });
|
|
699
738
|
// Send welcome message to owner after first connection
|
|
700
|
-
|
|
739
|
+
// pureIdentity(控制 AID):跳过 evolagent onboarding(根除 warn 噪声 + 永不 agentmdPut)
|
|
740
|
+
if (!this.config.pureIdentity) {
|
|
741
|
+
await this.sendWelcomeMessage();
|
|
742
|
+
}
|
|
701
743
|
}
|
|
702
744
|
catch (e) {
|
|
703
745
|
this.trace('OUT', 'client.connect.error', { error: String(e) });
|
|
@@ -968,6 +1010,9 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
968
1010
|
const threadId = typeof payload === 'object' && payload !== null ? payload.thread_id : undefined;
|
|
969
1011
|
const messageId = msg.message_id ?? '';
|
|
970
1012
|
const seq = msg.seq;
|
|
1013
|
+
// Observer forward (inbound):在所有过滤之前转发原始明文 payload。
|
|
1014
|
+
// forwardInbound 内部排除 self-echo 与 from-owner。
|
|
1015
|
+
this.forwardInbound(fromAid, seq, payload);
|
|
971
1016
|
// 回声过滤:自己发出的消息会被 gateway fanout 回来,
|
|
972
1017
|
// 只有 from_aid == self 且 chat_id 不匹配时才丢弃(说明是其它实例发的)
|
|
973
1018
|
const msgChatId = typeof payload === 'object' && payload !== null && payload.chat_id;
|
|
@@ -1056,6 +1101,9 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1056
1101
|
const threadId = typeof payload === 'object' && payload !== null ? payload.thread_id : undefined;
|
|
1057
1102
|
const messageId = msg.message_id ?? '';
|
|
1058
1103
|
const seq = msg.seq;
|
|
1104
|
+
// Observer forward (inbound):群聊消息在所有过滤之前转发原始明文 payload。
|
|
1105
|
+
// forwardInbound 内部排除 self-echo 与 from-owner。
|
|
1106
|
+
this.forwardInbound(senderAid, seq, payload);
|
|
1059
1107
|
// Extract structured mentions from payload (e.g. payload.mentions: ["evolai.agentid.pub"])
|
|
1060
1108
|
const payloadMentions = Array.isArray(payload?.mentions)
|
|
1061
1109
|
? payload.mentions.filter((m) => typeof m === 'string')
|
|
@@ -1216,6 +1264,12 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1216
1264
|
logger.info(`${this.logPrefix()} Group dispatched: group=${groupId} sender=${shortAid}(${displayName}) mode=${dispatchMode} mid=${messageId} chatmode=${msgChatmode ?? 'none'} text=${finalText.slice(0, 60)}`);
|
|
1217
1265
|
appendAidEvent({ ts: Date.now(), iso: new Date().toISOString(), event: 'message_in', aid: this.config.aid, from: senderAid, msgId: messageId, kind: 'text', len: finalText.length, groupId });
|
|
1218
1266
|
this.aidStatsCollector?.recordInbound(this.config.aid, senderAid, Buffer.byteLength(finalText, 'utf-8'), finalText, payloadType === 'event', msgEncrypted, msgChatmode);
|
|
1267
|
+
// 渲染用完整 @ 列表:结构化 payload.mentions + 正文 @aid 兜底,去重(含 self / "all")。
|
|
1268
|
+
// 与上面用于过滤/回复的精简 mentions 独立——这份不丢任何被 @ 的 AID。
|
|
1269
|
+
const renderMentionAids = Array.from(new Set([
|
|
1270
|
+
...payloadMentions,
|
|
1271
|
+
...this.extractMentionAidsFromText(text),
|
|
1272
|
+
]));
|
|
1219
1273
|
this.dispatchMessage({
|
|
1220
1274
|
channelId: groupId,
|
|
1221
1275
|
groupId,
|
|
@@ -1231,6 +1285,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1231
1285
|
seq,
|
|
1232
1286
|
threadId,
|
|
1233
1287
|
mentions,
|
|
1288
|
+
mentionAids: renderMentionAids.length > 0 ? renderMentionAids : undefined,
|
|
1234
1289
|
replyContext: this.buildGroupReplyContext(threadId, senderAid, msgEncrypted, messageId, msgChatmode),
|
|
1235
1290
|
images: inboundImages.length > 0 ? inboundImages : undefined,
|
|
1236
1291
|
});
|
|
@@ -1301,32 +1356,68 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1301
1356
|
messageId: event.messageId,
|
|
1302
1357
|
threadId: event.threadId,
|
|
1303
1358
|
mentions: mentionObjects,
|
|
1359
|
+
mentionAids: event.mentionAids,
|
|
1304
1360
|
replyContext,
|
|
1305
1361
|
images: event.images,
|
|
1306
1362
|
}).catch(err => {
|
|
1307
1363
|
logger.error(`${this.logPrefix()} Message handler error:`, err);
|
|
1308
1364
|
});
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1365
|
+
}
|
|
1366
|
+
// ── 观察者模式(Observer Mode) ──────────────────────────────
|
|
1367
|
+
//
|
|
1368
|
+
// observable=true 时,Agent 收发的每条 AUN 消息(原始信封 + 解密后明文
|
|
1369
|
+
// payload)镜像一份给 owners[]。入站在所有过滤之前转发;出站在真实发送
|
|
1370
|
+
// 成功后转发。外层一律明文。详见 docs/observer-mode-design.md。
|
|
1371
|
+
// observable / owners 不在此处缓存——由 daemon 注入 resolver,从 EvolAgent 的
|
|
1372
|
+
// in-memory merged config(启动/重启/热重载时统一更新的唯一缓存)读取,避免重复缓存。
|
|
1373
|
+
observerConfigResolver;
|
|
1374
|
+
/** 注入观察者配置读取器(daemon 侧从 EvolAgent merged config 读)。 */
|
|
1375
|
+
setObserverConfigResolver(fn) {
|
|
1376
|
+
this.observerConfigResolver = fn;
|
|
1377
|
+
}
|
|
1378
|
+
/** 读取 observable 开关 + owners;无 resolver(如未接入 daemon)时视为关闭。 */
|
|
1379
|
+
getObserverConfig() {
|
|
1380
|
+
return this.observerConfigResolver?.() ?? { observable: false, owners: [] };
|
|
1381
|
+
}
|
|
1382
|
+
/**
|
|
1383
|
+
* 入站转发:到达本 AID 的消息全部转发,排除 self-echo。
|
|
1384
|
+
* 若消息发送方本身是 owner,则不转发给该 owner,但仍转发给其他 owner。
|
|
1385
|
+
* 调用点须在所有过滤逻辑之前,payload 为 SDK 解密后的明文。
|
|
1386
|
+
*/
|
|
1387
|
+
forwardInbound(from, seq, payload) {
|
|
1388
|
+
if (!this.connected || !this.client)
|
|
1389
|
+
return;
|
|
1390
|
+
const { observable, owners } = this.getObserverConfig();
|
|
1391
|
+
if (!observable || owners.length === 0)
|
|
1392
|
+
return;
|
|
1393
|
+
if (this._aid && from === this._aid)
|
|
1394
|
+
return; // self-echo:已在出站转过
|
|
1395
|
+
// 排除来源 owner(不把"owner A 发来的"再转回 A),但仍转给其他 owner。
|
|
1396
|
+
const recipientOwners = owners.filter(o => o !== from);
|
|
1397
|
+
if (recipientOwners.length === 0)
|
|
1398
|
+
return;
|
|
1399
|
+
this.emitForward('inbound', { from, to: this.config.aid, seq, payload }, recipientOwners);
|
|
1316
1400
|
}
|
|
1317
1401
|
/**
|
|
1318
|
-
*
|
|
1319
|
-
*
|
|
1402
|
+
* 出站转发:Agent 经 AUN 真实发出的消息全部转发。
|
|
1403
|
+
* to 为对端(私聊 AID / 群 ID),payload 为实际发送的明文 payload。
|
|
1404
|
+
* 若 to 本身是 owner,排除该 owner(不把"回复 A"转发给 A 自己)。
|
|
1320
1405
|
*/
|
|
1321
|
-
|
|
1406
|
+
forwardOutbound(to, payload) {
|
|
1322
1407
|
if (!this.connected || !this.client)
|
|
1323
1408
|
return;
|
|
1324
|
-
const
|
|
1325
|
-
if (!
|
|
1409
|
+
const { observable, owners } = this.getObserverConfig();
|
|
1410
|
+
if (!observable || owners.length === 0)
|
|
1326
1411
|
return;
|
|
1327
|
-
|
|
1328
|
-
|
|
1412
|
+
// 过滤:若 to 本身是 owner,不转发给该 owner(避免"回复你"转给你自己);
|
|
1413
|
+
// 但仍转发给其他 owner。
|
|
1414
|
+
const recipientOwners = owners.filter(o => o !== to);
|
|
1415
|
+
if (recipientOwners.length === 0)
|
|
1329
1416
|
return;
|
|
1417
|
+
this.emitForward('outbound', { from: this.config.aid, to, payload }, recipientOwners);
|
|
1418
|
+
}
|
|
1419
|
+
/** 实际投递 observer.forward 给每个 owner,外层一律明文。 */
|
|
1420
|
+
emitForward(direction, original, owners) {
|
|
1330
1421
|
const forwardPayload = {
|
|
1331
1422
|
type: 'observer.forward',
|
|
1332
1423
|
direction,
|
|
@@ -1340,8 +1431,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1340
1431
|
},
|
|
1341
1432
|
};
|
|
1342
1433
|
for (const ownerAid of owners) {
|
|
1343
|
-
|
|
1344
|
-
this.callAndTrace('message.send', { to: ownerAid, payload: forwardPayload, encrypt })
|
|
1434
|
+
this.callAndTrace('message.send', { to: ownerAid, payload: forwardPayload, encrypt: false })
|
|
1345
1435
|
.catch(e => logger.debug(`${this.logPrefix()} observer.forward to ${ownerAid} failed: ${e}`));
|
|
1346
1436
|
}
|
|
1347
1437
|
}
|
|
@@ -1820,12 +1910,8 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1820
1910
|
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 });
|
|
1821
1911
|
this.aidStatsCollector?.recordOutbound(this.config.aid, channelId, Buffer.byteLength(finalText, 'utf-8'), finalText, false, encrypt, context?.metadata?.chatmode);
|
|
1822
1912
|
this.appendOutboundJsonl(channelId, finalText, mid, encrypt, context, true, 'text', source);
|
|
1823
|
-
// Observer forward: outbound (group)
|
|
1824
|
-
this.
|
|
1825
|
-
from: this.config.aid,
|
|
1826
|
-
to: channelId,
|
|
1827
|
-
payload: { type: 'text', text: finalText },
|
|
1828
|
-
});
|
|
1913
|
+
// Observer forward: outbound (group) — 转发实际发出的明文 payload
|
|
1914
|
+
this.forwardOutbound(channelId, payload);
|
|
1829
1915
|
}
|
|
1830
1916
|
}
|
|
1831
1917
|
else {
|
|
@@ -1839,12 +1925,8 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1839
1925
|
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 });
|
|
1840
1926
|
this.aidStatsCollector?.recordOutbound(this.config.aid, targetAid, Buffer.byteLength(finalText, 'utf-8'), finalText, false, encrypt, context?.metadata?.chatmode);
|
|
1841
1927
|
this.appendOutboundJsonl(targetAid, finalText, result.message_id, encrypt, context, false, 'text', source);
|
|
1842
|
-
// Observer forward: outbound (private)
|
|
1843
|
-
this.
|
|
1844
|
-
from: this.config.aid,
|
|
1845
|
-
to: targetAid,
|
|
1846
|
-
payload: { type: 'text', text: finalText },
|
|
1847
|
-
});
|
|
1928
|
+
// Observer forward: outbound (private) — 转发实际发出的明文 payload
|
|
1929
|
+
this.forwardOutbound(targetAid, payload);
|
|
1848
1930
|
}
|
|
1849
1931
|
}
|
|
1850
1932
|
return true;
|
|
@@ -1862,6 +1944,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1862
1944
|
if (!result || !result.message_id) {
|
|
1863
1945
|
logger.warn(`${this.logPrefix()} group.send fallback returned no message_id: ${JSON.stringify(result)}`);
|
|
1864
1946
|
}
|
|
1947
|
+
this.forwardOutbound(channelId, payload);
|
|
1865
1948
|
}
|
|
1866
1949
|
else {
|
|
1867
1950
|
this.trace('OUT', 'message.send.fallback', params);
|
|
@@ -1870,6 +1953,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1870
1953
|
if (!result || !result.message_id) {
|
|
1871
1954
|
logger.warn(`${this.logPrefix()} message.send fallback returned no message_id: ${JSON.stringify(result)}`);
|
|
1872
1955
|
}
|
|
1956
|
+
this.forwardOutbound(targetAid, payload);
|
|
1873
1957
|
}
|
|
1874
1958
|
return true;
|
|
1875
1959
|
}
|
|
@@ -1965,6 +2049,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1965
2049
|
const tid = putRes?.thought_id;
|
|
1966
2050
|
logger.info(`${this.logPrefix()} thought.put ok group=${targetId} task=${taskId} stage=${stage} encrypt=${encrypt} tid=${tid ?? '?'}`);
|
|
1967
2051
|
this.eventBus?.publish?.({ type: 'message:thought-put', agentName: this.config.aid, channelId, taskId, text: thoughtText });
|
|
2052
|
+
this.forwardOutbound(channelId, payload);
|
|
1968
2053
|
if (thoughtText) {
|
|
1969
2054
|
this.aidStatsCollector?.recordOutbound(this.config.aid, channelId, Buffer.byteLength(thoughtText, 'utf-8'), thoughtText, false, encrypt, context?.metadata?.chatmode ?? 'proactive');
|
|
1970
2055
|
this.appendOutboundJsonl(channelId, thoughtText, tid ?? `thought-${Date.now()}`, encrypt, context, true, 'thought', 'daemon');
|
|
@@ -1976,6 +2061,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1976
2061
|
const tid = putRes?.thought_id;
|
|
1977
2062
|
logger.info(`${this.logPrefix()} thought.put ok p2p=${this.peerLabel(targetId)} task=${taskId} stage=${stage} encrypt=${encrypt} tid=${tid ?? '?'}`);
|
|
1978
2063
|
this.eventBus?.publish?.({ type: 'message:thought-put', agentName: this.config.aid, channelId, taskId, text: thoughtText });
|
|
2064
|
+
this.forwardOutbound(channelId, payload);
|
|
1979
2065
|
if (thoughtText) {
|
|
1980
2066
|
this.aidStatsCollector?.recordOutbound(this.config.aid, targetId, Buffer.byteLength(thoughtText, 'utf-8'), thoughtText, false, encrypt, context?.metadata?.chatmode ?? 'proactive');
|
|
1981
2067
|
this.appendOutboundJsonl(channelId, thoughtText, tid ?? `thought-${Date.now()}`, encrypt, context, false, 'thought', 'daemon');
|
|
@@ -2012,12 +2098,14 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
2012
2098
|
const result = await this.callAndTrace('group.send', params);
|
|
2013
2099
|
const mid = result?.message?.message_id ?? result?.message_id ?? null;
|
|
2014
2100
|
logger.info(`${this.logPrefix()} group.send (${payload.type}) ok: group=${channelId} mid=${mid} encrypt=${encrypt}`);
|
|
2101
|
+
this.forwardOutbound(channelId, finalPayload);
|
|
2015
2102
|
return mid;
|
|
2016
2103
|
}
|
|
2017
2104
|
else {
|
|
2018
2105
|
params.to = targetAid;
|
|
2019
2106
|
const result = await this.callAndTrace('message.send', params);
|
|
2020
2107
|
logger.info(`${this.logPrefix()} message.send (${payload.type}) ok: to=${this.peerLabel(targetAid)} mid=${result?.message_id} encrypt=${encrypt}`);
|
|
2108
|
+
this.forwardOutbound(targetAid, finalPayload);
|
|
2021
2109
|
return result?.message_id ?? null;
|
|
2022
2110
|
}
|
|
2023
2111
|
}
|
|
@@ -2187,6 +2275,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
2187
2275
|
}
|
|
2188
2276
|
}
|
|
2189
2277
|
logger.info(`${this.logPrefix()} File sent: ${filename} (${formatSize(stat.size)}) → ${channelId}`);
|
|
2278
|
+
this.forwardOutbound(channelId, filePayload);
|
|
2190
2279
|
return true;
|
|
2191
2280
|
}
|
|
2192
2281
|
catch (e) {
|
|
@@ -2302,6 +2391,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
2302
2391
|
};
|
|
2303
2392
|
const method = isGroup ? 'group.send' : 'message.send';
|
|
2304
2393
|
sendOne(method, statusPayload, 'status');
|
|
2394
|
+
this.forwardOutbound(channelId, statusPayload);
|
|
2305
2395
|
this.aidStatsCollector?.recordOutbound(this.config.aid, channelId, JSON.stringify(statusPayload).length, undefined, true);
|
|
2306
2396
|
// 群聊显示 group id 简称,P2P 显示 peer label;从 context.metadata 读取 chatmode
|
|
2307
2397
|
const targetLabel = this.isGroupId(channelId) ? channelId : this.peerLabel(channelId);
|
|
@@ -2499,6 +2589,32 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
2499
2589
|
const result = await agentmdSync(aid, { store: this.store ?? undefined });
|
|
2500
2590
|
return result.content ?? '';
|
|
2501
2591
|
}
|
|
2592
|
+
/**
|
|
2593
|
+
* 取群显示名(group.get → group.name),进程内缓存。
|
|
2594
|
+
* 走长连接 callAndTrace,失败/未连接返回 undefined —— 绝不抛出阻塞消息处理。
|
|
2595
|
+
*/
|
|
2596
|
+
async getGroupName(groupId) {
|
|
2597
|
+
if (!groupId)
|
|
2598
|
+
return undefined;
|
|
2599
|
+
const cached = this.groupNameCache.get(groupId);
|
|
2600
|
+
if (cached !== undefined)
|
|
2601
|
+
return cached || undefined;
|
|
2602
|
+
if (!this.client)
|
|
2603
|
+
return undefined;
|
|
2604
|
+
try {
|
|
2605
|
+
const result = await this.callAndTrace('group.get', { group_id: groupId });
|
|
2606
|
+
const name = result?.group?.name;
|
|
2607
|
+
if (typeof name === 'string' && name) {
|
|
2608
|
+
this.groupNameCache.set(groupId, name);
|
|
2609
|
+
return name;
|
|
2610
|
+
}
|
|
2611
|
+
this.groupNameCache.set(groupId, ''); // 负缓存:避免反复 RPC(空串视为无名)
|
|
2612
|
+
return undefined;
|
|
2613
|
+
}
|
|
2614
|
+
catch {
|
|
2615
|
+
return undefined; // 不写缓存,下次仍可重试
|
|
2616
|
+
}
|
|
2617
|
+
}
|
|
2502
2618
|
}
|
|
2503
2619
|
// Plugin implementation
|
|
2504
2620
|
export class AUNChannelPlugin {
|
|
@@ -2663,7 +2779,8 @@ export class AUNChannelPlugin {
|
|
|
2663
2779
|
logger.warn(`[AUN] Unhandled payload kind: ${payload.kind}`);
|
|
2664
2780
|
}
|
|
2665
2781
|
}, acknowledge: (messageId) => { channel.acknowledge(messageId); return Promise.resolve(); }, onInteraction: (cb) => { channel.interactionCallback = cb; }, uploadAgentMd: (content) => channel.uploadAgentMd(content),
|
|
2666
|
-
downloadAgentMd: (aid) => channel.downloadAgentMd(aid),
|
|
2782
|
+
downloadAgentMd: (aid) => channel.downloadAgentMd(aid),
|
|
2783
|
+
getGroupName: (groupId) => channel.getGroupName(groupId), _selfAid: () => channel.getStatus().aid,
|
|
2667
2784
|
_selfName: () => channel.getSelfName(),
|
|
2668
2785
|
};
|
|
2669
2786
|
const policy = {
|
|
@@ -2710,24 +2827,7 @@ export class AUNChannelPlugin {
|
|
|
2710
2827
|
onProjectPathRequest: (channelId) => Promise.resolve(config.projects?.defaultPath || process.cwd()),
|
|
2711
2828
|
registerBridge(bridge, channelType) {
|
|
2712
2829
|
bridge.register(adapter.channelName, (handler) => channel.onMessage(async (opts) => {
|
|
2713
|
-
handler(
|
|
2714
|
-
channel: adapter.channelName,
|
|
2715
|
-
channelType,
|
|
2716
|
-
channelId: opts.channelId,
|
|
2717
|
-
selfAID: opts.selfAID,
|
|
2718
|
-
groupId: opts.groupId,
|
|
2719
|
-
content: opts.content,
|
|
2720
|
-
chatType: opts.chatType || 'private',
|
|
2721
|
-
peerId: opts.peerId || '',
|
|
2722
|
-
peerName: opts.peerName,
|
|
2723
|
-
peerType: opts.peerType,
|
|
2724
|
-
messageId: opts.messageId,
|
|
2725
|
-
mentions: opts.mentions,
|
|
2726
|
-
threadId: opts.threadId,
|
|
2727
|
-
replyContext: opts.replyContext,
|
|
2728
|
-
source: opts.source,
|
|
2729
|
-
images: opts.images,
|
|
2730
|
-
});
|
|
2830
|
+
handler(aunOptsToInbound(opts, adapter.channelName, channelType));
|
|
2731
2831
|
}), (channelId, text, replyContext) => channel.sendMessage(channelId, text, replyContext), adapter, channelType);
|
|
2732
2832
|
},
|
|
2733
2833
|
registerHooks(ctx) {
|
package/dist/channels/feishu.js
CHANGED
|
@@ -207,7 +207,7 @@ export class FeishuChannel {
|
|
|
207
207
|
// 清理残留的 mention 占位符(@_user_N 代表机器人)
|
|
208
208
|
content = content.replace(/@_user_\d+/g, '').trim();
|
|
209
209
|
const finalContent = quotedText + content;
|
|
210
|
-
await this.messageHandler({ channelId: msg.chat_id, content: finalContent, images: quotedImages.length > 0 ? quotedImages : undefined, peerId, peerName, messageId: msg.message_id, mentions: mentions.length > 0 ? mentions : undefined, threadId, rootId, chatType });
|
|
210
|
+
await this.messageHandler({ channelId: msg.chat_id, content: finalContent, images: quotedImages.length > 0 ? quotedImages : undefined, peerId, peerName, messageId: msg.message_id, mentions: mentions.length > 0 ? mentions : undefined, mentionAids: mentions.length > 0 ? mentions.map((m) => m.userId) : undefined, threadId, rootId, chatType });
|
|
211
211
|
}
|
|
212
212
|
// 处理图片消息
|
|
213
213
|
else if (msg.message_type === 'image') {
|
|
@@ -1519,12 +1519,12 @@ export class FeishuChannelPlugin {
|
|
|
1519
1519
|
disconnect: () => channel.disconnect(),
|
|
1520
1520
|
onProjectPathRequest: (channelId) => Promise.resolve(config.projects?.defaultPath || process.cwd()),
|
|
1521
1521
|
registerBridge(bridge, channelType) {
|
|
1522
|
-
bridge.register(adapter.channelName, (handler) => channel.onMessage(async ({ channelId: chatId, content, images, peerId, peerName, messageId, mentions, threadId, rootId, chatType, source }) => {
|
|
1522
|
+
bridge.register(adapter.channelName, (handler) => channel.onMessage(async ({ channelId: chatId, content, images, peerId, peerName, messageId, mentions, mentionAids, threadId, rootId, chatType, source }) => {
|
|
1523
1523
|
await handler({
|
|
1524
1524
|
channel: adapter.channelName, channelType, channelId: chatId, content, images,
|
|
1525
1525
|
selfAID: inst.agentName,
|
|
1526
1526
|
chatType: chatType || 'private',
|
|
1527
|
-
peerId: peerId || '', peerName, messageId, mentions, threadId,
|
|
1527
|
+
peerId: peerId || '', peerName, messageId, mentions, mentionAids, threadId,
|
|
1528
1528
|
replyContext: threadId ? { replyToMessageId: rootId ?? threadId, replyInThread: true } : undefined,
|
|
1529
1529
|
source,
|
|
1530
1530
|
});
|
package/dist/cli/agent.js
CHANGED
|
@@ -450,26 +450,32 @@ export async function agentCreateInteractive(opts = {}) {
|
|
|
450
450
|
export async function agentCreateNonInteractive(opts) {
|
|
451
451
|
const p = resolvePaths();
|
|
452
452
|
const { isValidAid, aidCreate } = await import('../aun/aid/index.js');
|
|
453
|
+
opts.onPhase?.('validating', 'begin');
|
|
454
|
+
/** 校验失败:透出 failed 进度并返回原结构(控制流不变)。 */
|
|
455
|
+
const failValidating = (error) => {
|
|
456
|
+
opts.onPhase?.('validating', 'failed', error);
|
|
457
|
+
return { ok: false, error };
|
|
458
|
+
};
|
|
453
459
|
if (!isValidAid(opts.aid)) {
|
|
454
|
-
return
|
|
460
|
+
return failValidating(`Invalid AID "${opts.aid}": must be a valid multi-level domain (e.g. mybot.agentid.pub)`);
|
|
455
461
|
}
|
|
456
462
|
const agentDirPath = path.join(p.agentsDir, opts.aid);
|
|
457
463
|
const configExists = fs.existsSync(path.join(agentDirPath, 'config.json'));
|
|
458
464
|
if (configExists && !opts.force) {
|
|
459
|
-
return
|
|
465
|
+
return failValidating(`Agent "${opts.aid}" already exists: ${agentDirPath}/config.json (use --force to overwrite)`);
|
|
460
466
|
}
|
|
461
467
|
// Baseagent
|
|
462
468
|
const available = detectAvailableBaseagents();
|
|
463
469
|
if (available.length === 0) {
|
|
464
|
-
return
|
|
470
|
+
return failValidating(`No usable baseagent detected. Install claude/gemini CLI or optional dependency @openai/codex-sdk.`);
|
|
465
471
|
}
|
|
466
472
|
let baseagent;
|
|
467
473
|
if (opts.baseagent) {
|
|
468
474
|
if (!BASEAGENT_CANDIDATES.includes(opts.baseagent)) {
|
|
469
|
-
return
|
|
475
|
+
return failValidating(`Invalid baseagent: ${opts.baseagent} (options: ${BASEAGENT_CANDIDATES.join('/')})`);
|
|
470
476
|
}
|
|
471
477
|
if (!available.includes(opts.baseagent)) {
|
|
472
|
-
return
|
|
478
|
+
return failValidating(`${opts.baseagent} is not available in the current environment (available: ${available.join('/')})`);
|
|
473
479
|
}
|
|
474
480
|
baseagent = opts.baseagent;
|
|
475
481
|
}
|
|
@@ -477,20 +483,22 @@ export async function agentCreateNonInteractive(opts) {
|
|
|
477
483
|
baseagent = pickDefaultBaseagent(available);
|
|
478
484
|
}
|
|
479
485
|
if (!path.isAbsolute(opts.project)) {
|
|
480
|
-
return
|
|
486
|
+
return failValidating(`--project must be absolute: ${opts.project}`);
|
|
481
487
|
}
|
|
482
488
|
if (!fs.existsSync(opts.project)) {
|
|
483
489
|
try {
|
|
484
490
|
fs.mkdirSync(opts.project, { recursive: true });
|
|
485
491
|
}
|
|
486
492
|
catch (e) {
|
|
487
|
-
return
|
|
493
|
+
return failValidating(`Failed to create ${opts.project}: ${e?.message || e}`);
|
|
488
494
|
}
|
|
489
495
|
}
|
|
490
496
|
if (opts.owner && !isValidAid(opts.owner)) {
|
|
491
|
-
return
|
|
497
|
+
return failValidating(`Invalid owner: ${opts.owner}`);
|
|
492
498
|
}
|
|
499
|
+
opts.onPhase?.('validating', 'done');
|
|
493
500
|
// Register AID
|
|
501
|
+
opts.onPhase?.('registering_aid', 'begin');
|
|
494
502
|
let aidCreated = false;
|
|
495
503
|
try {
|
|
496
504
|
const result = await aidCreate(opts.aid);
|
|
@@ -499,9 +507,12 @@ export async function agentCreateNonInteractive(opts) {
|
|
|
499
507
|
}
|
|
500
508
|
catch { /* ignore */ }
|
|
501
509
|
aidCreated = !result.alreadyExisted;
|
|
510
|
+
opts.onPhase?.('registering_aid', 'done', aidCreated ? 'created' : 'existed');
|
|
502
511
|
}
|
|
503
512
|
catch (e) {
|
|
504
|
-
|
|
513
|
+
const error = `AID creation failed: ${e?.message || e}`;
|
|
514
|
+
opts.onPhase?.('registering_aid', 'failed', error);
|
|
515
|
+
return { ok: false, error };
|
|
505
516
|
}
|
|
506
517
|
// Force 模式下若 agent 已存在且已 initialized,保留该状态(避免重复发欢迎)
|
|
507
518
|
let preservedInitialized = false;
|
|
@@ -526,9 +537,12 @@ export async function agentCreateNonInteractive(opts) {
|
|
|
526
537
|
chatmode: { ...DEFAULT_CHATMODE },
|
|
527
538
|
dispatch: DEFAULT_DISPATCH,
|
|
528
539
|
};
|
|
540
|
+
opts.onPhase?.('config_saved', 'begin');
|
|
529
541
|
saveAgent(agentConfig);
|
|
530
542
|
ensureAgentDirSkeleton(opts.aid);
|
|
543
|
+
opts.onPhase?.('config_saved', 'done');
|
|
531
544
|
// Generate and upload agent.md
|
|
545
|
+
opts.onPhase?.('uploading_agentmd', 'begin');
|
|
532
546
|
let agentmdUploaded = false;
|
|
533
547
|
try {
|
|
534
548
|
const { buildInitialAgentMd, agentmdPut } = await import('../aun/aid/index.js');
|
|
@@ -550,6 +564,7 @@ export async function agentCreateNonInteractive(opts) {
|
|
|
550
564
|
await new Promise(r => setTimeout(r, RETRY_DELAY_MS));
|
|
551
565
|
await agentmdPut(content, { aid: opts.aid, aunPath });
|
|
552
566
|
agentmdUploaded = true;
|
|
567
|
+
opts.onPhase?.('uploading_agentmd', 'done');
|
|
553
568
|
break;
|
|
554
569
|
}
|
|
555
570
|
catch (e) {
|
|
@@ -559,11 +574,13 @@ export async function agentCreateNonInteractive(opts) {
|
|
|
559
574
|
if (!agentmdUploaded) {
|
|
560
575
|
console.warn(`⚠ agent.md upload failed: ${lastError?.message || lastError}`);
|
|
561
576
|
console.warn(` Retry later with: evolclaw aid agentmd put ${opts.aid}`);
|
|
577
|
+
opts.onPhase?.('uploading_agentmd', 'warn', `upload failed: ${lastError?.message || lastError}`);
|
|
562
578
|
}
|
|
563
579
|
await new Promise(r => setTimeout(r, 0));
|
|
564
580
|
}
|
|
565
581
|
catch (e) {
|
|
566
582
|
console.warn(`⚠ agent.md generation failed: ${e?.message || e}`);
|
|
583
|
+
opts.onPhase?.('uploading_agentmd', 'warn', `generation failed: ${e?.message || e}`);
|
|
567
584
|
}
|
|
568
585
|
// Attempt hot-load via IPC (if daemon is running).
|
|
569
586
|
// Cold-starting a new agent (connecting AUN WebSocket) routinely takes
|
|
@@ -571,16 +588,24 @@ export async function agentCreateNonInteractive(opts) {
|
|
|
571
588
|
// report while the daemon actually finishes bringing the agent online.
|
|
572
589
|
let hotLoaded = false;
|
|
573
590
|
let hotLoadError;
|
|
591
|
+
opts.onPhase?.('hot_loading', 'begin');
|
|
574
592
|
try {
|
|
575
593
|
const ipcResult = await ipcQuery(p.socket, { type: 'evolagent.load', aid: opts.aid }, 30_000);
|
|
576
594
|
if (ipcResult?.ok) {
|
|
577
595
|
hotLoaded = true;
|
|
596
|
+
opts.onPhase?.('hot_loading', 'done');
|
|
578
597
|
}
|
|
579
598
|
else if (ipcResult) {
|
|
580
599
|
hotLoadError = ipcResult.error;
|
|
600
|
+
opts.onPhase?.('hot_loading', 'warn', hotLoadError);
|
|
601
|
+
}
|
|
602
|
+
else {
|
|
603
|
+
opts.onPhase?.('hot_loading', 'warn', 'daemon not running');
|
|
581
604
|
}
|
|
582
605
|
}
|
|
583
|
-
catch {
|
|
606
|
+
catch {
|
|
607
|
+
opts.onPhase?.('hot_loading', 'warn', 'daemon not running'); /* daemon not running */
|
|
608
|
+
}
|
|
584
609
|
return {
|
|
585
610
|
ok: true,
|
|
586
611
|
aid: opts.aid,
|
|
@@ -858,6 +883,9 @@ export async function agentDelete(aid, purge = false) {
|
|
|
858
883
|
}
|
|
859
884
|
else {
|
|
860
885
|
fs.unlinkSync(configPath);
|
|
886
|
+
// 清理构建进度文件(非 purge 删除只移除 config.json,需显式清理 create-status.json)
|
|
887
|
+
const { removeCreateStatus } = await import('../core/message/create-status.js');
|
|
888
|
+
removeCreateStatus(agentDir);
|
|
861
889
|
}
|
|
862
890
|
// Trigger resync so daemon drops the agent
|
|
863
891
|
try {
|