evolclaw 3.0.0 → 3.1.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/README.md +1 -1
- package/bin/ec.js +29 -0
- package/dist/agents/baseagent-normalize.js +19 -0
- package/dist/agents/claude-runner.js +7 -9
- package/dist/agents/codex-runner.js +2 -0
- package/dist/agents/gemini-runner.js +9 -9
- package/dist/agents/kit-renderer.js +281 -0
- package/dist/aun/aid/identity.js +28 -0
- package/dist/aun/aid/index.js +1 -1
- package/dist/aun/aid/lifecycle-log.js +33 -0
- package/dist/aun/msg/group.js +3 -1
- package/dist/aun/msg/p2p.js +4 -1
- package/dist/channels/aun.js +353 -125
- package/dist/channels/dingtalk.js +2 -1
- package/dist/channels/feishu.js +118 -5
- package/dist/channels/qqbot.js +2 -1
- package/dist/channels/wechat.js +3 -1
- package/dist/channels/wecom.js +2 -1
- package/dist/cli/bench.js +1219 -0
- package/dist/cli/index.js +279 -19
- package/dist/cli/link-rules.js +245 -0
- package/dist/cli/net-check.js +640 -0
- package/dist/cli/watch-msg.js +589 -0
- package/dist/config-store.js +37 -5
- package/dist/core/channel-loader.js +23 -10
- package/dist/core/command-handler.js +46 -22
- package/dist/core/evolagent.js +5 -10
- package/dist/core/message/im-renderer.js +50 -44
- package/dist/core/message/items-formatter.js +11 -4
- package/dist/core/message/message-bridge.js +7 -2
- package/dist/core/message/message-log.js +2 -0
- package/dist/core/message/message-processor.js +150 -99
- package/dist/core/message/message-queue.js +10 -3
- package/dist/core/permission.js +95 -3
- package/dist/core/session/session-manager.js +98 -64
- package/dist/core/trigger/scheduler.js +1 -1
- package/dist/data/error-dict.json +118 -0
- package/dist/eck/baseagent-caps.js +18 -0
- package/dist/eck/detect.js +47 -0
- package/dist/eck/init.js +77 -0
- package/dist/eck/rules-loader.js +28 -0
- package/dist/index.js +137 -16
- package/dist/net-check.js +640 -0
- package/dist/paths.js +31 -40
- package/dist/utils/aid-lifecycle-log.js +33 -0
- package/dist/utils/atomic-write.js +10 -0
- package/dist/utils/cross-platform.js +17 -8
- package/dist/utils/error-utils.js +10 -2
- package/dist/utils/instance-registry.js +6 -5
- package/dist/utils/log-writer.js +2 -1
- package/dist/utils/logger.js +10 -0
- package/dist/utils/npm-ops.js +35 -3
- package/dist/utils/process-introspect.js +16 -38
- package/dist/watch-msg.js +26 -11
- package/evolclaw-install-aun.md +14 -2
- package/kits/docs/GUIDE.md +20 -0
- package/kits/docs/INDEX.md +52 -0
- package/kits/docs/aun/CHEATSHEET.md +17 -0
- package/kits/docs/aun/SYNC_PROTOCOL.md +15 -0
- package/kits/docs/channels/feishu.md +27 -0
- package/kits/docs/eck_templates/GUIDE.template.md +22 -0
- package/kits/docs/eck_templates/INDEX.template.md +28 -0
- package/kits/docs/eck_templates/path-registry.template.md +33 -0
- package/kits/docs/eck_templates/runtime.template.md +19 -0
- package/kits/docs/evolclaw/MSG_GROUP.md +30 -0
- package/kits/docs/evolclaw/MSG_PRIVATE.md +25 -0
- package/kits/docs/identity/AID_PROFILE_SPEC.md +27 -0
- package/kits/docs/identity/PATH_OPS.md +16 -0
- package/kits/docs/identity/ROLE_DETAIL.md +20 -0
- package/kits/docs/path-registry.md +43 -0
- package/kits/eck_manifest.json +95 -0
- package/kits/rules/01-overview.md +120 -0
- package/kits/rules/02-navigation.md +75 -0
- package/kits/rules/03-identity.md +34 -0
- package/kits/rules/04-relation.md +49 -0
- package/kits/rules/05-venue.md +45 -0
- package/kits/rules/06-channel.md +43 -0
- package/kits/templates/system-fragments/baseagent.md +2 -0
- package/kits/templates/system-fragments/channel.md +10 -0
- package/kits/templates/system-fragments/identity.md +12 -0
- package/kits/templates/system-fragments/relation.md +9 -0
- package/kits/templates/system-fragments/runtime.md +19 -0
- package/kits/templates/system-fragments/venue.md +5 -0
- package/package.json +7 -5
- package/dist/agents/templates.js +0 -122
- package/dist/data/prompts.md +0 -137
- package/kits/aun/meta.md +0 -25
- package/kits/aun/role.md +0 -25
- package/kits/templates/group.md +0 -20
- package/kits/templates/private.md +0 -9
- package/kits/templates/system-fragments/personal-context.md +0 -3
- package/kits/templates/system-fragments/self-intro.md +0 -5
- package/kits/templates/system-fragments/speaker-intro.md +0 -5
- package/kits/templates/system-fragments/venue-intro.md +0 -5
- /package/kits/{channels → docs/channels}/aun.md +0 -0
- /package/kits/{evolclaw/commands.md → docs/evolclaw/AGENT_CMD.md} +0 -0
- /package/kits/{evolclaw → docs/evolclaw}/self-summary.md +0 -0
- /package/kits/{evolclaw → docs/evolclaw}/tools.md +0 -0
- /package/kits/{evolclaw → docs/identity}/identity-tools.md +0 -0
package/dist/channels/aun.js
CHANGED
|
@@ -9,6 +9,7 @@ import { normalizeChannelInstances, getChannelShowActivities } from '../utils/ch
|
|
|
9
9
|
import { resolvePaths, getPackageRoot } from '../paths.js';
|
|
10
10
|
import { saveToUploads, sanitizeFileName } from '../utils/media-cache.js';
|
|
11
11
|
import { appendAidEvent } from '../utils/instance-registry.js';
|
|
12
|
+
import { appendAidLifecycle } from '../aun/aid/identity.js';
|
|
12
13
|
import { loadAgent, saveAgent } from '../config-store.js';
|
|
13
14
|
import { getProcessStartTime } from '../utils/process-introspect.js';
|
|
14
15
|
import * as outbox from '../aun/outbox.js';
|
|
@@ -63,6 +64,7 @@ export class AUNChannel {
|
|
|
63
64
|
traceWriter = null;
|
|
64
65
|
eventBus = null;
|
|
65
66
|
ownerBoundHandler = null;
|
|
67
|
+
queuedHandler = null;
|
|
66
68
|
pendingEchoMessages = new Map();
|
|
67
69
|
isEchoSending = false;
|
|
68
70
|
trace(dir, event, data) {
|
|
@@ -168,7 +170,7 @@ export class AUNChannel {
|
|
|
168
170
|
const name = cached?.name;
|
|
169
171
|
return name && name !== short ? `${short}(${name})` : short;
|
|
170
172
|
}
|
|
171
|
-
extractTextPayload(payload, channelId) {
|
|
173
|
+
extractTextPayload(payload, channelId, senderAid) {
|
|
172
174
|
if (typeof payload === 'string')
|
|
173
175
|
return payload;
|
|
174
176
|
if (payload && typeof payload === 'object') {
|
|
@@ -176,21 +178,38 @@ export class AUNChannel {
|
|
|
176
178
|
const text = typeof obj.text === 'string' ? obj.text : '';
|
|
177
179
|
// action_card_reply:卡片交互回复,触发 interactionCallback,不分发给 agent
|
|
178
180
|
if (obj.type === 'action_card_reply') {
|
|
179
|
-
const cardMsgId = typeof obj.
|
|
181
|
+
const cardMsgId = typeof obj.ref_message_id === 'string' ? obj.ref_message_id
|
|
182
|
+
: typeof obj.card_message_id === 'string' ? obj.card_message_id : '';
|
|
180
183
|
const cardInfo = cardMsgId ? this.cardMessageIdMap.get(cardMsgId) : undefined;
|
|
181
184
|
if (cardInfo) {
|
|
182
|
-
const actionValue = typeof obj.
|
|
185
|
+
const actionValue = typeof obj.value === 'string' ? obj.value
|
|
186
|
+
: typeof obj.action_value === 'string' ? obj.action_value : text;
|
|
183
187
|
if (cardInfo.isCommandCard) {
|
|
184
188
|
// CommandCard:action_value 是完整 slash 命令,构造伪入站消息
|
|
185
189
|
this.cardMessageIdMap.delete(cardMsgId);
|
|
186
190
|
if (this.messageHandler && actionValue.startsWith('/')) {
|
|
187
191
|
const chatType = channelId ? (this.isGroupId(channelId) ? 'group' : 'private') : 'private';
|
|
192
|
+
// 卡片点击者身份:优先 payload.from / payload.sender_aid / payload.user_id,
|
|
193
|
+
// 再 fallback 到外层 senderAid,最后用 cardInfo 中记录的原始命令发起者
|
|
194
|
+
const cardClickerAid = (typeof obj.from === 'string' && obj.from)
|
|
195
|
+
|| (typeof obj.sender_aid === 'string' && obj.sender_aid)
|
|
196
|
+
|| (typeof obj.user_id === 'string' && obj.user_id)
|
|
197
|
+
|| senderAid
|
|
198
|
+
|| cardInfo.initiatorAid
|
|
199
|
+
|| channelId || '';
|
|
200
|
+
// Initiator 校验:群聊中仅卡片发起者可操作(与飞书行为对齐)
|
|
201
|
+
if (cardInfo.initiatorAid && cardClickerAid
|
|
202
|
+
&& cardClickerAid !== cardInfo.initiatorAid
|
|
203
|
+
&& !this.isGroupId(cardClickerAid)) {
|
|
204
|
+
logger.info(`${this.logPrefix()} CommandCard rejected: clicker=${cardClickerAid} initiator=${cardInfo.initiatorAid} mid=${cardMsgId}`);
|
|
205
|
+
return '';
|
|
206
|
+
}
|
|
188
207
|
this.messageHandler({
|
|
189
208
|
channelId: channelId || '',
|
|
190
209
|
chatType,
|
|
191
210
|
content: actionValue,
|
|
192
|
-
peerId:
|
|
193
|
-
peerName: typeof obj.action_label === 'string' ? obj.action_label : undefined,
|
|
211
|
+
peerId: cardClickerAid,
|
|
212
|
+
peerName: typeof obj.label === 'string' ? obj.label : typeof obj.action_label === 'string' ? obj.action_label : undefined,
|
|
194
213
|
messageId: `card-trigger-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
195
214
|
source: 'card-trigger',
|
|
196
215
|
});
|
|
@@ -205,7 +224,7 @@ export class AUNChannel {
|
|
|
205
224
|
type: 'interaction.response',
|
|
206
225
|
id: cardInfo.requestId,
|
|
207
226
|
action: actionValue,
|
|
208
|
-
values: { text, action_label: obj.action_label, behavior: obj.behavior },
|
|
227
|
+
values: { text, action_label: obj.label ?? obj.action_label, behavior: obj.behavior },
|
|
209
228
|
});
|
|
210
229
|
}
|
|
211
230
|
}
|
|
@@ -216,23 +235,126 @@ export class AUNChannel {
|
|
|
216
235
|
// 始终返回空字符串,阻止消息分发给 agent
|
|
217
236
|
return '';
|
|
218
237
|
}
|
|
219
|
-
// quote
|
|
238
|
+
// quote 类型:拼接被引用内容(支持 text / image / file attachments)
|
|
220
239
|
if (obj.type === 'quote' && obj.quote && typeof obj.quote === 'object') {
|
|
221
240
|
const q = obj.quote;
|
|
222
241
|
const quotedText = typeof q.text === 'string' ? q.text : '';
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
242
|
+
const sender = typeof q.sender_display === 'string' ? q.sender_display : '';
|
|
243
|
+
const prefix = sender ? `${sender}: ` : '';
|
|
244
|
+
// 构建引用内容:文本 + 附件描述
|
|
245
|
+
const quoteParts = [];
|
|
246
|
+
if (quotedText)
|
|
247
|
+
quoteParts.push(quotedText);
|
|
248
|
+
if (Array.isArray(q.attachments)) {
|
|
249
|
+
for (const att of q.attachments) {
|
|
250
|
+
if (att && typeof att === 'object') {
|
|
251
|
+
const ct = typeof att.content_type === 'string' ? att.content_type : '';
|
|
252
|
+
const fn = typeof att.filename === 'string' ? att.filename : '';
|
|
253
|
+
if (ct.startsWith('image/')) {
|
|
254
|
+
quoteParts.push(fn ? `[图片: ${fn}]` : '[图片]');
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
quoteParts.push(fn ? `[文件: ${fn}]` : '[文件]');
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
if (quoteParts.length > 0) {
|
|
263
|
+
const lines = quoteParts.join('\n').split('\n');
|
|
264
|
+
const quoted = lines.map((line, i) => `> ${i === 0 ? prefix : ''}${line}`).join('\n');
|
|
227
265
|
return text ? `${quoted}\n\n${text}` : quoted;
|
|
228
266
|
}
|
|
229
267
|
}
|
|
268
|
+
// merge 类型:合并转发消息,展开子消息为可读文本
|
|
269
|
+
if (obj.type === 'merge') {
|
|
270
|
+
const title = typeof obj.title === 'string' ? obj.title : '合并转发消息';
|
|
271
|
+
const parts = [`以下是转发的合并消息「${title}」:\n---`];
|
|
272
|
+
if (Array.isArray(obj.items)) {
|
|
273
|
+
for (const item of obj.items) {
|
|
274
|
+
if (item && typeof item === 'object') {
|
|
275
|
+
const sender = typeof item.sender_display === 'string' ? item.sender_display : '';
|
|
276
|
+
const itemText = typeof item.text === 'string' ? item.text : '';
|
|
277
|
+
const itemType = typeof item.type === 'string' ? item.type : '';
|
|
278
|
+
// 根据子消息类型构建展示
|
|
279
|
+
const lineParts = [];
|
|
280
|
+
if (itemText)
|
|
281
|
+
lineParts.push(itemText);
|
|
282
|
+
// 子消息附件(image/file)
|
|
283
|
+
if (Array.isArray(item.attachments)) {
|
|
284
|
+
for (const att of item.attachments) {
|
|
285
|
+
if (att && typeof att === 'object') {
|
|
286
|
+
const ct = typeof att.content_type === 'string' ? att.content_type : '';
|
|
287
|
+
const fn = typeof att.filename === 'string' ? att.filename : '';
|
|
288
|
+
if (ct.startsWith('image/') || itemType === 'image') {
|
|
289
|
+
lineParts.push(fn ? `[图片: ${fn}]` : '[图片]');
|
|
290
|
+
}
|
|
291
|
+
else if (ct.startsWith('video/') || itemType === 'video') {
|
|
292
|
+
lineParts.push(fn ? `[视频: ${fn}]` : '[视频]');
|
|
293
|
+
}
|
|
294
|
+
else {
|
|
295
|
+
lineParts.push(fn ? `[文件: ${fn}]` : '[文件]');
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
const content = lineParts.join(' ') || `[${itemType || '未知类型'}]`;
|
|
301
|
+
parts.push(sender ? `${sender}: ${content}` : content);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
if (typeof obj.summary === 'string' && obj.summary) {
|
|
306
|
+
parts.push(`\n[摘要] ${obj.summary}`);
|
|
307
|
+
}
|
|
308
|
+
parts.push('---');
|
|
309
|
+
return parts.join('\n');
|
|
310
|
+
}
|
|
230
311
|
if (typeof obj.text === 'string')
|
|
231
312
|
return text;
|
|
232
313
|
return JSON.stringify(payload);
|
|
233
314
|
}
|
|
234
315
|
return '';
|
|
235
316
|
}
|
|
317
|
+
/** 收集 payload 中所有需要下载的 attachments(顶层 + merge.items + quote.quote),按 url 去重 */
|
|
318
|
+
collectAllAttachments(payload) {
|
|
319
|
+
if (!payload || typeof payload !== 'object')
|
|
320
|
+
return [];
|
|
321
|
+
const obj = payload;
|
|
322
|
+
const result = [];
|
|
323
|
+
const seen = new Set();
|
|
324
|
+
const add = (att) => {
|
|
325
|
+
if (!att || typeof att !== 'object')
|
|
326
|
+
return;
|
|
327
|
+
const key = att.url || att.object_key || '';
|
|
328
|
+
if (key && seen.has(key))
|
|
329
|
+
return;
|
|
330
|
+
if (key)
|
|
331
|
+
seen.add(key);
|
|
332
|
+
result.push(att);
|
|
333
|
+
};
|
|
334
|
+
// 顶层 attachments
|
|
335
|
+
if (Array.isArray(obj.attachments)) {
|
|
336
|
+
for (const att of obj.attachments)
|
|
337
|
+
add(att);
|
|
338
|
+
}
|
|
339
|
+
// merge.items 中的子消息 attachments
|
|
340
|
+
if (obj.type === 'merge' && Array.isArray(obj.items)) {
|
|
341
|
+
for (const item of obj.items) {
|
|
342
|
+
if (item && typeof item === 'object' && Array.isArray(item.attachments)) {
|
|
343
|
+
for (const att of item.attachments)
|
|
344
|
+
add(att);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
// quote.quote 中的 attachments
|
|
349
|
+
if (obj.type === 'quote' && obj.quote && typeof obj.quote === 'object') {
|
|
350
|
+
const q = obj.quote;
|
|
351
|
+
if (Array.isArray(q.attachments)) {
|
|
352
|
+
for (const att of q.attachments)
|
|
353
|
+
add(att);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
return result;
|
|
357
|
+
}
|
|
236
358
|
hasExplicitMention(text, target) {
|
|
237
359
|
const escaped = target.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
238
360
|
return new RegExp(`(^|\\s)@${escaped}(?=$|\\s|[.,!?;:,。!?;:]|[\\u4e00-\\u9fff])`).test(text);
|
|
@@ -282,31 +404,24 @@ export class AUNChannel {
|
|
|
282
404
|
}
|
|
283
405
|
return out;
|
|
284
406
|
}
|
|
285
|
-
buildGroupReplyContext(taskId, senderAid, encrypted) {
|
|
407
|
+
buildGroupReplyContext(taskId, senderAid, encrypted, messageId) {
|
|
286
408
|
const replyContext = { metadata: { encrypted } };
|
|
287
409
|
if (taskId)
|
|
288
410
|
replyContext.threadId = taskId;
|
|
289
411
|
replyContext.peerId = senderAid;
|
|
412
|
+
if (messageId)
|
|
413
|
+
replyContext.replyToMessageId = messageId;
|
|
290
414
|
return replyContext;
|
|
291
415
|
}
|
|
292
|
-
acknowledgeImmediately(messageId,
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
logger.debug(`${this.logPrefix()} Immediate ack failed: ${e}`);
|
|
296
|
-
});
|
|
297
|
-
}
|
|
416
|
+
acknowledgeImmediately(messageId, _seq) {
|
|
417
|
+
// SDK internally manages seq tracking and ack — do not call message.ack RPC directly,
|
|
418
|
+
// as it corrupts the SDK's seqTracker state and breaks V2 e2ee message pull.
|
|
298
419
|
if (messageId)
|
|
299
420
|
this.messageSeqMap.delete(messageId);
|
|
300
421
|
}
|
|
301
|
-
shouldEncrypt(
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
return true;
|
|
305
|
-
if (Date.now() - cached.ts > AUNChannel.E2EE_PROBE_TTL) {
|
|
306
|
-
this.peerE2ee.delete(peerId);
|
|
307
|
-
return true;
|
|
308
|
-
}
|
|
309
|
-
return cached.ok;
|
|
422
|
+
shouldEncrypt(_peerId) {
|
|
423
|
+
// Default to plaintext; only encrypt when session is explicitly marked encrypted
|
|
424
|
+
return false;
|
|
310
425
|
}
|
|
311
426
|
_aid;
|
|
312
427
|
_selfName; // 本地 agent.md 中的 name,首次 connect 时读取
|
|
@@ -318,7 +433,6 @@ export class AUNChannel {
|
|
|
318
433
|
peerE2ee = new Map();
|
|
319
434
|
static E2EE_PROBE_TTL = 10 * 60 * 1000; // 10min
|
|
320
435
|
plaintextRecv = 0;
|
|
321
|
-
sessionModeResolver;
|
|
322
436
|
interactionCallback;
|
|
323
437
|
// action_card message_id → { requestId, isCommandCard }(用于关联 action_card_reply)
|
|
324
438
|
cardMessageIdMap = new Map();
|
|
@@ -579,6 +693,7 @@ export class AUNChannel {
|
|
|
579
693
|
}
|
|
580
694
|
logger.info(`${this.logPrefix()} Connected as ${this._aid}`);
|
|
581
695
|
appendAidEvent({ ts: Date.now(), iso: new Date().toISOString(), event: 'connected', aid: this.config.aid, gateway: this.client._gatewayUrl });
|
|
696
|
+
appendAidLifecycle({ ts: Date.now(), iso: new Date().toISOString(), event: 'connected', aid: this.config.aid, gateway: this.client._gatewayUrl });
|
|
582
697
|
// Send welcome message to owner after first connection
|
|
583
698
|
await this.sendWelcomeMessage();
|
|
584
699
|
}
|
|
@@ -625,7 +740,7 @@ export class AUNChannel {
|
|
|
625
740
|
const ownerAidClean = owner.startsWith('@') ? owner.slice(1) : owner;
|
|
626
741
|
const ownerDisplayName = (ownerInfo.name || ownerAidClean.split('.')[0]).slice(0, 12);
|
|
627
742
|
const currentNameMatch = existingFrontmatter.match(/^name:\s*"?([^"\n]+)/m);
|
|
628
|
-
const currentName = currentNameMatch?.[1]?.trim();
|
|
743
|
+
const currentName = currentNameMatch?.[1]?.trim().replace(/"$/, '');
|
|
629
744
|
const aidLabel = aidName.split('.')[0];
|
|
630
745
|
let agentDisplayName;
|
|
631
746
|
if (currentName && currentName !== aidLabel) {
|
|
@@ -634,13 +749,19 @@ export class AUNChannel {
|
|
|
634
749
|
else {
|
|
635
750
|
agentDisplayName = `${ownerDisplayName}的Evol助手 (${aidLabel})`;
|
|
636
751
|
}
|
|
752
|
+
// Preserve user-provided description (from `agent new --description`), fallback to default
|
|
753
|
+
const currentDescMatch = existingFrontmatter.match(/^description:\s*"?([^"\n]*)/m);
|
|
754
|
+
const currentDesc = currentDescMatch?.[1]?.trim().replace(/"$/, '');
|
|
755
|
+
const agentDescription = currentDesc
|
|
756
|
+
? currentDesc
|
|
757
|
+
: 'EvolClaw AI Agent Gateway - 连接 Claude/Codex 到消息通道';
|
|
637
758
|
// Generate new agent.md (no `initialized` frontmatter — that's now in config.json)
|
|
638
759
|
const newAgentMd = `---
|
|
639
760
|
aid: "${aid}"
|
|
640
761
|
name: "${agentDisplayName}"
|
|
641
762
|
type: "codeagent"
|
|
642
763
|
version: "1.0.0"
|
|
643
|
-
description: "
|
|
764
|
+
description: "${agentDescription}"
|
|
644
765
|
tags:
|
|
645
766
|
- evolclaw
|
|
646
767
|
- ai-agent
|
|
@@ -720,11 +841,11 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
720
841
|
async downloadAttachment(att, channelId) {
|
|
721
842
|
const ownerAid = att.owner_aid || this._aid || '';
|
|
722
843
|
const objectKey = att.object_key;
|
|
723
|
-
const filename = att.filename || objectKey.split('/').pop() || 'unknown';
|
|
724
844
|
if (!objectKey) {
|
|
725
845
|
logger.warn(`${this.logPrefix()} Attachment missing object_key, skipping`);
|
|
726
846
|
return null;
|
|
727
847
|
}
|
|
848
|
+
const filename = att.filename || objectKey.split('/').pop() || 'unknown';
|
|
728
849
|
let downloadUrl;
|
|
729
850
|
try {
|
|
730
851
|
const ticket = await this.callAndTrace('storage.create_download_ticket', {
|
|
@@ -802,10 +923,8 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
802
923
|
if (this._aid && text.includes(`@${this._aid}`)) {
|
|
803
924
|
mentions.push(this._aid);
|
|
804
925
|
}
|
|
805
|
-
// Process attachments
|
|
806
|
-
const rawAttachments =
|
|
807
|
-
? payload.attachments
|
|
808
|
-
: [];
|
|
926
|
+
// Process attachments (顶层 + 嵌套在 merge.items / quote.quote 中的)
|
|
927
|
+
const rawAttachments = this.collectAllAttachments(payload);
|
|
809
928
|
let finalText = text;
|
|
810
929
|
if (rawAttachments.length > 0 && this.client) {
|
|
811
930
|
const fileParts = [];
|
|
@@ -837,6 +956,24 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
837
956
|
// action_card_reply 已在 extractTextPayload 中消费,不分发给 agent
|
|
838
957
|
if (p2pPayloadType === 'action_card_reply')
|
|
839
958
|
return;
|
|
959
|
+
// menu.query:自定义消息快速路径,需要原始 payload JSON 传递给 bridge
|
|
960
|
+
if (p2pPayloadType === 'menu.query') {
|
|
961
|
+
this.acknowledgeImmediately(messageId, seq);
|
|
962
|
+
this.dispatchMessage({
|
|
963
|
+
channelId: chatId, userId: fromAid,
|
|
964
|
+
text: JSON.stringify(payload),
|
|
965
|
+
chatType: 'private', messageId, seq,
|
|
966
|
+
peerName: displayName || undefined,
|
|
967
|
+
peerType: peerInfo.type || undefined,
|
|
968
|
+
});
|
|
969
|
+
return;
|
|
970
|
+
}
|
|
971
|
+
// payload 类型白名单:信号类消息(status / event / thought 等)不进 Agent
|
|
972
|
+
if (p2pPayloadType && !AUNChannel.PROACTIVE_ALLOW_TYPES.has(p2pPayloadType)) {
|
|
973
|
+
this.acknowledgeImmediately(messageId, seq);
|
|
974
|
+
logger.info(`${this.logPrefix()} P2P dropped (type deny): type=${p2pPayloadType} from=${shortAid}(${displayName}) mid=${messageId}`);
|
|
975
|
+
return;
|
|
976
|
+
}
|
|
840
977
|
logger.info(`${this.logPrefix()} P2P dispatched: from=${shortAid}(${displayName}) mid=${messageId} encrypt=${msgEncrypted} text=${finalText.slice(0, 60)}`);
|
|
841
978
|
appendAidEvent({ ts: Date.now(), iso: new Date().toISOString(), event: 'message_in', aid: this.config.aid, from: fromAid, msgId: messageId, kind: 'text', len: finalText.length });
|
|
842
979
|
const isSystemP2P = p2pPayloadType === 'event';
|
|
@@ -865,7 +1002,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
865
1002
|
const groupId = msg.group_id ?? '';
|
|
866
1003
|
const senderAid = msg.sender_aid ?? '';
|
|
867
1004
|
const payload = msg.payload ?? '';
|
|
868
|
-
const text = this.extractTextPayload(payload, groupId);
|
|
1005
|
+
const text = this.extractTextPayload(payload, groupId, senderAid);
|
|
869
1006
|
const taskId = typeof payload === 'object' && payload !== null ? payload.thread_id : undefined;
|
|
870
1007
|
const messageId = msg.message_id ?? '';
|
|
871
1008
|
const seq = msg.seq;
|
|
@@ -884,7 +1021,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
884
1021
|
logger.debug(`${this.logPrefix()} Group dropped: own message (group=${groupId} mid=${messageId})`);
|
|
885
1022
|
return;
|
|
886
1023
|
}
|
|
887
|
-
// 短 echo 快速通道:连通性测试要尽量低延迟,命中后绕过所有 await
|
|
1024
|
+
// 短 echo 快速通道:连通性测试要尽量低延迟,命中后绕过所有 await(后续 mention 过滤)
|
|
888
1025
|
{
|
|
889
1026
|
const firstLineFast = text.split('\n')[0] || '';
|
|
890
1027
|
const hasEvolClawTrace = /\[EvolClaw\.(receive|reply|agent)\]/.test(text);
|
|
@@ -906,25 +1043,37 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
906
1043
|
peerName: displayName,
|
|
907
1044
|
peerType: peerInfo?.type || 'unknown',
|
|
908
1045
|
seq,
|
|
909
|
-
replyContext: this.buildGroupReplyContext(undefined, senderAid, msgEncryptedFast),
|
|
1046
|
+
replyContext: this.buildGroupReplyContext(undefined, senderAid, msgEncryptedFast, messageId),
|
|
910
1047
|
createdAt,
|
|
911
1048
|
});
|
|
912
1049
|
return;
|
|
913
1050
|
}
|
|
914
1051
|
}
|
|
915
|
-
//
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
1052
|
+
// action_card_reply 已在 extractTextPayload 中消费,不分发给 agent
|
|
1053
|
+
{
|
|
1054
|
+
const payloadType = (payload && typeof payload === 'object') ? payload.type ?? '' : '';
|
|
1055
|
+
if (payloadType === 'action_card_reply')
|
|
1056
|
+
return;
|
|
1057
|
+
}
|
|
1058
|
+
// ── payload 类型白名单(所有模式生效) ──
|
|
1059
|
+
// 信号类消息(status / event / thought / task.update 等)不进 Agent
|
|
1060
|
+
{
|
|
1061
|
+
const payloadObj = (payload && typeof payload === 'object') ? payload : null;
|
|
1062
|
+
const payloadType = payloadObj?.type ?? '';
|
|
1063
|
+
// menu.query:自定义消息快速路径
|
|
1064
|
+
if (payloadType === 'menu.query') {
|
|
1065
|
+
this.acknowledgeImmediately(messageId, seq);
|
|
1066
|
+
this.dispatchMessage({
|
|
1067
|
+
channelId: groupId, userId: senderAid,
|
|
1068
|
+
text: JSON.stringify(payload),
|
|
1069
|
+
chatType: 'group', messageId, seq, groupId,
|
|
1070
|
+
});
|
|
1071
|
+
return;
|
|
1072
|
+
}
|
|
1073
|
+
if (payloadType && !AUNChannel.PROACTIVE_ALLOW_TYPES.has(payloadType)) {
|
|
1074
|
+
this.acknowledgeImmediately(messageId, seq);
|
|
1075
|
+
logger.info(`${this.logPrefix()} Group dropped (type deny): type=${payloadType} group=${groupId} sender=${senderAid} mid=${messageId}`);
|
|
1076
|
+
return;
|
|
928
1077
|
}
|
|
929
1078
|
}
|
|
930
1079
|
// 记录入站消息加密状态,透传到出站 ReplyContext
|
|
@@ -958,13 +1107,13 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
958
1107
|
this.pendingEchoMessages.set(messageId, {
|
|
959
1108
|
text: echoText,
|
|
960
1109
|
channelId: groupId,
|
|
961
|
-
context: this.buildGroupReplyContext(undefined, senderAid, msgEncrypted),
|
|
1110
|
+
context: this.buildGroupReplyContext(undefined, senderAid, msgEncrypted, messageId),
|
|
962
1111
|
receiveTs: Date.now(),
|
|
963
1112
|
});
|
|
964
1113
|
// 继续走正常 Agent 流程(下面的代码会 dispatch)
|
|
965
1114
|
}
|
|
966
1115
|
else if (/echo/i.test(firstLineGroup) && hasEvolClawTraceGroup) {
|
|
967
|
-
//
|
|
1116
|
+
// 回声炸弹:已被任何 EvolClaw 节点 trace 过的 echo,直接丢弃
|
|
968
1117
|
this.acknowledgeImmediately(messageId, seq);
|
|
969
1118
|
logger.info(`${this.logPrefix()} Group dropped: echo bomb (already-traced group=${groupId} sender=${senderAid} mid=${messageId})`);
|
|
970
1119
|
return;
|
|
@@ -978,10 +1127,8 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
978
1127
|
}
|
|
979
1128
|
}
|
|
980
1129
|
const strippedText = this.stripSelfMentionIfOnly(text, this._aid);
|
|
981
|
-
// Detect attachments before the empty-text guard
|
|
982
|
-
const rawAttachments =
|
|
983
|
-
? payload.attachments
|
|
984
|
-
: [];
|
|
1130
|
+
// Detect attachments before the empty-text guard (顶层 + 嵌套)
|
|
1131
|
+
const rawAttachments = this.collectAllAttachments(payload);
|
|
985
1132
|
const hasAttachments = rawAttachments.length > 0;
|
|
986
1133
|
// Allow through if there's text OR attachments; both-empty messages are silently dropped
|
|
987
1134
|
if (!strippedText && !hasAttachments) {
|
|
@@ -1045,7 +1192,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1045
1192
|
seq,
|
|
1046
1193
|
taskId,
|
|
1047
1194
|
mentions,
|
|
1048
|
-
replyContext: this.buildGroupReplyContext(taskId, senderAid, msgEncrypted),
|
|
1195
|
+
replyContext: this.buildGroupReplyContext(taskId, senderAid, msgEncrypted, messageId),
|
|
1049
1196
|
});
|
|
1050
1197
|
}
|
|
1051
1198
|
dispatchMessage(event) {
|
|
@@ -1068,7 +1215,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1068
1215
|
this.handleEcho(event);
|
|
1069
1216
|
return;
|
|
1070
1217
|
}
|
|
1071
|
-
//
|
|
1218
|
+
// 回声炸弹:已被任何 EvolClaw 节点 trace 过的 echo,直接丢弃(防止多 agent 间无限回声)
|
|
1072
1219
|
if (/echo/i.test(firstLine) && hasEvolClawTracePrivate) {
|
|
1073
1220
|
logger.info(`${this.logPrefix()} Dropped: echo bomb (already-traced mid=${event.messageId} chat=${event.chatType})`);
|
|
1074
1221
|
return;
|
|
@@ -1258,16 +1405,46 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1258
1405
|
const d = data;
|
|
1259
1406
|
const reason = d.reason ?? '';
|
|
1260
1407
|
const error = d.error ?? 'unknown';
|
|
1408
|
+
const code = d.code ?? d.detail?.code ?? 0;
|
|
1409
|
+
const detail = (d.detail && typeof d.detail === 'object') ? d.detail : {};
|
|
1261
1410
|
if (this.intentionalDisconnect)
|
|
1262
1411
|
return;
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
this.
|
|
1412
|
+
if (this.isKickReason(reason) || code >= 4001) {
|
|
1413
|
+
// @ts-ignore — methods defined below in same class
|
|
1414
|
+
const kickDetail = this.buildKickDetail(code, reason, detail);
|
|
1415
|
+
// @ts-ignore — methods defined below in same class
|
|
1416
|
+
const action = this.classifyKickAction(code);
|
|
1417
|
+
appendAidEvent({
|
|
1418
|
+
ts: Date.now(), iso: new Date().toISOString(),
|
|
1419
|
+
event: 'kicked', aid: this.config.aid,
|
|
1420
|
+
code, reason, action,
|
|
1421
|
+
evictedBy: kickDetail.evictedBy,
|
|
1422
|
+
quotaKind: kickDetail.quotaKind,
|
|
1423
|
+
});
|
|
1424
|
+
appendAidLifecycle({
|
|
1425
|
+
ts: Date.now(), iso: new Date().toISOString(),
|
|
1426
|
+
event: 'kicked', aid: this.config.aid,
|
|
1427
|
+
code, reason, action,
|
|
1428
|
+
evictedBy: kickDetail.evictedBy,
|
|
1429
|
+
newExtra: kickDetail.newExtra,
|
|
1430
|
+
quotaKind: kickDetail.quotaKind,
|
|
1431
|
+
});
|
|
1432
|
+
if (action === 'no_retry') {
|
|
1433
|
+
logger.error(`${this.logPrefix()} Kicked (code=${code}): ${reason} — will NOT retry`);
|
|
1434
|
+
this.setAidStatus('kicked_no_retry', { lastError: `kicked(${code}): ${reason}`.slice(0, 80), kickDetail });
|
|
1435
|
+
}
|
|
1436
|
+
else if (action === 'retry_once') {
|
|
1437
|
+
logger.warn(`${this.logPrefix()} Kicked (code=${code}): ${reason} — retrying once after ${AUNChannel.FALLBACK_DELAY_MS / 1000}s`);
|
|
1438
|
+
this.setAidStatus('kicked', { lastError: `kicked(${code}): ${reason}`.slice(0, 80), kickDetail });
|
|
1439
|
+
this.takeoverReconnect(AUNChannel.FALLBACK_DELAY_MS, 'kicked');
|
|
1440
|
+
}
|
|
1441
|
+
else {
|
|
1442
|
+
logger.warn(`${this.logPrefix()} Kicked (code=${code}): ${reason} — retrying after ${AUNChannel.TAKEOVER_DELAY_MS / 1000}s`);
|
|
1443
|
+
this.setAidStatus('kicked', { lastError: `kicked(${code}): ${reason}`.slice(0, 80), kickDetail });
|
|
1444
|
+
this.takeoverReconnect(AUNChannel.TAKEOVER_DELAY_MS, 'kicked');
|
|
1445
|
+
}
|
|
1268
1446
|
}
|
|
1269
1447
|
else {
|
|
1270
|
-
// 其他 terminal failure(含 max_attempts_exhausted 兜底)→ 1min 后再试
|
|
1271
1448
|
logger.error(`${this.logPrefix()} Terminal failure: ${error}${reason ? ` (${reason})` : ''}, retrying in ${AUNChannel.FALLBACK_DELAY_MS / 1000}s`);
|
|
1272
1449
|
this.setAidStatus('failed', { lastError: `${error}`.slice(0, 80) });
|
|
1273
1450
|
this.takeoverReconnect(AUNChannel.FALLBACK_DELAY_MS, 'terminal');
|
|
@@ -1281,11 +1458,58 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1281
1458
|
const r = reason.toLowerCase();
|
|
1282
1459
|
if (r.includes('kicked') || r.includes('kick'))
|
|
1283
1460
|
return true;
|
|
1284
|
-
|
|
1285
|
-
if (/close code 40(0[13]|0[89]|1[01])/.test(r))
|
|
1461
|
+
if (/close code 40\d{2}/.test(r))
|
|
1286
1462
|
return true;
|
|
1287
1463
|
return false;
|
|
1288
1464
|
}
|
|
1465
|
+
/**
|
|
1466
|
+
* 根据 close code 决定重试策略:
|
|
1467
|
+
* - 'no_retry': 不重试(被挤掉、AID 无效、ACL 拒绝、长连接已存在、配额超限)
|
|
1468
|
+
* - 'retry_once': 重试一次(auth 失败可能 token 刚过期、nonce 无效)
|
|
1469
|
+
* - 'retry_delay': 延迟重试(短连接容量超限、空闲超时)
|
|
1470
|
+
*/
|
|
1471
|
+
classifyKickAction(code) {
|
|
1472
|
+
switch (code) {
|
|
1473
|
+
case 4003: // AID 无效
|
|
1474
|
+
case 4009: // 服务端主动踢
|
|
1475
|
+
case 4011: // ACL 拒绝
|
|
1476
|
+
case 4012: // 长连接已存在(自己另一个实例在线)
|
|
1477
|
+
case 4015: // 被新连接挤掉
|
|
1478
|
+
return 'no_retry';
|
|
1479
|
+
case 4001: // auth 失败(token 可能刚过期)
|
|
1480
|
+
case 4010: // nonce 无效
|
|
1481
|
+
return 'retry_once';
|
|
1482
|
+
case 4008: // auth 超时
|
|
1483
|
+
case 4013: // 短连接容量超限
|
|
1484
|
+
case 4014: // 短连接空闲超时
|
|
1485
|
+
return 'retry_delay';
|
|
1486
|
+
default:
|
|
1487
|
+
return 'retry_delay';
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
buildKickDetail(code, reason, detail) {
|
|
1491
|
+
const evictedByRaw = detail.evicted_by || detail.new_extra_info;
|
|
1492
|
+
let evictedBy;
|
|
1493
|
+
if (evictedByRaw && typeof evictedByRaw === 'object') {
|
|
1494
|
+
evictedBy = {
|
|
1495
|
+
aid: evictedByRaw.aid,
|
|
1496
|
+
deviceId: evictedByRaw.device_id,
|
|
1497
|
+
slotId: evictedByRaw.slot_id,
|
|
1498
|
+
app: evictedByRaw.app,
|
|
1499
|
+
hostname: evictedByRaw.hostname,
|
|
1500
|
+
};
|
|
1501
|
+
}
|
|
1502
|
+
return {
|
|
1503
|
+
code,
|
|
1504
|
+
reason,
|
|
1505
|
+
ts: Date.now(),
|
|
1506
|
+
evictedBy,
|
|
1507
|
+
quotaKind: detail.quota_kind,
|
|
1508
|
+
limit: detail.limit,
|
|
1509
|
+
selfExtra: detail.self_extra_info,
|
|
1510
|
+
newExtra: detail.new_extra_info,
|
|
1511
|
+
};
|
|
1512
|
+
}
|
|
1289
1513
|
/**
|
|
1290
1514
|
* TS 层接管重连:force close 当前 SDK client,安排 delayMs 后重新 initClient。
|
|
1291
1515
|
* 用于 flap / kicked / terminal_failed 三类场景,统一退避路径。
|
|
@@ -1347,7 +1571,11 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1347
1571
|
if (this.eventBus && this.ownerBoundHandler && typeof this.eventBus.unsubscribe === 'function') {
|
|
1348
1572
|
this.eventBus.unsubscribe('channel:owner-bound', this.ownerBoundHandler);
|
|
1349
1573
|
}
|
|
1574
|
+
if (this.eventBus && this.queuedHandler && typeof this.eventBus.unsubscribe === 'function') {
|
|
1575
|
+
this.eventBus.unsubscribe('task:queued', this.queuedHandler);
|
|
1576
|
+
}
|
|
1350
1577
|
this.ownerBoundHandler = null;
|
|
1578
|
+
this.queuedHandler = null;
|
|
1351
1579
|
this.eventBus = bus;
|
|
1352
1580
|
if (bus && typeof bus.subscribe === 'function') {
|
|
1353
1581
|
const handler = (event) => {
|
|
@@ -1365,6 +1593,13 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1365
1593
|
};
|
|
1366
1594
|
bus.subscribe('channel:owner-bound', handler);
|
|
1367
1595
|
this.ownerBoundHandler = handler;
|
|
1596
|
+
const queuedHandler = (event) => {
|
|
1597
|
+
if (event.channel !== this.config.channelName)
|
|
1598
|
+
return;
|
|
1599
|
+
this.sendProcessingStatus(event.channelId, 'queued', '', '', event.replyContext);
|
|
1600
|
+
};
|
|
1601
|
+
bus.subscribe('task:queued', queuedHandler);
|
|
1602
|
+
this.queuedHandler = queuedHandler;
|
|
1368
1603
|
}
|
|
1369
1604
|
}
|
|
1370
1605
|
onProjectPathRequest(provider) {
|
|
@@ -1373,9 +1608,6 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1373
1608
|
onMessage(handler) {
|
|
1374
1609
|
this.messageHandler = handler;
|
|
1375
1610
|
}
|
|
1376
|
-
setSessionModeResolver(resolver) {
|
|
1377
|
-
this.sessionModeResolver = resolver;
|
|
1378
|
-
}
|
|
1379
1611
|
setDispatchModeResolver(resolver) {
|
|
1380
1612
|
this.dispatchModeResolver = resolver;
|
|
1381
1613
|
}
|
|
@@ -1480,6 +1712,8 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1480
1712
|
payload.task_id = context.metadata.taskId;
|
|
1481
1713
|
if (context?.metadata?.chatmode)
|
|
1482
1714
|
payload.chatmode = context.metadata.chatmode;
|
|
1715
|
+
// 诊断日志:记录 payload 构造结果(含 task_id / thread_id / chatmode)
|
|
1716
|
+
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}`);
|
|
1483
1717
|
const isGroup = this.isGroupId(channelId);
|
|
1484
1718
|
const targetAid = channelId;
|
|
1485
1719
|
const encryptTarget = isGroup ? channelId : targetAid;
|
|
@@ -1491,7 +1725,8 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1491
1725
|
if (isGroup) {
|
|
1492
1726
|
params.group_id = channelId;
|
|
1493
1727
|
const result = await this.callAndTrace('group.send', params);
|
|
1494
|
-
|
|
1728
|
+
const mid = result?.message?.message_id ?? result?.message_id ?? null;
|
|
1729
|
+
if (!mid) {
|
|
1495
1730
|
const dispatchStatus = result?.message_dispatch?.status;
|
|
1496
1731
|
if (dispatchStatus === 'debounced' || dispatchStatus === 'dispatched') {
|
|
1497
1732
|
logger.info(`${this.logPrefix()} group.send ok (${dispatchStatus}): group=${channelId} encrypt=${encrypt} text=${finalText.slice(0, 60)}`);
|
|
@@ -1501,8 +1736,8 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1501
1736
|
}
|
|
1502
1737
|
}
|
|
1503
1738
|
else {
|
|
1504
|
-
logger.info(`${this.logPrefix()} group.send ok: group=${channelId} mid=${
|
|
1505
|
-
appendAidEvent({ ts: Date.now(), iso: new Date().toISOString(), event: 'message_out', aid: this.config.aid, to: channelId, msgId:
|
|
1739
|
+
logger.info(`${this.logPrefix()} group.send ok: group=${channelId} mid=${mid} encrypt=${encrypt} text=${finalText.slice(0, 60)}`);
|
|
1740
|
+
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 });
|
|
1506
1741
|
this.aidStatsCollector?.recordOutbound(this.config.aid, channelId, Buffer.byteLength(finalText, 'utf-8'), finalText);
|
|
1507
1742
|
}
|
|
1508
1743
|
}
|
|
@@ -1529,7 +1764,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1529
1764
|
if (isGroup) {
|
|
1530
1765
|
this.trace('OUT', 'group.send.fallback', params);
|
|
1531
1766
|
const result = await this.client.call('group.send', params);
|
|
1532
|
-
this.trace('OUT', 'group.send.fallback.ok', { message_id: result?.message_id });
|
|
1767
|
+
this.trace('OUT', 'group.send.fallback.ok', { message_id: result?.message?.message_id ?? result?.message_id });
|
|
1533
1768
|
if (!result || !result.message_id) {
|
|
1534
1769
|
logger.warn(`${this.logPrefix()} group.send fallback returned no message_id: ${JSON.stringify(result)}`);
|
|
1535
1770
|
}
|
|
@@ -1585,13 +1820,15 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1585
1820
|
const stage = payload?.stage ?? `items=${itemCount}`;
|
|
1586
1821
|
if (this.isGroupId(channelId)) {
|
|
1587
1822
|
params.group_id = targetId;
|
|
1588
|
-
await this.callAndTrace('group.thought.put', params);
|
|
1589
|
-
|
|
1823
|
+
const putRes = await this.callAndTrace('group.thought.put', params);
|
|
1824
|
+
const tid = putRes?.thought_id;
|
|
1825
|
+
logger.info(`${this.logPrefix()} thought.put ok group=${targetId} task=${taskId} stage=${stage} encrypt=${encrypt} tid=${tid ?? '?'}`);
|
|
1590
1826
|
}
|
|
1591
1827
|
else {
|
|
1592
1828
|
params.to = targetId;
|
|
1593
|
-
await this.callAndTrace('message.thought.put', params);
|
|
1594
|
-
|
|
1829
|
+
const putRes = await this.callAndTrace('message.thought.put', params);
|
|
1830
|
+
const tid = putRes?.thought_id;
|
|
1831
|
+
logger.info(`${this.logPrefix()} thought.put ok p2p=${this.peerLabel(targetId)} task=${taskId} stage=${stage} encrypt=${encrypt} tid=${tid ?? '?'}`);
|
|
1595
1832
|
}
|
|
1596
1833
|
}
|
|
1597
1834
|
catch (e) {
|
|
@@ -1622,8 +1859,9 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1622
1859
|
if (isGroup) {
|
|
1623
1860
|
params.group_id = channelId;
|
|
1624
1861
|
const result = await this.callAndTrace('group.send', params);
|
|
1625
|
-
|
|
1626
|
-
|
|
1862
|
+
const mid = result?.message?.message_id ?? result?.message_id ?? null;
|
|
1863
|
+
logger.info(`${this.logPrefix()} group.send (${payload.type}) ok: group=${channelId} mid=${mid} encrypt=${encrypt}`);
|
|
1864
|
+
return mid;
|
|
1627
1865
|
}
|
|
1628
1866
|
else {
|
|
1629
1867
|
params.to = targetAid;
|
|
@@ -1750,8 +1988,9 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1750
1988
|
params.group_id = channelId;
|
|
1751
1989
|
this.trace('OUT', 'group.send.file', params);
|
|
1752
1990
|
const result = await this.client.call('group.send', params);
|
|
1753
|
-
|
|
1754
|
-
|
|
1991
|
+
const fileMid = result?.message?.message_id ?? result?.message_id;
|
|
1992
|
+
this.trace('OUT', 'group.send.file.ok', { message_id: fileMid });
|
|
1993
|
+
if (!fileMid) {
|
|
1755
1994
|
logger.warn(`${this.logPrefix()} group.send.file returned no message_id: ${JSON.stringify(result)}`);
|
|
1756
1995
|
}
|
|
1757
1996
|
}
|
|
@@ -1777,8 +2016,9 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1777
2016
|
if (isGroup) {
|
|
1778
2017
|
this.trace('OUT', 'group.send.file.fallback', params);
|
|
1779
2018
|
const result = await this.client.call('group.send', params);
|
|
1780
|
-
|
|
1781
|
-
|
|
2019
|
+
const fbMid = result?.message?.message_id ?? result?.message_id;
|
|
2020
|
+
this.trace('OUT', 'group.send.file.fallback.ok', { message_id: fbMid });
|
|
2021
|
+
if (!fbMid) {
|
|
1782
2022
|
logger.warn(`${this.logPrefix()} group.send.file fallback returned no message_id: ${JSON.stringify(result)}`);
|
|
1783
2023
|
}
|
|
1784
2024
|
}
|
|
@@ -1850,30 +2090,14 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1850
2090
|
this.sentCount.delete(channelId); // 新任务开始,重置计数
|
|
1851
2091
|
if (!this.client || !this.connected)
|
|
1852
2092
|
return;
|
|
1853
|
-
// 旧路 payload(type='event', event='task.*')—— 前后兼容保留,未来废弃
|
|
1854
|
-
const eventMap = {
|
|
1855
|
-
start: 'task.started',
|
|
1856
|
-
done: 'task.completed',
|
|
1857
|
-
interrupted: 'task.interrupted',
|
|
1858
|
-
error: 'task.error',
|
|
1859
|
-
timeout: 'task.timeout',
|
|
1860
|
-
};
|
|
1861
2093
|
const severity = status === 'error' || status === 'timeout' ? 'error' : 'info';
|
|
1862
|
-
const eventPayload = {
|
|
1863
|
-
type: 'event',
|
|
1864
|
-
event: eventMap[status] ?? `task.${status}`,
|
|
1865
|
-
data: { task_id: taskId, session_id: sessionId },
|
|
1866
|
-
severity,
|
|
1867
|
-
};
|
|
1868
|
-
if (context?.threadId)
|
|
1869
|
-
eventPayload.thread_id = context.threadId;
|
|
1870
|
-
// 新路 payload(type='status')—— 结构化任务状态,下游直接读字段不用解析 event 字符串
|
|
1871
2094
|
const stateMap = {
|
|
1872
2095
|
start: 'started',
|
|
1873
2096
|
done: 'completed',
|
|
1874
2097
|
interrupted: 'interrupted',
|
|
1875
2098
|
error: 'error',
|
|
1876
2099
|
timeout: 'timeout',
|
|
2100
|
+
queued: 'queued',
|
|
1877
2101
|
};
|
|
1878
2102
|
const statusPayload = {
|
|
1879
2103
|
type: 'status',
|
|
@@ -1884,12 +2108,14 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1884
2108
|
};
|
|
1885
2109
|
if (context?.threadId)
|
|
1886
2110
|
statusPayload.thread_id = context.threadId;
|
|
2111
|
+
if (context?.peerId)
|
|
2112
|
+
statusPayload.initiator = context.peerId;
|
|
2113
|
+
if (context?.replyToMessageId)
|
|
2114
|
+
statusPayload.ref_message_id = context.replyToMessageId;
|
|
1887
2115
|
const isGroup = this.isGroupId(channelId);
|
|
1888
2116
|
// 私聊 channelId = 对端 AID(不含 device_id)
|
|
1889
2117
|
const statusTargetAid = channelId;
|
|
1890
2118
|
const encryptTarget = isGroup ? channelId : statusTargetAid;
|
|
1891
|
-
// 计算 encrypt 标志(每次调用读最新 peerE2ee 状态,
|
|
1892
|
-
// 这样第二条 send 能受益于第一条触发的 peerE2ee 标记)
|
|
1893
2119
|
const computeEncrypt = () => context?.metadata?.encrypted != null
|
|
1894
2120
|
? !!(context.metadata.encrypted)
|
|
1895
2121
|
: this.shouldEncrypt(encryptTarget);
|
|
@@ -1922,17 +2148,14 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1922
2148
|
});
|
|
1923
2149
|
};
|
|
1924
2150
|
const method = isGroup ? 'group.send' : 'message.send';
|
|
1925
|
-
|
|
1926
|
-
// 保证两路 payload 到达顺序(event 在前,status 在后)+ 第二条能用最新 encrypt 状态
|
|
1927
|
-
sendOne(method, eventPayload, 'event')
|
|
1928
|
-
.then(() => sendOne(method, statusPayload, 'status'));
|
|
1929
|
-
// 统计为系统消息(按两条独立消息分别记账)
|
|
1930
|
-
this.aidStatsCollector?.recordOutbound(this.config.aid, channelId, JSON.stringify(eventPayload).length, undefined, true);
|
|
2151
|
+
sendOne(method, statusPayload, 'status');
|
|
1931
2152
|
this.aidStatsCollector?.recordOutbound(this.config.aid, channelId, JSON.stringify(statusPayload).length, undefined, true);
|
|
1932
2153
|
// 群聊显示 group id 简称,P2P 显示 peer label;从 context.metadata 读取 chatmode
|
|
1933
2154
|
const targetLabel = this.isGroupId(channelId) ? channelId : this.peerLabel(channelId);
|
|
1934
2155
|
const chatmode = context?.metadata?.chatmode ?? '?';
|
|
1935
|
-
|
|
2156
|
+
const initiator = statusPayload.initiator ?? '';
|
|
2157
|
+
const refMsgId = statusPayload.ref_message_id ?? '';
|
|
2158
|
+
logger.info(`${this.logPrefix()} task.${status} task=${taskId} session=${sessionId} chatmode=${chatmode} target=${targetLabel} initiator=${initiator} ref_msg=${refMsgId}`);
|
|
1936
2159
|
}
|
|
1937
2160
|
sendCustomPayload(channelId, payload) {
|
|
1938
2161
|
if (!this.client || !this.connected)
|
|
@@ -1980,6 +2203,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1980
2203
|
}
|
|
1981
2204
|
this.connected = false;
|
|
1982
2205
|
appendAidEvent({ ts: Date.now(), iso: new Date().toISOString(), event: 'disconnected', aid: this.config.aid, reason: 'intentional' });
|
|
2206
|
+
appendAidLifecycle({ ts: Date.now(), iso: new Date().toISOString(), event: 'disconnected', aid: this.config.aid, reason: 'intentional' });
|
|
1983
2207
|
this.setAidStatus('disabled');
|
|
1984
2208
|
if (this.traceWriter) {
|
|
1985
2209
|
this.traceWriter.close();
|
|
@@ -2159,7 +2383,7 @@ export class AUNChannelPlugin {
|
|
|
2159
2383
|
case 'result.error': {
|
|
2160
2384
|
const sendCtx = { ...(ctx ?? {}) };
|
|
2161
2385
|
if (payload.kind === 'result.text' && payload.isFinal)
|
|
2162
|
-
sendCtx.title = '
|
|
2386
|
+
sendCtx.title = '✅ 最终回复:';
|
|
2163
2387
|
await channel.sendMessage(channelId, payload.text, sendCtx);
|
|
2164
2388
|
return;
|
|
2165
2389
|
}
|
|
@@ -2186,16 +2410,23 @@ export class AUNChannelPlugin {
|
|
|
2186
2410
|
};
|
|
2187
2411
|
if (ctx?.threadId)
|
|
2188
2412
|
aunPayload.thread_id = ctx.threadId;
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2413
|
+
if (envelope.chatmode === 'proactive') {
|
|
2414
|
+
await channel.sendThought(channelId, envelope.taskId, aunPayload, ctx);
|
|
2415
|
+
}
|
|
2416
|
+
else {
|
|
2417
|
+
await Promise.all([
|
|
2418
|
+
channel.sendThought(channelId, envelope.taskId, aunPayload, ctx),
|
|
2419
|
+
channel.sendStructured(channelId, aunPayload, ctx),
|
|
2420
|
+
]);
|
|
2421
|
+
}
|
|
2194
2422
|
return;
|
|
2195
2423
|
}
|
|
2196
2424
|
case 'status.started':
|
|
2197
2425
|
channel.sendProcessingStatus(channelId, 'start', envelope.taskId, envelope.taskId, ctx);
|
|
2198
2426
|
return;
|
|
2427
|
+
case 'status.queued':
|
|
2428
|
+
channel.sendProcessingStatus(channelId, 'queued', envelope.taskId, envelope.taskId, ctx);
|
|
2429
|
+
return;
|
|
2199
2430
|
case 'status.completed':
|
|
2200
2431
|
channel.sendProcessingStatus(channelId, 'done', envelope.taskId, envelope.taskId, ctx);
|
|
2201
2432
|
return;
|
|
@@ -2215,7 +2446,6 @@ export class AUNChannelPlugin {
|
|
|
2215
2446
|
const aunCard = {
|
|
2216
2447
|
type: 'action_card',
|
|
2217
2448
|
title: action.title,
|
|
2218
|
-
description: action.body,
|
|
2219
2449
|
actions: action.buttons.map(btn => ({
|
|
2220
2450
|
label: btn.label,
|
|
2221
2451
|
value: btn.key,
|
|
@@ -2223,6 +2453,8 @@ export class AUNChannelPlugin {
|
|
|
2223
2453
|
behavior: 'reply',
|
|
2224
2454
|
})),
|
|
2225
2455
|
};
|
|
2456
|
+
if (action.body)
|
|
2457
|
+
aunCard.description = action.body;
|
|
2226
2458
|
if (ctx?.threadId)
|
|
2227
2459
|
aunCard.thread_id = ctx.threadId;
|
|
2228
2460
|
const msgId = await channel.sendStructured(channelId, aunCard, ctx);
|
|
@@ -2236,7 +2468,6 @@ export class AUNChannelPlugin {
|
|
|
2236
2468
|
const aunCard = {
|
|
2237
2469
|
type: 'action_card',
|
|
2238
2470
|
title: card.title,
|
|
2239
|
-
description: card.body,
|
|
2240
2471
|
actions: card.buttons.map(btn => ({
|
|
2241
2472
|
label: btn.label,
|
|
2242
2473
|
value: btn.command,
|
|
@@ -2244,11 +2475,13 @@ export class AUNChannelPlugin {
|
|
|
2244
2475
|
behavior: 'reply',
|
|
2245
2476
|
})),
|
|
2246
2477
|
};
|
|
2478
|
+
if (card.body)
|
|
2479
|
+
aunCard.description = card.body;
|
|
2247
2480
|
if (ctx?.threadId)
|
|
2248
2481
|
aunCard.thread_id = ctx.threadId;
|
|
2249
2482
|
const msgId = await channel.sendStructured(channelId, aunCard, ctx);
|
|
2250
2483
|
if (msgId) {
|
|
2251
|
-
channel.cardMessageIdMap.set(msgId, { requestId: req.id, isCommandCard: true });
|
|
2484
|
+
channel.cardMessageIdMap.set(msgId, { requestId: req.id, isCommandCard: true, initiatorAid: req.initiatorId });
|
|
2252
2485
|
setTimeout(() => channel.cardMessageIdMap.delete(msgId), 20 * 60 * 1000);
|
|
2253
2486
|
}
|
|
2254
2487
|
}
|
|
@@ -2323,6 +2556,7 @@ export class AUNChannelPlugin {
|
|
|
2323
2556
|
chatType: opts.chatType || 'private',
|
|
2324
2557
|
peerId: opts.peerId || '',
|
|
2325
2558
|
peerName: opts.peerName,
|
|
2559
|
+
peerType: opts.peerType,
|
|
2326
2560
|
messageId: opts.messageId,
|
|
2327
2561
|
mentions: opts.mentions,
|
|
2328
2562
|
threadId: opts.threadId,
|
|
@@ -2345,12 +2579,6 @@ export class AUNChannelPlugin {
|
|
|
2345
2579
|
});
|
|
2346
2580
|
});
|
|
2347
2581
|
}
|
|
2348
|
-
if (typeof channel.setSessionModeResolver === 'function') {
|
|
2349
|
-
channel.setSessionModeResolver(async (channelId) => {
|
|
2350
|
-
const session = await ctx.sessionManager.getActiveSession(adapter.channelName, channelId);
|
|
2351
|
-
return session?.sessionMode;
|
|
2352
|
-
});
|
|
2353
|
-
}
|
|
2354
2582
|
if (typeof channel.setDispatchModeResolver === 'function') {
|
|
2355
2583
|
channel.setDispatchModeResolver(async (channelId) => {
|
|
2356
2584
|
const session = await ctx.sessionManager.getActiveSession(adapter.channelName, channelId);
|