evolclaw 3.0.0 → 3.1.1
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 +47 -12
- 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 +42 -1
- package/dist/channels/aun.js +427 -146
- package/dist/channels/dingtalk.js +3 -1
- package/dist/channels/feishu.js +128 -7
- package/dist/channels/qqbot.js +3 -1
- package/dist/channels/wechat.js +4 -1
- package/dist/channels/wecom.js +3 -1
- package/dist/cli/bench.js +1219 -0
- package/dist/cli/index.js +418 -40
- package/dist/cli/init.js +3 -4
- package/dist/cli/link-rules.js +245 -0
- package/dist/cli/net-check.js +640 -0
- package/dist/cli/watch-msg.js +666 -0
- package/dist/config-store.js +82 -5
- package/dist/core/channel-loader.js +23 -10
- package/dist/core/command-handler.js +127 -99
- package/dist/core/evolagent.js +5 -10
- package/dist/core/message/im-renderer.js +93 -48
- package/dist/core/message/items-formatter.js +11 -4
- package/dist/core/message/message-bridge.js +11 -2
- package/dist/core/message/message-log.js +8 -1
- package/dist/core/message/message-processor.js +194 -127
- package/dist/core/message/message-queue.js +10 -3
- package/dist/core/permission.js +95 -3
- package/dist/core/relation/peer-identity.js +161 -0
- package/dist/core/session/session-manager.js +103 -65
- package/dist/core/trigger/manager.js +16 -0
- package/dist/core/trigger/parser.js +110 -0
- package/dist/core/trigger/scheduler.js +7 -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 +186 -19
- 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 +27 -15
- 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/utils/stats.js +216 -2
- 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 +72 -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 +73 -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
|
@@ -6,13 +6,17 @@ 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 } from '../paths.js';
|
|
9
|
+
import { resolvePaths, getPackageRoot, 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
|
+
import { appendMessageLog, buildOutboundEntry } from '../core/message/message-log.js';
|
|
13
|
+
import { chatDirPath } from '../core/session/session-fs-store.js';
|
|
14
|
+
import { appendAidLifecycle } from '../aun/aid/identity.js';
|
|
12
15
|
import { loadAgent, saveAgent } from '../config-store.js';
|
|
13
16
|
import { getProcessStartTime } from '../utils/process-introspect.js';
|
|
14
17
|
import * as outbox from '../aun/outbox.js';
|
|
15
18
|
import { guessMime, formatSize } from '../utils/media-cache.js';
|
|
19
|
+
import { PeerIdentityCache } from '../core/relation/peer-identity.js';
|
|
16
20
|
/**
|
|
17
21
|
* 构造 connect extra_info:自描述本进程身份。
|
|
18
22
|
*
|
|
@@ -63,8 +67,10 @@ export class AUNChannel {
|
|
|
63
67
|
traceWriter = null;
|
|
64
68
|
eventBus = null;
|
|
65
69
|
ownerBoundHandler = null;
|
|
70
|
+
queuedHandler = null;
|
|
66
71
|
pendingEchoMessages = new Map();
|
|
67
72
|
isEchoSending = false;
|
|
73
|
+
agentDir;
|
|
68
74
|
trace(dir, event, data) {
|
|
69
75
|
if (!this.config.aunTrace)
|
|
70
76
|
return;
|
|
@@ -168,7 +174,7 @@ export class AUNChannel {
|
|
|
168
174
|
const name = cached?.name;
|
|
169
175
|
return name && name !== short ? `${short}(${name})` : short;
|
|
170
176
|
}
|
|
171
|
-
extractTextPayload(payload, channelId) {
|
|
177
|
+
extractTextPayload(payload, channelId, senderAid) {
|
|
172
178
|
if (typeof payload === 'string')
|
|
173
179
|
return payload;
|
|
174
180
|
if (payload && typeof payload === 'object') {
|
|
@@ -176,21 +182,38 @@ export class AUNChannel {
|
|
|
176
182
|
const text = typeof obj.text === 'string' ? obj.text : '';
|
|
177
183
|
// action_card_reply:卡片交互回复,触发 interactionCallback,不分发给 agent
|
|
178
184
|
if (obj.type === 'action_card_reply') {
|
|
179
|
-
const cardMsgId = typeof obj.
|
|
185
|
+
const cardMsgId = typeof obj.ref_message_id === 'string' ? obj.ref_message_id
|
|
186
|
+
: typeof obj.card_message_id === 'string' ? obj.card_message_id : '';
|
|
180
187
|
const cardInfo = cardMsgId ? this.cardMessageIdMap.get(cardMsgId) : undefined;
|
|
181
188
|
if (cardInfo) {
|
|
182
|
-
const actionValue = typeof obj.
|
|
189
|
+
const actionValue = typeof obj.value === 'string' ? obj.value
|
|
190
|
+
: typeof obj.action_value === 'string' ? obj.action_value : text;
|
|
183
191
|
if (cardInfo.isCommandCard) {
|
|
184
192
|
// CommandCard:action_value 是完整 slash 命令,构造伪入站消息
|
|
185
193
|
this.cardMessageIdMap.delete(cardMsgId);
|
|
186
194
|
if (this.messageHandler && actionValue.startsWith('/')) {
|
|
187
195
|
const chatType = channelId ? (this.isGroupId(channelId) ? 'group' : 'private') : 'private';
|
|
196
|
+
// 卡片点击者身份:优先 payload.from / payload.sender_aid / payload.user_id,
|
|
197
|
+
// 再 fallback 到外层 senderAid,最后用 cardInfo 中记录的原始命令发起者
|
|
198
|
+
const cardClickerAid = (typeof obj.from === 'string' && obj.from)
|
|
199
|
+
|| (typeof obj.sender_aid === 'string' && obj.sender_aid)
|
|
200
|
+
|| (typeof obj.user_id === 'string' && obj.user_id)
|
|
201
|
+
|| senderAid
|
|
202
|
+
|| cardInfo.initiatorAid
|
|
203
|
+
|| channelId || '';
|
|
204
|
+
// Initiator 校验:群聊中仅卡片发起者可操作(与飞书行为对齐)
|
|
205
|
+
if (cardInfo.initiatorAid && cardClickerAid
|
|
206
|
+
&& cardClickerAid !== cardInfo.initiatorAid
|
|
207
|
+
&& !this.isGroupId(cardClickerAid)) {
|
|
208
|
+
logger.info(`${this.logPrefix()} CommandCard rejected: clicker=${cardClickerAid} initiator=${cardInfo.initiatorAid} mid=${cardMsgId}`);
|
|
209
|
+
return '';
|
|
210
|
+
}
|
|
188
211
|
this.messageHandler({
|
|
189
212
|
channelId: channelId || '',
|
|
190
213
|
chatType,
|
|
191
214
|
content: actionValue,
|
|
192
|
-
peerId:
|
|
193
|
-
peerName: typeof obj.action_label === 'string' ? obj.action_label : undefined,
|
|
215
|
+
peerId: cardClickerAid,
|
|
216
|
+
peerName: typeof obj.label === 'string' ? obj.label : typeof obj.action_label === 'string' ? obj.action_label : undefined,
|
|
194
217
|
messageId: `card-trigger-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
195
218
|
source: 'card-trigger',
|
|
196
219
|
});
|
|
@@ -205,7 +228,7 @@ export class AUNChannel {
|
|
|
205
228
|
type: 'interaction.response',
|
|
206
229
|
id: cardInfo.requestId,
|
|
207
230
|
action: actionValue,
|
|
208
|
-
values: { text, action_label: obj.action_label, behavior: obj.behavior },
|
|
231
|
+
values: { text, action_label: obj.label ?? obj.action_label, behavior: obj.behavior },
|
|
209
232
|
});
|
|
210
233
|
}
|
|
211
234
|
}
|
|
@@ -216,23 +239,126 @@ export class AUNChannel {
|
|
|
216
239
|
// 始终返回空字符串,阻止消息分发给 agent
|
|
217
240
|
return '';
|
|
218
241
|
}
|
|
219
|
-
// quote
|
|
242
|
+
// quote 类型:拼接被引用内容(支持 text / image / file attachments)
|
|
220
243
|
if (obj.type === 'quote' && obj.quote && typeof obj.quote === 'object') {
|
|
221
244
|
const q = obj.quote;
|
|
222
245
|
const quotedText = typeof q.text === 'string' ? q.text : '';
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
246
|
+
const sender = typeof q.sender_display === 'string' ? q.sender_display : '';
|
|
247
|
+
const prefix = sender ? `${sender}: ` : '';
|
|
248
|
+
// 构建引用内容:文本 + 附件描述
|
|
249
|
+
const quoteParts = [];
|
|
250
|
+
if (quotedText)
|
|
251
|
+
quoteParts.push(quotedText);
|
|
252
|
+
if (Array.isArray(q.attachments)) {
|
|
253
|
+
for (const att of q.attachments) {
|
|
254
|
+
if (att && typeof att === 'object') {
|
|
255
|
+
const ct = typeof att.content_type === 'string' ? att.content_type : '';
|
|
256
|
+
const fn = typeof att.filename === 'string' ? att.filename : '';
|
|
257
|
+
if (ct.startsWith('image/')) {
|
|
258
|
+
quoteParts.push(fn ? `[图片: ${fn}]` : '[图片]');
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
quoteParts.push(fn ? `[文件: ${fn}]` : '[文件]');
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
if (quoteParts.length > 0) {
|
|
267
|
+
const lines = quoteParts.join('\n').split('\n');
|
|
268
|
+
const quoted = lines.map((line, i) => `> ${i === 0 ? prefix : ''}${line}`).join('\n');
|
|
227
269
|
return text ? `${quoted}\n\n${text}` : quoted;
|
|
228
270
|
}
|
|
229
271
|
}
|
|
272
|
+
// merge 类型:合并转发消息,展开子消息为可读文本
|
|
273
|
+
if (obj.type === 'merge') {
|
|
274
|
+
const title = typeof obj.title === 'string' ? obj.title : '合并转发消息';
|
|
275
|
+
const parts = [`以下是转发的合并消息「${title}」:\n---`];
|
|
276
|
+
if (Array.isArray(obj.items)) {
|
|
277
|
+
for (const item of obj.items) {
|
|
278
|
+
if (item && typeof item === 'object') {
|
|
279
|
+
const sender = typeof item.sender_display === 'string' ? item.sender_display : '';
|
|
280
|
+
const itemText = typeof item.text === 'string' ? item.text : '';
|
|
281
|
+
const itemType = typeof item.type === 'string' ? item.type : '';
|
|
282
|
+
// 根据子消息类型构建展示
|
|
283
|
+
const lineParts = [];
|
|
284
|
+
if (itemText)
|
|
285
|
+
lineParts.push(itemText);
|
|
286
|
+
// 子消息附件(image/file)
|
|
287
|
+
if (Array.isArray(item.attachments)) {
|
|
288
|
+
for (const att of item.attachments) {
|
|
289
|
+
if (att && typeof att === 'object') {
|
|
290
|
+
const ct = typeof att.content_type === 'string' ? att.content_type : '';
|
|
291
|
+
const fn = typeof att.filename === 'string' ? att.filename : '';
|
|
292
|
+
if (ct.startsWith('image/') || itemType === 'image') {
|
|
293
|
+
lineParts.push(fn ? `[图片: ${fn}]` : '[图片]');
|
|
294
|
+
}
|
|
295
|
+
else if (ct.startsWith('video/') || itemType === 'video') {
|
|
296
|
+
lineParts.push(fn ? `[视频: ${fn}]` : '[视频]');
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
lineParts.push(fn ? `[文件: ${fn}]` : '[文件]');
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
const content = lineParts.join(' ') || `[${itemType || '未知类型'}]`;
|
|
305
|
+
parts.push(sender ? `${sender}: ${content}` : content);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
if (typeof obj.summary === 'string' && obj.summary) {
|
|
310
|
+
parts.push(`\n[摘要] ${obj.summary}`);
|
|
311
|
+
}
|
|
312
|
+
parts.push('---');
|
|
313
|
+
return parts.join('\n');
|
|
314
|
+
}
|
|
230
315
|
if (typeof obj.text === 'string')
|
|
231
316
|
return text;
|
|
232
317
|
return JSON.stringify(payload);
|
|
233
318
|
}
|
|
234
319
|
return '';
|
|
235
320
|
}
|
|
321
|
+
/** 收集 payload 中所有需要下载的 attachments(顶层 + merge.items + quote.quote),按 url 去重 */
|
|
322
|
+
collectAllAttachments(payload) {
|
|
323
|
+
if (!payload || typeof payload !== 'object')
|
|
324
|
+
return [];
|
|
325
|
+
const obj = payload;
|
|
326
|
+
const result = [];
|
|
327
|
+
const seen = new Set();
|
|
328
|
+
const add = (att) => {
|
|
329
|
+
if (!att || typeof att !== 'object')
|
|
330
|
+
return;
|
|
331
|
+
const key = att.url || att.object_key || '';
|
|
332
|
+
if (key && seen.has(key))
|
|
333
|
+
return;
|
|
334
|
+
if (key)
|
|
335
|
+
seen.add(key);
|
|
336
|
+
result.push(att);
|
|
337
|
+
};
|
|
338
|
+
// 顶层 attachments
|
|
339
|
+
if (Array.isArray(obj.attachments)) {
|
|
340
|
+
for (const att of obj.attachments)
|
|
341
|
+
add(att);
|
|
342
|
+
}
|
|
343
|
+
// merge.items 中的子消息 attachments
|
|
344
|
+
if (obj.type === 'merge' && Array.isArray(obj.items)) {
|
|
345
|
+
for (const item of obj.items) {
|
|
346
|
+
if (item && typeof item === 'object' && Array.isArray(item.attachments)) {
|
|
347
|
+
for (const att of item.attachments)
|
|
348
|
+
add(att);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
// quote.quote 中的 attachments
|
|
353
|
+
if (obj.type === 'quote' && obj.quote && typeof obj.quote === 'object') {
|
|
354
|
+
const q = obj.quote;
|
|
355
|
+
if (Array.isArray(q.attachments)) {
|
|
356
|
+
for (const att of q.attachments)
|
|
357
|
+
add(att);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
return result;
|
|
361
|
+
}
|
|
236
362
|
hasExplicitMention(text, target) {
|
|
237
363
|
const escaped = target.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
238
364
|
return new RegExp(`(^|\\s)@${escaped}(?=$|\\s|[.,!?;:,。!?;:]|[\\u4e00-\\u9fff])`).test(text);
|
|
@@ -282,31 +408,24 @@ export class AUNChannel {
|
|
|
282
408
|
}
|
|
283
409
|
return out;
|
|
284
410
|
}
|
|
285
|
-
buildGroupReplyContext(taskId, senderAid, encrypted) {
|
|
286
|
-
const replyContext = { metadata: { encrypted } };
|
|
411
|
+
buildGroupReplyContext(taskId, senderAid, encrypted, messageId, chatmode) {
|
|
412
|
+
const replyContext = { metadata: { encrypted, chatmode } };
|
|
287
413
|
if (taskId)
|
|
288
414
|
replyContext.threadId = taskId;
|
|
289
415
|
replyContext.peerId = senderAid;
|
|
416
|
+
if (messageId)
|
|
417
|
+
replyContext.replyToMessageId = messageId;
|
|
290
418
|
return replyContext;
|
|
291
419
|
}
|
|
292
|
-
acknowledgeImmediately(messageId,
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
logger.debug(`${this.logPrefix()} Immediate ack failed: ${e}`);
|
|
296
|
-
});
|
|
297
|
-
}
|
|
420
|
+
acknowledgeImmediately(messageId, _seq) {
|
|
421
|
+
// SDK internally manages seq tracking and ack — do not call message.ack RPC directly,
|
|
422
|
+
// as it corrupts the SDK's seqTracker state and breaks V2 e2ee message pull.
|
|
298
423
|
if (messageId)
|
|
299
424
|
this.messageSeqMap.delete(messageId);
|
|
300
425
|
}
|
|
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;
|
|
426
|
+
shouldEncrypt(_peerId) {
|
|
427
|
+
// Default to plaintext; only encrypt when session is explicitly marked encrypted
|
|
428
|
+
return false;
|
|
310
429
|
}
|
|
311
430
|
_aid;
|
|
312
431
|
_selfName; // 本地 agent.md 中的 name,首次 connect 时读取
|
|
@@ -318,7 +437,6 @@ export class AUNChannel {
|
|
|
318
437
|
peerE2ee = new Map();
|
|
319
438
|
static E2EE_PROBE_TTL = 10 * 60 * 1000; // 10min
|
|
320
439
|
plaintextRecv = 0;
|
|
321
|
-
sessionModeResolver;
|
|
322
440
|
interactionCallback;
|
|
323
441
|
// action_card message_id → { requestId, isCommandCard }(用于关联 action_card_reply)
|
|
324
442
|
cardMessageIdMap = new Map();
|
|
@@ -356,6 +474,7 @@ export class AUNChannel {
|
|
|
356
474
|
aidStatsCollector;
|
|
357
475
|
constructor(config) {
|
|
358
476
|
this.config = config;
|
|
477
|
+
this.agentDir = agentDirPath(config.aid);
|
|
359
478
|
if (config.aunTrace) {
|
|
360
479
|
this.traceWriter = new LogWriter({
|
|
361
480
|
baseName: 'aun',
|
|
@@ -579,6 +698,7 @@ export class AUNChannel {
|
|
|
579
698
|
}
|
|
580
699
|
logger.info(`${this.logPrefix()} Connected as ${this._aid}`);
|
|
581
700
|
appendAidEvent({ ts: Date.now(), iso: new Date().toISOString(), event: 'connected', aid: this.config.aid, gateway: this.client._gatewayUrl });
|
|
701
|
+
appendAidLifecycle({ ts: Date.now(), iso: new Date().toISOString(), event: 'connected', aid: this.config.aid, gateway: this.client._gatewayUrl });
|
|
582
702
|
// Send welcome message to owner after first connection
|
|
583
703
|
await this.sendWelcomeMessage();
|
|
584
704
|
}
|
|
@@ -625,7 +745,7 @@ export class AUNChannel {
|
|
|
625
745
|
const ownerAidClean = owner.startsWith('@') ? owner.slice(1) : owner;
|
|
626
746
|
const ownerDisplayName = (ownerInfo.name || ownerAidClean.split('.')[0]).slice(0, 12);
|
|
627
747
|
const currentNameMatch = existingFrontmatter.match(/^name:\s*"?([^"\n]+)/m);
|
|
628
|
-
const currentName = currentNameMatch?.[1]?.trim();
|
|
748
|
+
const currentName = currentNameMatch?.[1]?.trim().replace(/"$/, '');
|
|
629
749
|
const aidLabel = aidName.split('.')[0];
|
|
630
750
|
let agentDisplayName;
|
|
631
751
|
if (currentName && currentName !== aidLabel) {
|
|
@@ -634,13 +754,19 @@ export class AUNChannel {
|
|
|
634
754
|
else {
|
|
635
755
|
agentDisplayName = `${ownerDisplayName}的Evol助手 (${aidLabel})`;
|
|
636
756
|
}
|
|
757
|
+
// Preserve user-provided description (from `agent new --description`), fallback to default
|
|
758
|
+
const currentDescMatch = existingFrontmatter.match(/^description:\s*"?([^"\n]*)/m);
|
|
759
|
+
const currentDesc = currentDescMatch?.[1]?.trim().replace(/"$/, '');
|
|
760
|
+
const agentDescription = currentDesc
|
|
761
|
+
? currentDesc
|
|
762
|
+
: 'EvolClaw AI Agent Gateway - 连接 Claude/Codex 到消息通道';
|
|
637
763
|
// Generate new agent.md (no `initialized` frontmatter — that's now in config.json)
|
|
638
764
|
const newAgentMd = `---
|
|
639
765
|
aid: "${aid}"
|
|
640
766
|
name: "${agentDisplayName}"
|
|
641
767
|
type: "codeagent"
|
|
642
768
|
version: "1.0.0"
|
|
643
|
-
description: "
|
|
769
|
+
description: "${agentDescription}"
|
|
644
770
|
tags:
|
|
645
771
|
- evolclaw
|
|
646
772
|
- ai-agent
|
|
@@ -720,11 +846,11 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
720
846
|
async downloadAttachment(att, channelId) {
|
|
721
847
|
const ownerAid = att.owner_aid || this._aid || '';
|
|
722
848
|
const objectKey = att.object_key;
|
|
723
|
-
const filename = att.filename || objectKey.split('/').pop() || 'unknown';
|
|
724
849
|
if (!objectKey) {
|
|
725
850
|
logger.warn(`${this.logPrefix()} Attachment missing object_key, skipping`);
|
|
726
851
|
return null;
|
|
727
852
|
}
|
|
853
|
+
const filename = att.filename || objectKey.split('/').pop() || 'unknown';
|
|
728
854
|
let downloadUrl;
|
|
729
855
|
try {
|
|
730
856
|
const ticket = await this.callAndTrace('storage.create_download_ticket', {
|
|
@@ -802,10 +928,8 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
802
928
|
if (this._aid && text.includes(`@${this._aid}`)) {
|
|
803
929
|
mentions.push(this._aid);
|
|
804
930
|
}
|
|
805
|
-
// Process attachments
|
|
806
|
-
const rawAttachments =
|
|
807
|
-
? payload.attachments
|
|
808
|
-
: [];
|
|
931
|
+
// Process attachments (顶层 + 嵌套在 merge.items / quote.quote 中的)
|
|
932
|
+
const rawAttachments = this.collectAllAttachments(payload);
|
|
809
933
|
let finalText = text;
|
|
810
934
|
if (rawAttachments.length > 0 && this.client) {
|
|
811
935
|
const fileParts = [];
|
|
@@ -828,20 +952,40 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
828
952
|
// 私聊 channelId = 对端 AID(不再读 payload.chat_id 含 device 三段式)
|
|
829
953
|
// device_id 仅 SDK 内部多实例去重用,evolclaw session 层面跨端共享会话
|
|
830
954
|
const chatId = fromAid;
|
|
831
|
-
|
|
955
|
+
// 解析对端身份(30天缓存)
|
|
956
|
+
const peerIdentity = await PeerIdentityCache.resolve('aun', fromAid, this.agentDir, this.client, false);
|
|
832
957
|
const shortAid = this.getShortAid(fromAid);
|
|
833
|
-
const displayName =
|
|
958
|
+
const displayName = peerIdentity.name || shortAid;
|
|
834
959
|
// 详细 dispatch 决策日志:记录消息为何被路由到 agent
|
|
835
960
|
const p2pPayloadType = (payload && typeof payload === 'object') ? payload.type ?? '' : '';
|
|
836
|
-
logger.info(`${this.logPrefix()} P2P dispatch decision: mid=${messageId} from=${shortAid}(${displayName}) peerType=${
|
|
961
|
+
logger.info(`${this.logPrefix()} P2P dispatch decision: mid=${messageId} from=${shortAid}(${displayName}) peerType=${peerIdentity.type} payloadType=${p2pPayloadType} chatId=${chatId} encrypt=${msgEncrypted} textPreview=${JSON.stringify(text.slice(0, 80))}`);
|
|
837
962
|
// action_card_reply 已在 extractTextPayload 中消费,不分发给 agent
|
|
838
963
|
if (p2pPayloadType === 'action_card_reply')
|
|
839
964
|
return;
|
|
840
|
-
|
|
965
|
+
// menu.query:自定义消息快速路径,需要原始 payload JSON 传递给 bridge
|
|
966
|
+
if (p2pPayloadType === 'menu.query') {
|
|
967
|
+
this.acknowledgeImmediately(messageId, seq);
|
|
968
|
+
this.dispatchMessage({
|
|
969
|
+
channelId: chatId, userId: fromAid,
|
|
970
|
+
text: JSON.stringify(payload),
|
|
971
|
+
chatType: 'private', messageId, seq,
|
|
972
|
+
peerName: displayName || undefined,
|
|
973
|
+
peerType: peerIdentity.type,
|
|
974
|
+
});
|
|
975
|
+
return;
|
|
976
|
+
}
|
|
977
|
+
// payload 类型白名单:信号类消息(status / event / thought 等)不进 Agent
|
|
978
|
+
if (p2pPayloadType && !AUNChannel.PROACTIVE_ALLOW_TYPES.has(p2pPayloadType)) {
|
|
979
|
+
this.acknowledgeImmediately(messageId, seq);
|
|
980
|
+
logger.info(`${this.logPrefix()} P2P dropped (type deny): type=${p2pPayloadType} from=${shortAid}(${displayName}) mid=${messageId}`);
|
|
981
|
+
return;
|
|
982
|
+
}
|
|
983
|
+
const msgChatmode = (payload && typeof payload === 'object') ? payload.chatmode : undefined;
|
|
984
|
+
logger.info(`${this.logPrefix()} P2P dispatched: from=${shortAid}(${displayName}) mid=${messageId} encrypt=${msgEncrypted} chatmode=${msgChatmode ?? 'none'} text=${finalText.slice(0, 60)}`);
|
|
841
985
|
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
986
|
const isSystemP2P = p2pPayloadType === 'event';
|
|
843
|
-
this.aidStatsCollector?.recordInbound(this.config.aid, fromAid, Buffer.byteLength(finalText, 'utf-8'), finalText, isSystemP2P);
|
|
844
|
-
const replyContext = { metadata: { encrypted: msgEncrypted } };
|
|
987
|
+
this.aidStatsCollector?.recordInbound(this.config.aid, fromAid, Buffer.byteLength(finalText, 'utf-8'), finalText, isSystemP2P, msgEncrypted, msgChatmode);
|
|
988
|
+
const replyContext = { metadata: { encrypted: msgEncrypted, chatmode: msgChatmode } };
|
|
845
989
|
if (taskId)
|
|
846
990
|
replyContext.threadId = taskId;
|
|
847
991
|
this.dispatchMessage({
|
|
@@ -854,7 +998,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
854
998
|
taskId,
|
|
855
999
|
mentions,
|
|
856
1000
|
peerName: displayName || undefined,
|
|
857
|
-
peerType:
|
|
1001
|
+
peerType: peerIdentity.type,
|
|
858
1002
|
replyContext,
|
|
859
1003
|
});
|
|
860
1004
|
}
|
|
@@ -865,7 +1009,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
865
1009
|
const groupId = msg.group_id ?? '';
|
|
866
1010
|
const senderAid = msg.sender_aid ?? '';
|
|
867
1011
|
const payload = msg.payload ?? '';
|
|
868
|
-
const text = this.extractTextPayload(payload, groupId);
|
|
1012
|
+
const text = this.extractTextPayload(payload, groupId, senderAid);
|
|
869
1013
|
const taskId = typeof payload === 'object' && payload !== null ? payload.thread_id : undefined;
|
|
870
1014
|
const messageId = msg.message_id ?? '';
|
|
871
1015
|
const seq = msg.seq;
|
|
@@ -884,13 +1028,14 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
884
1028
|
logger.debug(`${this.logPrefix()} Group dropped: own message (group=${groupId} mid=${messageId})`);
|
|
885
1029
|
return;
|
|
886
1030
|
}
|
|
887
|
-
// 短 echo 快速通道:连通性测试要尽量低延迟,命中后绕过所有 await
|
|
1031
|
+
// 短 echo 快速通道:连通性测试要尽量低延迟,命中后绕过所有 await(后续 mention 过滤)
|
|
888
1032
|
{
|
|
889
1033
|
const firstLineFast = text.split('\n')[0] || '';
|
|
890
1034
|
const hasEvolClawTrace = /\[EvolClaw\.(receive|reply|agent)\]/.test(text);
|
|
891
1035
|
if (/echo/i.test(firstLineFast) && firstLineFast.trim().length <= 10 && !hasEvolClawTrace) {
|
|
892
1036
|
this.acknowledgeImmediately(messageId, seq);
|
|
893
1037
|
const msgEncryptedFast = !!(msg.e2ee);
|
|
1038
|
+
const msgChatmodeFast = (payload && typeof payload === 'object') ? payload.chatmode : undefined;
|
|
894
1039
|
const peerInfo = this.peerInfoCached(senderAid);
|
|
895
1040
|
const shortAid = this.getShortAid(senderAid);
|
|
896
1041
|
const displayName = peerInfo?.name || shortAid;
|
|
@@ -906,25 +1051,37 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
906
1051
|
peerName: displayName,
|
|
907
1052
|
peerType: peerInfo?.type || 'unknown',
|
|
908
1053
|
seq,
|
|
909
|
-
replyContext: this.buildGroupReplyContext(undefined, senderAid, msgEncryptedFast),
|
|
1054
|
+
replyContext: this.buildGroupReplyContext(undefined, senderAid, msgEncryptedFast, messageId, msgChatmodeFast),
|
|
910
1055
|
createdAt,
|
|
911
1056
|
});
|
|
912
1057
|
return;
|
|
913
1058
|
}
|
|
914
1059
|
}
|
|
915
|
-
//
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
1060
|
+
// action_card_reply 已在 extractTextPayload 中消费,不分发给 agent
|
|
1061
|
+
{
|
|
1062
|
+
const payloadType = (payload && typeof payload === 'object') ? payload.type ?? '' : '';
|
|
1063
|
+
if (payloadType === 'action_card_reply')
|
|
1064
|
+
return;
|
|
1065
|
+
}
|
|
1066
|
+
// ── payload 类型白名单(所有模式生效) ──
|
|
1067
|
+
// 信号类消息(status / event / thought / task.update 等)不进 Agent
|
|
1068
|
+
{
|
|
1069
|
+
const payloadObj = (payload && typeof payload === 'object') ? payload : null;
|
|
1070
|
+
const payloadType = payloadObj?.type ?? '';
|
|
1071
|
+
// menu.query:自定义消息快速路径
|
|
1072
|
+
if (payloadType === 'menu.query') {
|
|
1073
|
+
this.acknowledgeImmediately(messageId, seq);
|
|
1074
|
+
this.dispatchMessage({
|
|
1075
|
+
channelId: groupId, userId: senderAid,
|
|
1076
|
+
text: JSON.stringify(payload),
|
|
1077
|
+
chatType: 'group', messageId, seq, groupId,
|
|
1078
|
+
});
|
|
1079
|
+
return;
|
|
1080
|
+
}
|
|
1081
|
+
if (payloadType && !AUNChannel.PROACTIVE_ALLOW_TYPES.has(payloadType)) {
|
|
1082
|
+
this.acknowledgeImmediately(messageId, seq);
|
|
1083
|
+
logger.info(`${this.logPrefix()} Group dropped (type deny): type=${payloadType} group=${groupId} sender=${senderAid} mid=${messageId}`);
|
|
1084
|
+
return;
|
|
928
1085
|
}
|
|
929
1086
|
}
|
|
930
1087
|
// 记录入站消息加密状态,透传到出站 ReplyContext
|
|
@@ -955,16 +1112,17 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
955
1112
|
};
|
|
956
1113
|
let echoText = text;
|
|
957
1114
|
echoText += `\n${echoTs()} [EvolClaw.receive] from=${senderAid} mid=${messageId} chat=group self=${this._aid || 'unknown'} conn_uptime=${this.connectedAt ? Math.round((Date.now() - this.connectedAt) / 1000) + 's' : 'unknown'}`;
|
|
1115
|
+
const msgChatmodeEcho = (payload && typeof payload === 'object') ? payload.chatmode : undefined;
|
|
958
1116
|
this.pendingEchoMessages.set(messageId, {
|
|
959
1117
|
text: echoText,
|
|
960
1118
|
channelId: groupId,
|
|
961
|
-
context: this.buildGroupReplyContext(undefined, senderAid, msgEncrypted),
|
|
1119
|
+
context: this.buildGroupReplyContext(undefined, senderAid, msgEncrypted, messageId, msgChatmodeEcho),
|
|
962
1120
|
receiveTs: Date.now(),
|
|
963
1121
|
});
|
|
964
1122
|
// 继续走正常 Agent 流程(下面的代码会 dispatch)
|
|
965
1123
|
}
|
|
966
1124
|
else if (/echo/i.test(firstLineGroup) && hasEvolClawTraceGroup) {
|
|
967
|
-
//
|
|
1125
|
+
// 回声炸弹:已被任何 EvolClaw 节点 trace 过的 echo,直接丢弃
|
|
968
1126
|
this.acknowledgeImmediately(messageId, seq);
|
|
969
1127
|
logger.info(`${this.logPrefix()} Group dropped: echo bomb (already-traced group=${groupId} sender=${senderAid} mid=${messageId})`);
|
|
970
1128
|
return;
|
|
@@ -978,10 +1136,8 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
978
1136
|
}
|
|
979
1137
|
}
|
|
980
1138
|
const strippedText = this.stripSelfMentionIfOnly(text, this._aid);
|
|
981
|
-
// Detect attachments before the empty-text guard
|
|
982
|
-
const rawAttachments =
|
|
983
|
-
? payload.attachments
|
|
984
|
-
: [];
|
|
1139
|
+
// Detect attachments before the empty-text guard (顶层 + 嵌套)
|
|
1140
|
+
const rawAttachments = this.collectAllAttachments(payload);
|
|
985
1141
|
const hasAttachments = rawAttachments.length > 0;
|
|
986
1142
|
// Allow through if there's text OR attachments; both-empty messages are silently dropped
|
|
987
1143
|
if (!strippedText && !hasAttachments) {
|
|
@@ -1012,9 +1168,9 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1012
1168
|
finalText = parts.join('\n\n');
|
|
1013
1169
|
}
|
|
1014
1170
|
}
|
|
1015
|
-
const
|
|
1171
|
+
const peerIdentity = await PeerIdentityCache.resolve('aun', senderAid, this.agentDir, this.client, false);
|
|
1016
1172
|
const shortAid = this.getShortAid(senderAid);
|
|
1017
|
-
const displayName =
|
|
1173
|
+
const displayName = peerIdentity.name || shortAid;
|
|
1018
1174
|
// 详细 dispatch 决策日志:记录消息为何被路由到 agent
|
|
1019
1175
|
const payloadType = (payload && typeof payload === 'object') ? payload.type ?? '' : '';
|
|
1020
1176
|
const textMentionSelf = this._aid ? this.hasExplicitMention(text, this._aid) : false;
|
|
@@ -1026,26 +1182,27 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1026
1182
|
: mentionedSelf
|
|
1027
1183
|
? (structMentionSelf ? 'mention.self(struct)' : 'mention.self(text)')
|
|
1028
1184
|
: `${dispatchMode}.no-mention`;
|
|
1029
|
-
logger.info(`${this.logPrefix()} Group dispatch decision: mid=${messageId} group=${groupId} sender=${shortAid}(${displayName}) peerType=${
|
|
1185
|
+
logger.info(`${this.logPrefix()} Group dispatch decision: mid=${messageId} group=${groupId} sender=${shortAid}(${displayName}) peerType=${peerIdentity.type} payloadType=${payloadType} dispatchMode=${dispatchMode} reason=${reason} structMentions=${JSON.stringify(payloadMentions)} textMentionSelf=${textMentionSelf} textMentionAll=${textMentionAll} structMentionSelf=${structMentionSelf} structMentionAll=${structMentionAll} encrypt=${msgEncrypted} textPreview=${JSON.stringify(text.slice(0, 80))}`);
|
|
1030
1186
|
// action_card_reply 已在 extractTextPayload 中消费,不分发给 agent
|
|
1031
1187
|
if (payloadType === 'action_card_reply')
|
|
1032
1188
|
return;
|
|
1033
|
-
|
|
1189
|
+
const msgChatmode = (payload && typeof payload === 'object') ? payload.chatmode : undefined;
|
|
1190
|
+
logger.info(`${this.logPrefix()} Group dispatched: group=${groupId} sender=${shortAid}(${displayName}) mode=${dispatchMode} mid=${messageId} chatmode=${msgChatmode ?? 'none'} text=${finalText.slice(0, 60)}`);
|
|
1034
1191
|
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 });
|
|
1035
|
-
this.aidStatsCollector?.recordInbound(this.config.aid, senderAid, Buffer.byteLength(finalText, 'utf-8'), finalText, payloadType === 'event');
|
|
1192
|
+
this.aidStatsCollector?.recordInbound(this.config.aid, senderAid, Buffer.byteLength(finalText, 'utf-8'), finalText, payloadType === 'event', msgEncrypted, msgChatmode);
|
|
1036
1193
|
this.dispatchMessage({
|
|
1037
1194
|
channelId: groupId,
|
|
1038
1195
|
groupId,
|
|
1039
1196
|
userId: senderAid,
|
|
1040
1197
|
peerName: displayName || undefined,
|
|
1041
|
-
peerType:
|
|
1198
|
+
peerType: peerIdentity.type,
|
|
1042
1199
|
text: finalText,
|
|
1043
1200
|
chatType: 'group',
|
|
1044
1201
|
messageId,
|
|
1045
1202
|
seq,
|
|
1046
1203
|
taskId,
|
|
1047
1204
|
mentions,
|
|
1048
|
-
replyContext: this.buildGroupReplyContext(taskId, senderAid, msgEncrypted),
|
|
1205
|
+
replyContext: this.buildGroupReplyContext(taskId, senderAid, msgEncrypted, messageId, msgChatmode),
|
|
1049
1206
|
});
|
|
1050
1207
|
}
|
|
1051
1208
|
dispatchMessage(event) {
|
|
@@ -1068,7 +1225,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1068
1225
|
this.handleEcho(event);
|
|
1069
1226
|
return;
|
|
1070
1227
|
}
|
|
1071
|
-
//
|
|
1228
|
+
// 回声炸弹:已被任何 EvolClaw 节点 trace 过的 echo,直接丢弃(防止多 agent 间无限回声)
|
|
1072
1229
|
if (/echo/i.test(firstLine) && hasEvolClawTracePrivate) {
|
|
1073
1230
|
logger.info(`${this.logPrefix()} Dropped: echo bomb (already-traced mid=${event.messageId} chat=${event.chatType})`);
|
|
1074
1231
|
return;
|
|
@@ -1258,16 +1415,46 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1258
1415
|
const d = data;
|
|
1259
1416
|
const reason = d.reason ?? '';
|
|
1260
1417
|
const error = d.error ?? 'unknown';
|
|
1418
|
+
const code = d.code ?? d.detail?.code ?? 0;
|
|
1419
|
+
const detail = (d.detail && typeof d.detail === 'object') ? d.detail : {};
|
|
1261
1420
|
if (this.intentionalDisconnect)
|
|
1262
1421
|
return;
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
this.
|
|
1422
|
+
if (this.isKickReason(reason) || code >= 4001) {
|
|
1423
|
+
// @ts-ignore — methods defined below in same class
|
|
1424
|
+
const kickDetail = this.buildKickDetail(code, reason, detail);
|
|
1425
|
+
// @ts-ignore — methods defined below in same class
|
|
1426
|
+
const action = this.classifyKickAction(code);
|
|
1427
|
+
appendAidEvent({
|
|
1428
|
+
ts: Date.now(), iso: new Date().toISOString(),
|
|
1429
|
+
event: 'kicked', aid: this.config.aid,
|
|
1430
|
+
code, reason, action,
|
|
1431
|
+
evictedBy: kickDetail.evictedBy,
|
|
1432
|
+
quotaKind: kickDetail.quotaKind,
|
|
1433
|
+
});
|
|
1434
|
+
appendAidLifecycle({
|
|
1435
|
+
ts: Date.now(), iso: new Date().toISOString(),
|
|
1436
|
+
event: 'kicked', aid: this.config.aid,
|
|
1437
|
+
code, reason, action,
|
|
1438
|
+
evictedBy: kickDetail.evictedBy,
|
|
1439
|
+
newExtra: kickDetail.newExtra,
|
|
1440
|
+
quotaKind: kickDetail.quotaKind,
|
|
1441
|
+
});
|
|
1442
|
+
if (action === 'no_retry') {
|
|
1443
|
+
logger.error(`${this.logPrefix()} Kicked (code=${code}): ${reason} — will NOT retry`);
|
|
1444
|
+
this.setAidStatus('kicked_no_retry', { lastError: `kicked(${code}): ${reason}`.slice(0, 80), kickDetail });
|
|
1445
|
+
}
|
|
1446
|
+
else if (action === 'retry_once') {
|
|
1447
|
+
logger.warn(`${this.logPrefix()} Kicked (code=${code}): ${reason} — retrying once after ${AUNChannel.FALLBACK_DELAY_MS / 1000}s`);
|
|
1448
|
+
this.setAidStatus('kicked', { lastError: `kicked(${code}): ${reason}`.slice(0, 80), kickDetail });
|
|
1449
|
+
this.takeoverReconnect(AUNChannel.FALLBACK_DELAY_MS, 'kicked');
|
|
1450
|
+
}
|
|
1451
|
+
else {
|
|
1452
|
+
logger.warn(`${this.logPrefix()} Kicked (code=${code}): ${reason} — retrying after ${AUNChannel.TAKEOVER_DELAY_MS / 1000}s`);
|
|
1453
|
+
this.setAidStatus('kicked', { lastError: `kicked(${code}): ${reason}`.slice(0, 80), kickDetail });
|
|
1454
|
+
this.takeoverReconnect(AUNChannel.TAKEOVER_DELAY_MS, 'kicked');
|
|
1455
|
+
}
|
|
1268
1456
|
}
|
|
1269
1457
|
else {
|
|
1270
|
-
// 其他 terminal failure(含 max_attempts_exhausted 兜底)→ 1min 后再试
|
|
1271
1458
|
logger.error(`${this.logPrefix()} Terminal failure: ${error}${reason ? ` (${reason})` : ''}, retrying in ${AUNChannel.FALLBACK_DELAY_MS / 1000}s`);
|
|
1272
1459
|
this.setAidStatus('failed', { lastError: `${error}`.slice(0, 80) });
|
|
1273
1460
|
this.takeoverReconnect(AUNChannel.FALLBACK_DELAY_MS, 'terminal');
|
|
@@ -1281,11 +1468,58 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1281
1468
|
const r = reason.toLowerCase();
|
|
1282
1469
|
if (r.includes('kicked') || r.includes('kick'))
|
|
1283
1470
|
return true;
|
|
1284
|
-
|
|
1285
|
-
if (/close code 40(0[13]|0[89]|1[01])/.test(r))
|
|
1471
|
+
if (/close code 40\d{2}/.test(r))
|
|
1286
1472
|
return true;
|
|
1287
1473
|
return false;
|
|
1288
1474
|
}
|
|
1475
|
+
/**
|
|
1476
|
+
* 根据 close code 决定重试策略:
|
|
1477
|
+
* - 'no_retry': 不重试(被挤掉、AID 无效、ACL 拒绝、长连接已存在、配额超限)
|
|
1478
|
+
* - 'retry_once': 重试一次(auth 失败可能 token 刚过期、nonce 无效)
|
|
1479
|
+
* - 'retry_delay': 延迟重试(短连接容量超限、空闲超时)
|
|
1480
|
+
*/
|
|
1481
|
+
classifyKickAction(code) {
|
|
1482
|
+
switch (code) {
|
|
1483
|
+
case 4003: // AID 无效
|
|
1484
|
+
case 4009: // 服务端主动踢
|
|
1485
|
+
case 4011: // ACL 拒绝
|
|
1486
|
+
case 4012: // 长连接已存在(自己另一个实例在线)
|
|
1487
|
+
case 4015: // 被新连接挤掉
|
|
1488
|
+
return 'no_retry';
|
|
1489
|
+
case 4001: // auth 失败(token 可能刚过期)
|
|
1490
|
+
case 4010: // nonce 无效
|
|
1491
|
+
return 'retry_once';
|
|
1492
|
+
case 4008: // auth 超时
|
|
1493
|
+
case 4013: // 短连接容量超限
|
|
1494
|
+
case 4014: // 短连接空闲超时
|
|
1495
|
+
return 'retry_delay';
|
|
1496
|
+
default:
|
|
1497
|
+
return 'retry_delay';
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
buildKickDetail(code, reason, detail) {
|
|
1501
|
+
const evictedByRaw = detail.evicted_by || detail.new_extra_info;
|
|
1502
|
+
let evictedBy;
|
|
1503
|
+
if (evictedByRaw && typeof evictedByRaw === 'object') {
|
|
1504
|
+
evictedBy = {
|
|
1505
|
+
aid: evictedByRaw.aid,
|
|
1506
|
+
deviceId: evictedByRaw.device_id,
|
|
1507
|
+
slotId: evictedByRaw.slot_id,
|
|
1508
|
+
app: evictedByRaw.app,
|
|
1509
|
+
hostname: evictedByRaw.hostname,
|
|
1510
|
+
};
|
|
1511
|
+
}
|
|
1512
|
+
return {
|
|
1513
|
+
code,
|
|
1514
|
+
reason,
|
|
1515
|
+
ts: Date.now(),
|
|
1516
|
+
evictedBy,
|
|
1517
|
+
quotaKind: detail.quota_kind,
|
|
1518
|
+
limit: detail.limit,
|
|
1519
|
+
selfExtra: detail.self_extra_info,
|
|
1520
|
+
newExtra: detail.new_extra_info,
|
|
1521
|
+
};
|
|
1522
|
+
}
|
|
1289
1523
|
/**
|
|
1290
1524
|
* TS 层接管重连:force close 当前 SDK client,安排 delayMs 后重新 initClient。
|
|
1291
1525
|
* 用于 flap / kicked / terminal_failed 三类场景,统一退避路径。
|
|
@@ -1347,7 +1581,11 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1347
1581
|
if (this.eventBus && this.ownerBoundHandler && typeof this.eventBus.unsubscribe === 'function') {
|
|
1348
1582
|
this.eventBus.unsubscribe('channel:owner-bound', this.ownerBoundHandler);
|
|
1349
1583
|
}
|
|
1584
|
+
if (this.eventBus && this.queuedHandler && typeof this.eventBus.unsubscribe === 'function') {
|
|
1585
|
+
this.eventBus.unsubscribe('task:queued', this.queuedHandler);
|
|
1586
|
+
}
|
|
1350
1587
|
this.ownerBoundHandler = null;
|
|
1588
|
+
this.queuedHandler = null;
|
|
1351
1589
|
this.eventBus = bus;
|
|
1352
1590
|
if (bus && typeof bus.subscribe === 'function') {
|
|
1353
1591
|
const handler = (event) => {
|
|
@@ -1365,6 +1603,13 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1365
1603
|
};
|
|
1366
1604
|
bus.subscribe('channel:owner-bound', handler);
|
|
1367
1605
|
this.ownerBoundHandler = handler;
|
|
1606
|
+
const queuedHandler = (event) => {
|
|
1607
|
+
if (event.channel !== this.config.channelName)
|
|
1608
|
+
return;
|
|
1609
|
+
this.sendProcessingStatus(event.channelId, 'queued', '', '', event.replyContext);
|
|
1610
|
+
};
|
|
1611
|
+
bus.subscribe('task:queued', queuedHandler);
|
|
1612
|
+
this.queuedHandler = queuedHandler;
|
|
1368
1613
|
}
|
|
1369
1614
|
}
|
|
1370
1615
|
onProjectPathRequest(provider) {
|
|
@@ -1373,9 +1618,6 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1373
1618
|
onMessage(handler) {
|
|
1374
1619
|
this.messageHandler = handler;
|
|
1375
1620
|
}
|
|
1376
|
-
setSessionModeResolver(resolver) {
|
|
1377
|
-
this.sessionModeResolver = resolver;
|
|
1378
|
-
}
|
|
1379
1621
|
setDispatchModeResolver(resolver) {
|
|
1380
1622
|
this.dispatchModeResolver = resolver;
|
|
1381
1623
|
}
|
|
@@ -1392,9 +1634,6 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1392
1634
|
await this.flushPendingEcho(channelId);
|
|
1393
1635
|
}
|
|
1394
1636
|
let finalText = text;
|
|
1395
|
-
if (context?.title && (this.sentCount.get(channelId) || 0) > 0) {
|
|
1396
|
-
finalText = '最终回复\n' + text;
|
|
1397
|
-
}
|
|
1398
1637
|
this.sentCount.set(channelId, (this.sentCount.get(channelId) || 0) + 1);
|
|
1399
1638
|
if (this.isGroupId(channelId) && context?.peerId) {
|
|
1400
1639
|
if (!finalText.includes(`@${context.peerId}`)) {
|
|
@@ -1480,6 +1719,8 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1480
1719
|
payload.task_id = context.metadata.taskId;
|
|
1481
1720
|
if (context?.metadata?.chatmode)
|
|
1482
1721
|
payload.chatmode = context.metadata.chatmode;
|
|
1722
|
+
// 诊断日志:记录 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}`);
|
|
1483
1724
|
const isGroup = this.isGroupId(channelId);
|
|
1484
1725
|
const targetAid = channelId;
|
|
1485
1726
|
const encryptTarget = isGroup ? channelId : targetAid;
|
|
@@ -1491,7 +1732,8 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1491
1732
|
if (isGroup) {
|
|
1492
1733
|
params.group_id = channelId;
|
|
1493
1734
|
const result = await this.callAndTrace('group.send', params);
|
|
1494
|
-
|
|
1735
|
+
const mid = result?.message?.message_id ?? result?.message_id ?? null;
|
|
1736
|
+
if (!mid) {
|
|
1495
1737
|
const dispatchStatus = result?.message_dispatch?.status;
|
|
1496
1738
|
if (dispatchStatus === 'debounced' || dispatchStatus === 'dispatched') {
|
|
1497
1739
|
logger.info(`${this.logPrefix()} group.send ok (${dispatchStatus}): group=${channelId} encrypt=${encrypt} text=${finalText.slice(0, 60)}`);
|
|
@@ -1501,9 +1743,10 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1501
1743
|
}
|
|
1502
1744
|
}
|
|
1503
1745
|
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:
|
|
1506
|
-
this.aidStatsCollector?.recordOutbound(this.config.aid, channelId, Buffer.byteLength(finalText, 'utf-8'), finalText);
|
|
1746
|
+
logger.info(`${this.logPrefix()} group.send ok: group=${channelId} mid=${mid} encrypt=${encrypt} text=${finalText.slice(0, 60)}`);
|
|
1747
|
+
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
|
+
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);
|
|
1507
1750
|
}
|
|
1508
1751
|
}
|
|
1509
1752
|
else {
|
|
@@ -1515,7 +1758,8 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1515
1758
|
else {
|
|
1516
1759
|
logger.info(`${this.logPrefix()} message.send ok: to=${this.peerLabel(targetAid)} mid=${result.message_id} encrypt=${encrypt} text=${finalText.slice(0, 60)}`);
|
|
1517
1760
|
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 });
|
|
1518
|
-
this.aidStatsCollector?.recordOutbound(this.config.aid, targetAid, Buffer.byteLength(finalText, 'utf-8'), finalText);
|
|
1761
|
+
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);
|
|
1519
1763
|
}
|
|
1520
1764
|
}
|
|
1521
1765
|
return true;
|
|
@@ -1529,7 +1773,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1529
1773
|
if (isGroup) {
|
|
1530
1774
|
this.trace('OUT', 'group.send.fallback', params);
|
|
1531
1775
|
const result = await this.client.call('group.send', params);
|
|
1532
|
-
this.trace('OUT', 'group.send.fallback.ok', { message_id: result?.message_id });
|
|
1776
|
+
this.trace('OUT', 'group.send.fallback.ok', { message_id: result?.message?.message_id ?? result?.message_id });
|
|
1533
1777
|
if (!result || !result.message_id) {
|
|
1534
1778
|
logger.warn(`${this.logPrefix()} group.send fallback returned no message_id: ${JSON.stringify(result)}`);
|
|
1535
1779
|
}
|
|
@@ -1557,6 +1801,33 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1557
1801
|
}
|
|
1558
1802
|
}
|
|
1559
1803
|
}
|
|
1804
|
+
/** 出站消息写入 messages.jsonl(message.send/group.send/thought.put 成功后调用) */
|
|
1805
|
+
appendOutboundJsonl(channelId, text, msgId, encrypt, context, isGroup, msgType = 'text') {
|
|
1806
|
+
try {
|
|
1807
|
+
const sessionsDir = resolvePaths().sessionsDir;
|
|
1808
|
+
const selfId = this.config.aid;
|
|
1809
|
+
const chatDir = chatDirPath(sessionsDir, 'aun', channelId, selfId);
|
|
1810
|
+
const chatmode = context?.metadata?.chatmode;
|
|
1811
|
+
appendMessageLog(chatDir, buildOutboundEntry({
|
|
1812
|
+
from: selfId,
|
|
1813
|
+
to: channelId,
|
|
1814
|
+
chatType: isGroup ? 'group' : 'private',
|
|
1815
|
+
groupId: isGroup ? channelId : null,
|
|
1816
|
+
msgId,
|
|
1817
|
+
content: text,
|
|
1818
|
+
replyTo: null,
|
|
1819
|
+
agent: null,
|
|
1820
|
+
model: null,
|
|
1821
|
+
durationMs: null,
|
|
1822
|
+
encrypt,
|
|
1823
|
+
chatmode,
|
|
1824
|
+
msgType,
|
|
1825
|
+
}));
|
|
1826
|
+
}
|
|
1827
|
+
catch (e) {
|
|
1828
|
+
logger.debug(`${this.logPrefix()} appendOutboundJsonl failed: ${e}`);
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1560
1831
|
/**
|
|
1561
1832
|
* 发送 thought 内容(Proactive 模式可观测)
|
|
1562
1833
|
* 群聊:调用 group.thought.put
|
|
@@ -1583,15 +1854,28 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1583
1854
|
try {
|
|
1584
1855
|
const itemCount = Array.isArray(payload?.items) ? payload.items.length : 0;
|
|
1585
1856
|
const stage = payload?.stage ?? `items=${itemCount}`;
|
|
1857
|
+
// 提取 thought 文本(取最后一项的 text 或 content 字段)
|
|
1858
|
+
const items = payload?.items;
|
|
1859
|
+
let thoughtText;
|
|
1860
|
+
if (Array.isArray(items) && items.length > 0) {
|
|
1861
|
+
const lastItem = items[items.length - 1];
|
|
1862
|
+
thoughtText = lastItem?.text || lastItem?.content || (typeof lastItem === 'string' ? lastItem : undefined);
|
|
1863
|
+
}
|
|
1586
1864
|
if (this.isGroupId(channelId)) {
|
|
1587
1865
|
params.group_id = targetId;
|
|
1588
|
-
await this.callAndTrace('group.thought.put', params);
|
|
1589
|
-
|
|
1866
|
+
const putRes = await this.callAndTrace('group.thought.put', params);
|
|
1867
|
+
const tid = putRes?.thought_id;
|
|
1868
|
+
logger.info(`${this.logPrefix()} thought.put ok group=${targetId} task=${taskId} stage=${stage} encrypt=${encrypt} tid=${tid ?? '?'}`);
|
|
1869
|
+
this.eventBus?.publish?.({ type: 'message:thought-put', agentName: this.config.aid, channelId, taskId, text: thoughtText });
|
|
1870
|
+
// thought jsonl 写入已改为按 LLM 调用次数统计(在 complete 事件处写入),此处不再写
|
|
1590
1871
|
}
|
|
1591
1872
|
else {
|
|
1592
1873
|
params.to = targetId;
|
|
1593
|
-
await this.callAndTrace('message.thought.put', params);
|
|
1594
|
-
|
|
1874
|
+
const putRes = await this.callAndTrace('message.thought.put', params);
|
|
1875
|
+
const tid = putRes?.thought_id;
|
|
1876
|
+
logger.info(`${this.logPrefix()} thought.put ok p2p=${this.peerLabel(targetId)} task=${taskId} stage=${stage} encrypt=${encrypt} tid=${tid ?? '?'}`);
|
|
1877
|
+
this.eventBus?.publish?.({ type: 'message:thought-put', agentName: this.config.aid, channelId, taskId, text: thoughtText });
|
|
1878
|
+
// thought jsonl 写入已改为按 LLM 调用次数统计(在 complete 事件处写入),此处不再写
|
|
1595
1879
|
}
|
|
1596
1880
|
}
|
|
1597
1881
|
catch (e) {
|
|
@@ -1622,8 +1906,9 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1622
1906
|
if (isGroup) {
|
|
1623
1907
|
params.group_id = channelId;
|
|
1624
1908
|
const result = await this.callAndTrace('group.send', params);
|
|
1625
|
-
|
|
1626
|
-
|
|
1909
|
+
const mid = result?.message?.message_id ?? result?.message_id ?? null;
|
|
1910
|
+
logger.info(`${this.logPrefix()} group.send (${payload.type}) ok: group=${channelId} mid=${mid} encrypt=${encrypt}`);
|
|
1911
|
+
return mid;
|
|
1627
1912
|
}
|
|
1628
1913
|
else {
|
|
1629
1914
|
params.to = targetAid;
|
|
@@ -1750,8 +2035,9 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1750
2035
|
params.group_id = channelId;
|
|
1751
2036
|
this.trace('OUT', 'group.send.file', params);
|
|
1752
2037
|
const result = await this.client.call('group.send', params);
|
|
1753
|
-
|
|
1754
|
-
|
|
2038
|
+
const fileMid = result?.message?.message_id ?? result?.message_id;
|
|
2039
|
+
this.trace('OUT', 'group.send.file.ok', { message_id: fileMid });
|
|
2040
|
+
if (!fileMid) {
|
|
1755
2041
|
logger.warn(`${this.logPrefix()} group.send.file returned no message_id: ${JSON.stringify(result)}`);
|
|
1756
2042
|
}
|
|
1757
2043
|
}
|
|
@@ -1777,8 +2063,9 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1777
2063
|
if (isGroup) {
|
|
1778
2064
|
this.trace('OUT', 'group.send.file.fallback', params);
|
|
1779
2065
|
const result = await this.client.call('group.send', params);
|
|
1780
|
-
|
|
1781
|
-
|
|
2066
|
+
const fbMid = result?.message?.message_id ?? result?.message_id;
|
|
2067
|
+
this.trace('OUT', 'group.send.file.fallback.ok', { message_id: fbMid });
|
|
2068
|
+
if (!fbMid) {
|
|
1782
2069
|
logger.warn(`${this.logPrefix()} group.send.file fallback returned no message_id: ${JSON.stringify(result)}`);
|
|
1783
2070
|
}
|
|
1784
2071
|
}
|
|
@@ -1845,35 +2132,20 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1845
2132
|
// to avoid duplicate "已送达" at the sender CLI
|
|
1846
2133
|
this.messageSeqMap.delete(messageId);
|
|
1847
2134
|
}
|
|
1848
|
-
sendProcessingStatus(channelId, status, sessionId, taskId, context) {
|
|
2135
|
+
sendProcessingStatus(channelId, status, sessionId, taskId, context, extraMeta) {
|
|
1849
2136
|
if (status === 'start')
|
|
1850
2137
|
this.sentCount.delete(channelId); // 新任务开始,重置计数
|
|
1851
2138
|
if (!this.client || !this.connected)
|
|
1852
2139
|
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
2140
|
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
2141
|
const stateMap = {
|
|
1872
2142
|
start: 'started',
|
|
1873
2143
|
done: 'completed',
|
|
1874
2144
|
interrupted: 'interrupted',
|
|
1875
2145
|
error: 'error',
|
|
1876
2146
|
timeout: 'timeout',
|
|
2147
|
+
queued: 'queued',
|
|
2148
|
+
progress: 'progress',
|
|
1877
2149
|
};
|
|
1878
2150
|
const statusPayload = {
|
|
1879
2151
|
type: 'status',
|
|
@@ -1881,15 +2153,18 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1881
2153
|
task_id: taskId,
|
|
1882
2154
|
session_id: sessionId,
|
|
1883
2155
|
severity,
|
|
2156
|
+
...(extraMeta && Object.keys(extraMeta).length > 0 && { metadata: extraMeta }),
|
|
1884
2157
|
};
|
|
1885
2158
|
if (context?.threadId)
|
|
1886
2159
|
statusPayload.thread_id = context.threadId;
|
|
2160
|
+
if (context?.peerId)
|
|
2161
|
+
statusPayload.initiator = context.peerId;
|
|
2162
|
+
if (context?.replyToMessageId)
|
|
2163
|
+
statusPayload.ref_message_id = context.replyToMessageId;
|
|
1887
2164
|
const isGroup = this.isGroupId(channelId);
|
|
1888
2165
|
// 私聊 channelId = 对端 AID(不含 device_id)
|
|
1889
2166
|
const statusTargetAid = channelId;
|
|
1890
2167
|
const encryptTarget = isGroup ? channelId : statusTargetAid;
|
|
1891
|
-
// 计算 encrypt 标志(每次调用读最新 peerE2ee 状态,
|
|
1892
|
-
// 这样第二条 send 能受益于第一条触发的 peerE2ee 标记)
|
|
1893
2168
|
const computeEncrypt = () => context?.metadata?.encrypted != null
|
|
1894
2169
|
? !!(context.metadata.encrypted)
|
|
1895
2170
|
: this.shouldEncrypt(encryptTarget);
|
|
@@ -1922,17 +2197,15 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1922
2197
|
});
|
|
1923
2198
|
};
|
|
1924
2199
|
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);
|
|
2200
|
+
sendOne(method, statusPayload, 'status');
|
|
1931
2201
|
this.aidStatsCollector?.recordOutbound(this.config.aid, channelId, JSON.stringify(statusPayload).length, undefined, true);
|
|
1932
2202
|
// 群聊显示 group id 简称,P2P 显示 peer label;从 context.metadata 读取 chatmode
|
|
1933
2203
|
const targetLabel = this.isGroupId(channelId) ? channelId : this.peerLabel(channelId);
|
|
1934
2204
|
const chatmode = context?.metadata?.chatmode ?? '?';
|
|
1935
|
-
|
|
2205
|
+
const initiator = statusPayload.initiator ?? '';
|
|
2206
|
+
const refMsgId = statusPayload.ref_message_id ?? '';
|
|
2207
|
+
const metaStr = statusPayload.metadata ? ` meta=${JSON.stringify(statusPayload.metadata)}` : '';
|
|
2208
|
+
logger.info(`${this.logPrefix()} task.${status} task=${taskId} session=${sessionId} chatmode=${chatmode} target=${targetLabel} initiator=${initiator} ref_msg=${refMsgId}${metaStr}`);
|
|
1936
2209
|
}
|
|
1937
2210
|
sendCustomPayload(channelId, payload) {
|
|
1938
2211
|
if (!this.client || !this.connected)
|
|
@@ -1980,6 +2253,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1980
2253
|
}
|
|
1981
2254
|
this.connected = false;
|
|
1982
2255
|
appendAidEvent({ ts: Date.now(), iso: new Date().toISOString(), event: 'disconnected', aid: this.config.aid, reason: 'intentional' });
|
|
2256
|
+
appendAidLifecycle({ ts: Date.now(), iso: new Date().toISOString(), event: 'disconnected', aid: this.config.aid, reason: 'intentional' });
|
|
1983
2257
|
this.setAidStatus('disabled');
|
|
1984
2258
|
if (this.traceWriter) {
|
|
1985
2259
|
this.traceWriter.close();
|
|
@@ -2159,7 +2433,7 @@ export class AUNChannelPlugin {
|
|
|
2159
2433
|
case 'result.error': {
|
|
2160
2434
|
const sendCtx = { ...(ctx ?? {}) };
|
|
2161
2435
|
if (payload.kind === 'result.text' && payload.isFinal)
|
|
2162
|
-
sendCtx.title = '
|
|
2436
|
+
sendCtx.title = '✅ 最终回复:';
|
|
2163
2437
|
await channel.sendMessage(channelId, payload.text, sendCtx);
|
|
2164
2438
|
return;
|
|
2165
2439
|
}
|
|
@@ -2186,16 +2460,26 @@ export class AUNChannelPlugin {
|
|
|
2186
2460
|
};
|
|
2187
2461
|
if (ctx?.threadId)
|
|
2188
2462
|
aunPayload.thread_id = ctx.threadId;
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2463
|
+
if (envelope.chatmode === 'proactive') {
|
|
2464
|
+
await channel.sendThought(channelId, envelope.taskId, aunPayload, ctx);
|
|
2465
|
+
}
|
|
2466
|
+
else {
|
|
2467
|
+
await Promise.all([
|
|
2468
|
+
channel.sendThought(channelId, envelope.taskId, aunPayload, ctx),
|
|
2469
|
+
channel.sendStructured(channelId, aunPayload, ctx),
|
|
2470
|
+
]);
|
|
2471
|
+
}
|
|
2194
2472
|
return;
|
|
2195
2473
|
}
|
|
2474
|
+
case 'status.progress':
|
|
2475
|
+
channel.sendProcessingStatus(channelId, 'progress', envelope.taskId, envelope.taskId, ctx, payload.metadata);
|
|
2476
|
+
return;
|
|
2196
2477
|
case 'status.started':
|
|
2197
2478
|
channel.sendProcessingStatus(channelId, 'start', envelope.taskId, envelope.taskId, ctx);
|
|
2198
2479
|
return;
|
|
2480
|
+
case 'status.queued':
|
|
2481
|
+
channel.sendProcessingStatus(channelId, 'queued', envelope.taskId, envelope.taskId, ctx);
|
|
2482
|
+
return;
|
|
2199
2483
|
case 'status.completed':
|
|
2200
2484
|
channel.sendProcessingStatus(channelId, 'done', envelope.taskId, envelope.taskId, ctx);
|
|
2201
2485
|
return;
|
|
@@ -2215,7 +2499,6 @@ export class AUNChannelPlugin {
|
|
|
2215
2499
|
const aunCard = {
|
|
2216
2500
|
type: 'action_card',
|
|
2217
2501
|
title: action.title,
|
|
2218
|
-
description: action.body,
|
|
2219
2502
|
actions: action.buttons.map(btn => ({
|
|
2220
2503
|
label: btn.label,
|
|
2221
2504
|
value: btn.key,
|
|
@@ -2223,6 +2506,8 @@ export class AUNChannelPlugin {
|
|
|
2223
2506
|
behavior: 'reply',
|
|
2224
2507
|
})),
|
|
2225
2508
|
};
|
|
2509
|
+
if (action.body)
|
|
2510
|
+
aunCard.description = action.body;
|
|
2226
2511
|
if (ctx?.threadId)
|
|
2227
2512
|
aunCard.thread_id = ctx.threadId;
|
|
2228
2513
|
const msgId = await channel.sendStructured(channelId, aunCard, ctx);
|
|
@@ -2236,7 +2521,6 @@ export class AUNChannelPlugin {
|
|
|
2236
2521
|
const aunCard = {
|
|
2237
2522
|
type: 'action_card',
|
|
2238
2523
|
title: card.title,
|
|
2239
|
-
description: card.body,
|
|
2240
2524
|
actions: card.buttons.map(btn => ({
|
|
2241
2525
|
label: btn.label,
|
|
2242
2526
|
value: btn.command,
|
|
@@ -2244,11 +2528,13 @@ export class AUNChannelPlugin {
|
|
|
2244
2528
|
behavior: 'reply',
|
|
2245
2529
|
})),
|
|
2246
2530
|
};
|
|
2531
|
+
if (card.body)
|
|
2532
|
+
aunCard.description = card.body;
|
|
2247
2533
|
if (ctx?.threadId)
|
|
2248
2534
|
aunCard.thread_id = ctx.threadId;
|
|
2249
2535
|
const msgId = await channel.sendStructured(channelId, aunCard, ctx);
|
|
2250
2536
|
if (msgId) {
|
|
2251
|
-
channel.cardMessageIdMap.set(msgId, { requestId: req.id, isCommandCard: true });
|
|
2537
|
+
channel.cardMessageIdMap.set(msgId, { requestId: req.id, isCommandCard: true, initiatorAid: req.initiatorId });
|
|
2252
2538
|
setTimeout(() => channel.cardMessageIdMap.delete(msgId), 20 * 60 * 1000);
|
|
2253
2539
|
}
|
|
2254
2540
|
}
|
|
@@ -2323,6 +2609,7 @@ export class AUNChannelPlugin {
|
|
|
2323
2609
|
chatType: opts.chatType || 'private',
|
|
2324
2610
|
peerId: opts.peerId || '',
|
|
2325
2611
|
peerName: opts.peerName,
|
|
2612
|
+
peerType: opts.peerType,
|
|
2326
2613
|
messageId: opts.messageId,
|
|
2327
2614
|
mentions: opts.mentions,
|
|
2328
2615
|
threadId: opts.threadId,
|
|
@@ -2345,12 +2632,6 @@ export class AUNChannelPlugin {
|
|
|
2345
2632
|
});
|
|
2346
2633
|
});
|
|
2347
2634
|
}
|
|
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
2635
|
if (typeof channel.setDispatchModeResolver === 'function') {
|
|
2355
2636
|
channel.setDispatchModeResolver(async (channelId) => {
|
|
2356
2637
|
const session = await ctx.sessionManager.getActiveSession(adapter.channelName, channelId);
|