evolclaw 2.8.0 → 2.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/templates.js +122 -0
- package/dist/channels/aun-ops.js +275 -0
- package/dist/channels/aun.js +206 -103
- package/dist/channels/qqbot.js +1 -1
- package/dist/channels/wechat.js +1 -1
- package/dist/cli.js +676 -20
- package/dist/config.js +94 -22
- package/dist/core/agent-registry.js +450 -0
- package/dist/core/command-handler.js +422 -255
- package/dist/core/evolagent-registry.js +503 -0
- package/dist/core/evolagent-schema.js +72 -0
- package/dist/core/evolagent.js +315 -0
- package/dist/core/message/message-bridge.js +23 -3
- package/dist/core/message/message-processor.js +56 -11
- package/dist/core/message/message-queue.js +59 -4
- package/dist/core/reload-hooks.js +87 -0
- package/dist/index.js +119 -20
- package/dist/ipc.js +47 -0
- package/dist/paths.js +2 -0
- package/dist/types.js +2 -0
- package/dist/utils/init-channel.js +91 -221
- package/dist/utils/init.js +18 -42
- package/dist/utils/logger.js +58 -2
- package/dist/utils/reload-hooks.js +87 -0
- package/dist/utils/rich-content-renderer.js +33 -0
- package/dist/utils/stats-collector.js +15 -10
- package/evolclaw-install-aun.md +48 -7
- package/package.json +1 -1
package/dist/channels/aun.js
CHANGED
|
@@ -35,16 +35,45 @@ export class AUNChannel {
|
|
|
35
35
|
recallHandler;
|
|
36
36
|
connected = false;
|
|
37
37
|
traceStream = null;
|
|
38
|
-
|
|
38
|
+
traceHourTag = ''; // 当前 trace 文件对应的小时标识 (YYYYMMDD-HH)
|
|
39
39
|
trace(dir, event, data) {
|
|
40
40
|
if (!this.config.aunTrace)
|
|
41
41
|
return;
|
|
42
42
|
this.rotateTraceIfNeeded();
|
|
43
43
|
if (!this.traceStream)
|
|
44
44
|
return;
|
|
45
|
-
|
|
45
|
+
// 自动从 data 推断顶层字段(self_aid / peer_aid / group_id / task_id / chatmode),
|
|
46
|
+
// 便于 jq 过滤:`jq 'select(.task_id == "task-xxx")'`
|
|
47
|
+
const d = (data && typeof data === 'object') ? data : {};
|
|
48
|
+
const payload = d.payload ?? {};
|
|
49
|
+
const topContext = {
|
|
50
|
+
self_aid: this._aid ?? this.config.aid,
|
|
51
|
+
};
|
|
52
|
+
// peer / group 识别
|
|
53
|
+
const peerAid = d.to ?? d.from ?? d.sender_aid ?? payload.to;
|
|
54
|
+
if (peerAid)
|
|
55
|
+
topContext.peer_aid = peerAid;
|
|
56
|
+
const groupId = d.group_id ?? payload.group_id;
|
|
57
|
+
if (groupId)
|
|
58
|
+
topContext.group_id = groupId;
|
|
59
|
+
// task_id / chatmode(message.send / thought.put / status 都可能有)
|
|
60
|
+
const taskId = payload.task_id ?? d.context?.id ?? d.data?.task_id;
|
|
61
|
+
if (taskId)
|
|
62
|
+
topContext.task_id = taskId;
|
|
63
|
+
const chatmode = payload.chatmode;
|
|
64
|
+
if (chatmode)
|
|
65
|
+
topContext.chatmode = chatmode;
|
|
66
|
+
const line = JSON.stringify({ ts: localTimestamp(), dir, event, ...topContext, data });
|
|
46
67
|
this.traceStream.write(line + '\n');
|
|
47
68
|
}
|
|
69
|
+
/** 日志前缀(含 self aid 简称,多实例可识别) */
|
|
70
|
+
logPrefix() {
|
|
71
|
+
const aid = this._aid ?? this.config.aid;
|
|
72
|
+
if (!aid)
|
|
73
|
+
return '[AUN]';
|
|
74
|
+
const short = aid.split('.')[0] || aid;
|
|
75
|
+
return `[AUN ${short}]`;
|
|
76
|
+
}
|
|
48
77
|
/**
|
|
49
78
|
* 统一的 RPC 调用包装:自动记录 OUT 发送、.ok 结果、.error 错误(含 trace + evolclaw.log 失败日志)。
|
|
50
79
|
* 所有 client.call() 都应通过此方法调用,保证 aun-trace 里每个 OUT 调用都有"发+收/错"成对记录。
|
|
@@ -68,22 +97,43 @@ export class AUNChannel {
|
|
|
68
97
|
code: e?.code,
|
|
69
98
|
name: e?.name,
|
|
70
99
|
});
|
|
71
|
-
logger.warn(
|
|
100
|
+
logger.warn(`${this.logPrefix()} rpc ${method} failed: ${e?.name ?? ''}(${e?.code ?? ''}) ${e?.message ?? e}`);
|
|
72
101
|
throw e;
|
|
73
102
|
}
|
|
74
103
|
}
|
|
104
|
+
static AUN_TRACE_RE = /^aun-\d{8}-\d{2}\.log$/;
|
|
105
|
+
static AUN_RETAIN_HOURS = 12;
|
|
75
106
|
rotateTraceIfNeeded() {
|
|
76
107
|
const d = new Date();
|
|
77
|
-
const
|
|
78
|
-
|
|
108
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
109
|
+
const hourTag = `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}`;
|
|
110
|
+
if (this.traceHourTag === hourTag && this.traceStream)
|
|
79
111
|
return;
|
|
80
112
|
if (this.traceStream) {
|
|
81
113
|
this.traceStream.end();
|
|
82
114
|
this.traceStream = null;
|
|
83
115
|
}
|
|
84
|
-
this.
|
|
85
|
-
const logPath = path.join(resolvePaths().logs, `aun-${
|
|
116
|
+
this.traceHourTag = hourTag;
|
|
117
|
+
const logPath = path.join(resolvePaths().logs, `aun-${hourTag}.log`);
|
|
86
118
|
this.traceStream = fs.createWriteStream(logPath, { flags: 'a' });
|
|
119
|
+
this.cleanupOldTraceLogs();
|
|
120
|
+
}
|
|
121
|
+
cleanupOldTraceLogs() {
|
|
122
|
+
const logsDir = resolvePaths().logs;
|
|
123
|
+
const cutoff = Date.now() - AUNChannel.AUN_RETAIN_HOURS * 60 * 60 * 1000;
|
|
124
|
+
try {
|
|
125
|
+
for (const name of fs.readdirSync(logsDir)) {
|
|
126
|
+
if (!AUNChannel.AUN_TRACE_RE.test(name))
|
|
127
|
+
continue;
|
|
128
|
+
try {
|
|
129
|
+
const full = path.join(logsDir, name);
|
|
130
|
+
if (fs.statSync(full).mtimeMs < cutoff)
|
|
131
|
+
fs.unlinkSync(full);
|
|
132
|
+
}
|
|
133
|
+
catch { }
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
catch { }
|
|
87
137
|
}
|
|
88
138
|
/** 判断 channelId 是否为群组 ID
|
|
89
139
|
* - 新格式:group.{issuer}/{group_no|group_name}
|
|
@@ -104,6 +154,18 @@ export class AUNChannel {
|
|
|
104
154
|
return undefined;
|
|
105
155
|
return trimmed.split('.')[0] || trimmed;
|
|
106
156
|
}
|
|
157
|
+
/** 同步获取对端显示名(仅从缓存,不触发网络请求)。用于日志中补充对端标识。
|
|
158
|
+
* 返回 `shortAid(displayName)` 或 `shortAid`,若 aid 为空返回 '?'
|
|
159
|
+
*/
|
|
160
|
+
peerLabel(aid) {
|
|
161
|
+
if (!aid)
|
|
162
|
+
return '?';
|
|
163
|
+
const bareAid = aid.includes(':') ? aid.substring(0, aid.indexOf(':')) : aid;
|
|
164
|
+
const short = this.getShortAid(bareAid) ?? bareAid;
|
|
165
|
+
const cached = this.peerInfoCache.get(bareAid);
|
|
166
|
+
const name = cached?.name;
|
|
167
|
+
return name && name !== short ? `${short}(${name})` : short;
|
|
168
|
+
}
|
|
107
169
|
extractTextPayload(payload) {
|
|
108
170
|
if (typeof payload === 'string')
|
|
109
171
|
return payload;
|
|
@@ -150,6 +212,20 @@ export class AUNChannel {
|
|
|
150
212
|
}
|
|
151
213
|
return false;
|
|
152
214
|
}
|
|
215
|
+
/** 从正文提取 @aid(AID 格式:至少三段域名),用于出站填充 payload.mentions */
|
|
216
|
+
static MENTION_AID_RE = /@([a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?){2,})/g;
|
|
217
|
+
extractMentionAidsFromText(text) {
|
|
218
|
+
const out = [];
|
|
219
|
+
const seen = new Set();
|
|
220
|
+
for (const m of text.matchAll(AUNChannel.MENTION_AID_RE)) {
|
|
221
|
+
const aid = m[1];
|
|
222
|
+
if (!seen.has(aid)) {
|
|
223
|
+
seen.add(aid);
|
|
224
|
+
out.push(aid);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return out;
|
|
228
|
+
}
|
|
153
229
|
buildGroupReplyContext(taskId, senderAid, encrypted) {
|
|
154
230
|
const replyContext = { metadata: { encrypted } };
|
|
155
231
|
if (taskId)
|
|
@@ -160,7 +236,7 @@ export class AUNChannel {
|
|
|
160
236
|
acknowledgeImmediately(messageId, seq) {
|
|
161
237
|
if (seq != null && this.client) {
|
|
162
238
|
this.client.call('message.ack', { seq }).catch(e => {
|
|
163
|
-
logger.debug(
|
|
239
|
+
logger.debug(`${this.logPrefix()} Immediate ack failed: ${e}`);
|
|
164
240
|
});
|
|
165
241
|
}
|
|
166
242
|
if (messageId)
|
|
@@ -207,7 +283,7 @@ export class AUNChannel {
|
|
|
207
283
|
this.config = config;
|
|
208
284
|
if (config.aunTrace) {
|
|
209
285
|
this.rotateTraceIfNeeded();
|
|
210
|
-
logger.info(
|
|
286
|
+
logger.info(`${this.logPrefix()} Trace logging enabled (hourly rotation, 12h retention): ${resolvePaths().logs}/aun-YYYYMMDD-HH.log`);
|
|
211
287
|
}
|
|
212
288
|
}
|
|
213
289
|
async connect() {
|
|
@@ -240,17 +316,17 @@ export class AUNChannel {
|
|
|
240
316
|
try {
|
|
241
317
|
const discovery = new GatewayDiscovery({});
|
|
242
318
|
gateway = await discovery.discover(wellKnownUrl);
|
|
243
|
-
logger.info(
|
|
319
|
+
logger.info(`${this.logPrefix()} Gateway discovered: ${gateway}`);
|
|
244
320
|
}
|
|
245
321
|
catch (e) {
|
|
246
|
-
logger.warn(
|
|
322
|
+
logger.warn(`${this.logPrefix()} Well-known discovery failed (${e}), no fallback available`);
|
|
247
323
|
}
|
|
248
324
|
}
|
|
249
325
|
if (!gateway) {
|
|
250
|
-
logger.error(
|
|
326
|
+
logger.error(`${this.logPrefix()} Cannot resolve gateway URL from AID`);
|
|
251
327
|
throw new Error('Cannot resolve gateway URL from AID');
|
|
252
328
|
}
|
|
253
|
-
logger.info(
|
|
329
|
+
logger.info(`${this.logPrefix()} Initializing: aid=${aidName}, gateway=${gateway}, aun_path=${aunPath}`);
|
|
254
330
|
// Create client with FileSecretStore (AES-256-GCM)
|
|
255
331
|
// 不传 encryption_seed 时,SDK 自动从 {aun_path}/.seed 文件派生密钥(与 aun_cli.py 对齐)
|
|
256
332
|
const rootCaPath = path.join(aunPath, 'CA', 'root', 'root.crt');
|
|
@@ -266,14 +342,14 @@ export class AUNChannel {
|
|
|
266
342
|
this.trace('IN', 'message.received', data);
|
|
267
343
|
const kind = (data && typeof data === 'object') ? data.kind ?? '' : '';
|
|
268
344
|
const keys = (data && typeof data === 'object') ? Object.keys(data).join(',') : typeof data;
|
|
269
|
-
logger.debug(
|
|
345
|
+
logger.debug(`${this.logPrefix()}[DIAG] message.received: kind=${kind} keys=${keys}`);
|
|
270
346
|
this.handleIncomingPrivateMessage(data);
|
|
271
347
|
});
|
|
272
348
|
this.client.on('group.message_created', (data) => {
|
|
273
349
|
this.trace('IN', 'group.message_created', data);
|
|
274
350
|
const gid = (data && typeof data === 'object') ? data.group_id ?? '' : '';
|
|
275
351
|
const sender = (data && typeof data === 'object') ? data.sender_aid ?? '' : '';
|
|
276
|
-
logger.debug(
|
|
352
|
+
logger.debug(`${this.logPrefix()}[DIAG] group.message_created: group_id=${gid} sender=${sender}`);
|
|
277
353
|
this.handleIncomingGroupMessage(data);
|
|
278
354
|
});
|
|
279
355
|
this.client.on('connection.state', (data) => {
|
|
@@ -287,7 +363,7 @@ export class AUNChannel {
|
|
|
287
363
|
if (Array.isArray(ids)) {
|
|
288
364
|
for (const id of ids) {
|
|
289
365
|
if (typeof id === 'string') {
|
|
290
|
-
logger.info(
|
|
366
|
+
logger.info(`${this.logPrefix()} Message recalled: ${id}`);
|
|
291
367
|
this.recallHandler?.(id);
|
|
292
368
|
}
|
|
293
369
|
}
|
|
@@ -297,12 +373,12 @@ export class AUNChannel {
|
|
|
297
373
|
this.client.on('message.undecryptable', (data) => {
|
|
298
374
|
this.trace('IN', 'message.undecryptable', data);
|
|
299
375
|
const d = data;
|
|
300
|
-
logger.warn(
|
|
376
|
+
logger.warn(`${this.logPrefix()} Message undecryptable: from=${d.from} mid=${d.message_id} err=${d._decrypt_error}`);
|
|
301
377
|
});
|
|
302
378
|
this.client.on('group.message_undecryptable', (data) => {
|
|
303
379
|
this.trace('IN', 'group.message_undecryptable', data);
|
|
304
380
|
const d = data;
|
|
305
|
-
logger.warn(
|
|
381
|
+
logger.warn(`${this.logPrefix()} Group message undecryptable: group=${d.group_id} from=${d.from} mid=${d.message_id} err=${d._decrypt_error}`);
|
|
306
382
|
});
|
|
307
383
|
// Authenticate
|
|
308
384
|
// Workaround: SDK 0.3.x _loadIdentityOrRaise doesn't set identity.aid from requested aid,
|
|
@@ -319,7 +395,7 @@ export class AUNChannel {
|
|
|
319
395
|
}
|
|
320
396
|
let accessToken;
|
|
321
397
|
try {
|
|
322
|
-
logger.info(
|
|
398
|
+
logger.info(`${this.logPrefix()} Authenticating as ${aidName}...`);
|
|
323
399
|
this.trace('OUT', 'auth.authenticate', { aid: aidName });
|
|
324
400
|
const auth = await this.client.auth.authenticate(aidName ? { aid: aidName } : undefined);
|
|
325
401
|
this.trace('OUT', 'auth.authenticate.ok', { aid: auth.aid, gateway: auth.gateway, hasToken: !!auth.access_token });
|
|
@@ -327,23 +403,23 @@ export class AUNChannel {
|
|
|
327
403
|
accessToken = auth.access_token;
|
|
328
404
|
const resolvedGateway = auth.gateway || gateway;
|
|
329
405
|
this.client._gatewayUrl = resolvedGateway;
|
|
330
|
-
logger.info(
|
|
406
|
+
logger.info(`${this.logPrefix()} Authenticated as ${auth.aid ?? '?'}, gateway=${resolvedGateway}`);
|
|
331
407
|
}
|
|
332
408
|
catch (e) {
|
|
333
409
|
const errMsg = e.message || String(e);
|
|
334
410
|
const errName = e.constructor?.name || 'Error';
|
|
335
411
|
this.trace('OUT', 'auth.authenticate.error', { error: errMsg, name: errName });
|
|
336
|
-
logger.error(
|
|
412
|
+
logger.error(`${this.logPrefix()} Authentication failed (${errName}): ${errMsg}`);
|
|
337
413
|
if (e.stack)
|
|
338
|
-
logger.debug(
|
|
414
|
+
logger.debug(`${this.logPrefix()} Auth stack: ${e.stack}`);
|
|
339
415
|
// Fallback: try direct token from env/config (legacy)
|
|
340
416
|
accessToken = this.config.accessToken || process.env.AUN_ACCESS_TOKEN || '';
|
|
341
417
|
if (!accessToken) {
|
|
342
|
-
logger.error(
|
|
418
|
+
logger.error(`${this.logPrefix()} No accessToken fallback available, scheduling retry`);
|
|
343
419
|
this.scheduleReconnect();
|
|
344
420
|
throw new Error('Authentication failed and no accessToken fallback available');
|
|
345
421
|
}
|
|
346
|
-
logger.warn(
|
|
422
|
+
logger.warn(`${this.logPrefix()} Using accessToken fallback`);
|
|
347
423
|
}
|
|
348
424
|
// Connect (SDK auto_reconnect handles transient failures)
|
|
349
425
|
try {
|
|
@@ -364,16 +440,16 @@ export class AUNChannel {
|
|
|
364
440
|
const cert = clientAny._keystore?.loadCert?.(aidName);
|
|
365
441
|
if (cert) {
|
|
366
442
|
clientAny._identity.cert = cert;
|
|
367
|
-
logger.info(
|
|
443
|
+
logger.info(`${this.logPrefix()} Backfilled identity.cert from keystore for e2ee fingerprint`);
|
|
368
444
|
}
|
|
369
445
|
}
|
|
370
|
-
logger.info(
|
|
446
|
+
logger.info(`${this.logPrefix()} Connected as ${this._aid}`);
|
|
371
447
|
// Send welcome message to owner after first connection
|
|
372
448
|
await this.sendWelcomeMessage();
|
|
373
449
|
}
|
|
374
450
|
catch (e) {
|
|
375
451
|
this.trace('OUT', 'client.connect.error', { error: String(e) });
|
|
376
|
-
logger.error(
|
|
452
|
+
logger.error(`${this.logPrefix()} Connection failed: ${e}`);
|
|
377
453
|
this.scheduleReconnect();
|
|
378
454
|
throw e;
|
|
379
455
|
}
|
|
@@ -382,7 +458,7 @@ export class AUNChannel {
|
|
|
382
458
|
try {
|
|
383
459
|
const owner = this.config.owner;
|
|
384
460
|
if (!owner) {
|
|
385
|
-
logger.info(
|
|
461
|
+
logger.info(`${this.logPrefix()} No owner configured, skipping welcome message`);
|
|
386
462
|
return;
|
|
387
463
|
}
|
|
388
464
|
// Check agent.md initialized field
|
|
@@ -390,25 +466,25 @@ export class AUNChannel {
|
|
|
390
466
|
const aidName = aid.startsWith('@') ? aid.slice(1) : aid;
|
|
391
467
|
const agentMdPath = path.join(os.homedir(), '.aun', 'AIDs', aidName, 'agent.md');
|
|
392
468
|
if (!fs.existsSync(agentMdPath)) {
|
|
393
|
-
logger.warn(
|
|
469
|
+
logger.warn(`${this.logPrefix()} agent.md not found, skipping welcome message`);
|
|
394
470
|
return;
|
|
395
471
|
}
|
|
396
472
|
const agentMdContent = fs.readFileSync(agentMdPath, 'utf-8');
|
|
397
473
|
const match = agentMdContent.match(/^---\n([\s\S]*?)\n---/);
|
|
398
474
|
if (!match) {
|
|
399
|
-
logger.warn(
|
|
475
|
+
logger.warn(`${this.logPrefix()} agent.md frontmatter not found`);
|
|
400
476
|
return;
|
|
401
477
|
}
|
|
402
478
|
const frontmatter = match[1];
|
|
403
479
|
const initializedMatch = frontmatter.match(/^initialized:\s*(true|false)/m);
|
|
404
480
|
if (!initializedMatch || initializedMatch[1] === 'true') {
|
|
405
|
-
logger.info(
|
|
481
|
+
logger.info(`${this.logPrefix()} Agent already initialized, skipping welcome message`);
|
|
406
482
|
return;
|
|
407
483
|
}
|
|
408
484
|
// Fetch owner's agent.md to derive name and validate type
|
|
409
485
|
const ownerInfo = await this.fetchPeerInfo(owner);
|
|
410
486
|
if (ownerInfo.type !== null && ownerInfo.type !== 'human') {
|
|
411
|
-
logger.warn(
|
|
487
|
+
logger.warn(`${this.logPrefix()} Owner ${owner} type is "${ownerInfo.type}" (not human). Consider using a human AID as owner.`);
|
|
412
488
|
}
|
|
413
489
|
// Name: prefer existing agent.md name if user has customized it,
|
|
414
490
|
// otherwise generate "{ownerName}的Evol助手 ({aidLabel})" for disambiguation
|
|
@@ -452,14 +528,14 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
452
528
|
`;
|
|
453
529
|
// Write locally
|
|
454
530
|
fs.writeFileSync(agentMdPath, newAgentMd, 'utf-8');
|
|
455
|
-
logger.info(
|
|
531
|
+
logger.info(`${this.logPrefix()} Updated agent.md with initialized=true`);
|
|
456
532
|
// Publish to AUN network via auth.uploadAgentMd
|
|
457
533
|
try {
|
|
458
534
|
await this.client.auth.uploadAgentMd(newAgentMd);
|
|
459
|
-
logger.info(
|
|
535
|
+
logger.info(`${this.logPrefix()} Published agent.md to AUN network`);
|
|
460
536
|
}
|
|
461
537
|
catch (e) {
|
|
462
|
-
logger.warn(
|
|
538
|
+
logger.warn(`${this.logPrefix()} Failed to publish agent.md: ${e}`);
|
|
463
539
|
}
|
|
464
540
|
// Send welcome message
|
|
465
541
|
const welcomeText = `🎉 欢迎使用 EvolClaw!
|
|
@@ -487,7 +563,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
487
563
|
// pull if the initial E2EE push still arrives before the cert resolves.
|
|
488
564
|
await new Promise(resolve => setTimeout(resolve, 3000));
|
|
489
565
|
if (!this.client) {
|
|
490
|
-
logger.warn(
|
|
566
|
+
logger.warn(`${this.logPrefix()} Client disconnected before welcome message could be sent`);
|
|
491
567
|
return;
|
|
492
568
|
}
|
|
493
569
|
await this.callAndTrace('message.send', {
|
|
@@ -496,10 +572,10 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
496
572
|
encrypt: true,
|
|
497
573
|
persist_required: true,
|
|
498
574
|
});
|
|
499
|
-
logger.info(
|
|
575
|
+
logger.info(`${this.logPrefix()} Welcome message sent to owner: ${owner}`);
|
|
500
576
|
}
|
|
501
577
|
catch (e) {
|
|
502
|
-
logger.warn(
|
|
578
|
+
logger.warn(`${this.logPrefix()} Failed to send welcome message: ${e}`);
|
|
503
579
|
}
|
|
504
580
|
}
|
|
505
581
|
// ── Event handlers ──────────────────────────────────────────
|
|
@@ -508,7 +584,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
508
584
|
const objectKey = att.object_key;
|
|
509
585
|
const filename = att.filename || objectKey.split('/').pop() || 'unknown';
|
|
510
586
|
if (!objectKey) {
|
|
511
|
-
logger.warn(
|
|
587
|
+
logger.warn(`${this.logPrefix()} Attachment missing object_key, skipping`);
|
|
512
588
|
return null;
|
|
513
589
|
}
|
|
514
590
|
let downloadUrl;
|
|
@@ -519,32 +595,32 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
519
595
|
});
|
|
520
596
|
downloadUrl = ticket.download_url || '';
|
|
521
597
|
if (!downloadUrl) {
|
|
522
|
-
logger.warn(
|
|
598
|
+
logger.warn(`${this.logPrefix()} No download_url for attachment: ${filename}`);
|
|
523
599
|
return null;
|
|
524
600
|
}
|
|
525
601
|
}
|
|
526
602
|
catch (e) {
|
|
527
|
-
logger.warn(
|
|
603
|
+
logger.warn(`${this.logPrefix()} create_download_ticket failed for ${filename}: ${e}`);
|
|
528
604
|
return null;
|
|
529
605
|
}
|
|
530
606
|
let buffer;
|
|
531
607
|
try {
|
|
532
608
|
const res = await fetch(downloadUrl);
|
|
533
609
|
if (!res.ok) {
|
|
534
|
-
logger.warn(
|
|
610
|
+
logger.warn(`${this.logPrefix()} Download failed for ${filename}: HTTP ${res.status}`);
|
|
535
611
|
return null;
|
|
536
612
|
}
|
|
537
613
|
buffer = Buffer.from(await res.arrayBuffer());
|
|
538
614
|
}
|
|
539
615
|
catch (e) {
|
|
540
|
-
logger.warn(
|
|
616
|
+
logger.warn(`${this.logPrefix()} Download error for ${filename}: ${e}`);
|
|
541
617
|
return null;
|
|
542
618
|
}
|
|
543
619
|
if (att.sha256) {
|
|
544
620
|
const { createHash } = await import('node:crypto');
|
|
545
621
|
const actual = createHash('sha256').update(buffer).digest('hex');
|
|
546
622
|
if (actual !== att.sha256) {
|
|
547
|
-
logger.warn(
|
|
623
|
+
logger.warn(`${this.logPrefix()} SHA256 mismatch for ${filename}: expected ${att.sha256.slice(0, 8)}… got ${actual.slice(0, 8)}…`);
|
|
548
624
|
return null;
|
|
549
625
|
}
|
|
550
626
|
}
|
|
@@ -553,11 +629,11 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
553
629
|
: process.cwd();
|
|
554
630
|
try {
|
|
555
631
|
const result = saveToUploads(buffer, filename, projectPath);
|
|
556
|
-
logger.info(
|
|
632
|
+
logger.info(`${this.logPrefix()} Saved attachment: ${result.filePath} (${result.size} bytes)`);
|
|
557
633
|
return result.filePath;
|
|
558
634
|
}
|
|
559
635
|
catch (e) {
|
|
560
|
-
logger.warn(
|
|
636
|
+
logger.warn(`${this.logPrefix()} saveToUploads failed for ${filename}: ${e}`);
|
|
561
637
|
return null;
|
|
562
638
|
}
|
|
563
639
|
}
|
|
@@ -576,7 +652,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
576
652
|
const msgChatId = typeof payload === 'object' && payload !== null && payload.chat_id;
|
|
577
653
|
if (this._aid && fromAid === this._aid && (!msgChatId || !this._chatId || msgChatId !== this._chatId)) {
|
|
578
654
|
this.acknowledgeImmediately(messageId, seq);
|
|
579
|
-
logger.debug(
|
|
655
|
+
logger.debug(`${this.logPrefix()} P2P dropped: echo from self (from=${fromAid} mid=${messageId})`);
|
|
580
656
|
return;
|
|
581
657
|
}
|
|
582
658
|
// 记录入站消息加密状态,透传到出站 ReplyContext
|
|
@@ -618,7 +694,10 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
618
694
|
const peerInfo = await this.fetchPeerInfo(fromAid);
|
|
619
695
|
const shortAid = this.getShortAid(fromAid);
|
|
620
696
|
const displayName = peerInfo.name || shortAid;
|
|
621
|
-
|
|
697
|
+
// 详细 dispatch 决策日志:记录消息为何被路由到 agent
|
|
698
|
+
const p2pPayloadType = (payload && typeof payload === 'object') ? payload.type ?? '' : '';
|
|
699
|
+
logger.info(`${this.logPrefix()} P2P dispatch decision: mid=${messageId} from=${shortAid}(${displayName}) peerType=${peerInfo.type || 'unknown'} payloadType=${p2pPayloadType} chatId=${chatId} encrypt=${msgEncrypted} textPreview=${JSON.stringify(text.slice(0, 80))}`);
|
|
700
|
+
logger.info(`${this.logPrefix()} P2P dispatched: from=${shortAid}(${displayName}) mid=${messageId} encrypt=${msgEncrypted} text=${finalText.slice(0, 60)}`);
|
|
622
701
|
const replyContext = { metadata: { encrypted: msgEncrypted } };
|
|
623
702
|
if (taskId)
|
|
624
703
|
replyContext.threadId = taskId;
|
|
@@ -651,15 +730,15 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
651
730
|
const payloadMentions = Array.isArray(payload?.mentions)
|
|
652
731
|
? payload.mentions.filter((m) => typeof m === 'string')
|
|
653
732
|
: [];
|
|
654
|
-
logger.debug(
|
|
733
|
+
logger.debug(`${this.logPrefix()}[DIAG-GRP] full_msg=${JSON.stringify(msg).substring(0, 500)}`);
|
|
655
734
|
if (!groupId || !senderAid) {
|
|
656
735
|
this.acknowledgeImmediately(messageId, seq);
|
|
657
|
-
logger.debug(
|
|
736
|
+
logger.debug(`${this.logPrefix()} Group dropped: missing groupId or senderAid (mid=${messageId})`);
|
|
658
737
|
return;
|
|
659
738
|
}
|
|
660
739
|
if (this._aid && senderAid === this._aid) {
|
|
661
740
|
this.acknowledgeImmediately(messageId, seq);
|
|
662
|
-
logger.debug(
|
|
741
|
+
logger.debug(`${this.logPrefix()} Group dropped: own message (group=${groupId} mid=${messageId})`);
|
|
663
742
|
return;
|
|
664
743
|
}
|
|
665
744
|
// ── proactive 模式入站白名单 ──
|
|
@@ -670,17 +749,18 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
670
749
|
const payloadType = payloadObj?.type ?? '';
|
|
671
750
|
if (!AUNChannel.PROACTIVE_ALLOW_TYPES.has(payloadType)) {
|
|
672
751
|
this.acknowledgeImmediately(messageId, seq);
|
|
673
|
-
logger.
|
|
752
|
+
logger.info(`${this.logPrefix()} Group dropped (proactive deny): type=${payloadType} group=${groupId} sender=${senderAid} mid=${messageId}`);
|
|
674
753
|
return;
|
|
675
754
|
}
|
|
676
755
|
const rawText = typeof payloadObj?.text === 'string' ? payloadObj.text : '';
|
|
677
756
|
const rawMentions = Array.isArray(payloadObj?.mentions) ? payloadObj.mentions : [];
|
|
678
757
|
const mentionAids = this.extractMentionAids(rawMentions);
|
|
679
758
|
const mentionsSelf = !!this._aid && (this.hasExplicitMention(rawText, this._aid) || mentionAids.includes(this._aid));
|
|
680
|
-
|
|
759
|
+
// @all 仅认结构化 mentions(payload.mentions),不扫描正文 — 避免引述性 "@all" 误判
|
|
760
|
+
const mentionsAll = this.hasMentionAll(rawMentions);
|
|
681
761
|
if (!mentionsSelf && !mentionsAll) {
|
|
682
762
|
this.acknowledgeImmediately(messageId, seq);
|
|
683
|
-
logger.
|
|
763
|
+
logger.info(`${this.logPrefix()} Group dropped (proactive whitelist): type=${payloadType} group=${groupId} sender=${senderAid} mid=${messageId} textPreview=${JSON.stringify(rawText.slice(0, 80))}`);
|
|
684
764
|
return;
|
|
685
765
|
}
|
|
686
766
|
}
|
|
@@ -694,11 +774,12 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
694
774
|
const mentionedSelf = this._aid
|
|
695
775
|
? (this.hasExplicitMention(text, this._aid) || payloadMentions.includes(this._aid))
|
|
696
776
|
: false;
|
|
697
|
-
|
|
777
|
+
// @all 仅认结构化 mentions(payload.mentions),不扫描正文 — 避免引述性 "@all" 误判
|
|
778
|
+
const mentionedAll = payloadMentions.includes('all');
|
|
698
779
|
// In mention mode, only respond when explicitly mentioned; in broadcast mode, respond to all
|
|
699
780
|
if (dispatchMode === 'mention' && !mentionedSelf && !mentionedAll) {
|
|
700
781
|
this.acknowledgeImmediately(messageId, seq);
|
|
701
|
-
logger.
|
|
782
|
+
logger.info(`${this.logPrefix()} Group dropped: unmentioned in mention-mode (group=${groupId} sender=${senderAid} mid=${messageId} textPreview=${JSON.stringify(text.slice(0, 80))})`);
|
|
702
783
|
return;
|
|
703
784
|
}
|
|
704
785
|
const strippedText = this.stripSelfMentionIfOnly(text, this._aid);
|
|
@@ -710,7 +791,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
710
791
|
// Allow through if there's text OR attachments; both-empty messages are silently dropped
|
|
711
792
|
if (!strippedText && !hasAttachments) {
|
|
712
793
|
this.acknowledgeImmediately(messageId, seq);
|
|
713
|
-
logger.debug(
|
|
794
|
+
logger.debug(`${this.logPrefix()} Group dropped: empty text and no attachments (group=${groupId} sender=${senderAid} mid=${messageId})`);
|
|
714
795
|
return;
|
|
715
796
|
}
|
|
716
797
|
const mentions = mentionedAll
|
|
@@ -739,7 +820,19 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
739
820
|
const peerInfo = await this.fetchPeerInfo(senderAid);
|
|
740
821
|
const shortAid = this.getShortAid(senderAid);
|
|
741
822
|
const displayName = peerInfo.name || shortAid;
|
|
742
|
-
|
|
823
|
+
// 详细 dispatch 决策日志:记录消息为何被路由到 agent
|
|
824
|
+
const payloadType = (payload && typeof payload === 'object') ? payload.type ?? '' : '';
|
|
825
|
+
const textMentionSelf = this._aid ? this.hasExplicitMention(text, this._aid) : false;
|
|
826
|
+
const textMentionAll = this.hasExplicitMention(text, 'all');
|
|
827
|
+
const structMentionSelf = this._aid ? payloadMentions.includes(this._aid) : false;
|
|
828
|
+
const structMentionAll = payloadMentions.includes('all');
|
|
829
|
+
const reason = mentionedAll
|
|
830
|
+
? 'mention.all(struct)'
|
|
831
|
+
: mentionedSelf
|
|
832
|
+
? (structMentionSelf ? 'mention.self(struct)' : 'mention.self(text)')
|
|
833
|
+
: `${dispatchMode}.no-mention`;
|
|
834
|
+
logger.info(`${this.logPrefix()} Group dispatch decision: mid=${messageId} group=${groupId} sender=${shortAid}(${displayName}) peerType=${peerInfo.type || 'unknown'} 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))}`);
|
|
835
|
+
logger.info(`${this.logPrefix()} Group dispatched: group=${groupId} sender=${shortAid}(${displayName}) mode=${dispatchMode} mid=${messageId} text=${finalText.slice(0, 60)}`);
|
|
743
836
|
this.dispatchMessage({
|
|
744
837
|
channelId: groupId,
|
|
745
838
|
userId: senderAid,
|
|
@@ -787,7 +880,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
787
880
|
mentions: mentionObjects,
|
|
788
881
|
replyContext,
|
|
789
882
|
}).catch(err => {
|
|
790
|
-
logger.error(
|
|
883
|
+
logger.error(`${this.logPrefix()} Message handler error:`, err);
|
|
791
884
|
});
|
|
792
885
|
}
|
|
793
886
|
handleConnectionState(data) {
|
|
@@ -800,14 +893,15 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
800
893
|
this.lastReconnectLogTime = 0;
|
|
801
894
|
this.lastReconnectLogAttempt = 0;
|
|
802
895
|
this.trace('IN', 'connection.state', data);
|
|
803
|
-
logger.info(
|
|
896
|
+
logger.info(`${this.logPrefix()} Connected`);
|
|
804
897
|
}
|
|
805
898
|
else if (state === 'disconnected') {
|
|
806
899
|
this.connected = false;
|
|
807
900
|
this.trace('IN', 'connection.state', data);
|
|
808
|
-
logger.warn(
|
|
901
|
+
logger.warn(`${this.logPrefix()} Disconnected: ${data.error ?? 'unknown'}`);
|
|
809
902
|
}
|
|
810
903
|
else if (state === 'reconnecting') {
|
|
904
|
+
this.connected = false;
|
|
811
905
|
const attempt = data.attempt ?? 0;
|
|
812
906
|
const now = Date.now();
|
|
813
907
|
// Throttled logging: first attempt, every N attempts, or every M seconds
|
|
@@ -817,14 +911,14 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
817
911
|
if (isFirst || isStep || isInterval) {
|
|
818
912
|
const suppressed = attempt - this.lastReconnectLogAttempt - 1;
|
|
819
913
|
const suffix = suppressed > 0 ? `, ${suppressed} suppressed since last log` : '';
|
|
820
|
-
logger.info(
|
|
914
|
+
logger.info(`${this.logPrefix()} SDK reconnecting (attempt ${attempt}${suffix})`);
|
|
821
915
|
this.lastReconnectLogTime = now;
|
|
822
916
|
this.lastReconnectLogAttempt = attempt;
|
|
823
917
|
this.trace('IN', 'connection.state', data);
|
|
824
918
|
}
|
|
825
919
|
// Detect runaway SDK reconnect loop: force disconnect and use TS-layer backoff
|
|
826
920
|
if (attempt >= AUNChannel.SDK_RECONNECT_GIVEUP && !this.intentionalDisconnect) {
|
|
827
|
-
logger.warn(
|
|
921
|
+
logger.warn(`${this.logPrefix()} SDK reconnect stuck at attempt ${attempt}, forcing TS-layer reconnect with backoff`);
|
|
828
922
|
this.connected = false;
|
|
829
923
|
if (this.client) {
|
|
830
924
|
this.trace('OUT', 'client.close', { reason: 'sdk_reconnect_stuck' });
|
|
@@ -838,7 +932,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
838
932
|
this.connected = false;
|
|
839
933
|
this.trace('IN', 'connection.state', data);
|
|
840
934
|
const reason = data.reason ?? '';
|
|
841
|
-
logger.error(
|
|
935
|
+
logger.error(`${this.logPrefix()} Terminal failure: ${data.error ?? 'unknown'}${reason ? ` (${reason})` : ''}`);
|
|
842
936
|
if (!this.intentionalDisconnect) {
|
|
843
937
|
this.scheduleReconnect();
|
|
844
938
|
}
|
|
@@ -859,11 +953,11 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
859
953
|
}
|
|
860
954
|
async sendMessage(channelId, text, context) {
|
|
861
955
|
if (!this.connected || !this.client) {
|
|
862
|
-
logger.warn(
|
|
956
|
+
logger.warn(`${this.logPrefix()} Cannot send: not connected`);
|
|
863
957
|
return;
|
|
864
958
|
}
|
|
865
959
|
if (!text?.trim()) {
|
|
866
|
-
logger.warn(
|
|
960
|
+
logger.warn(`${this.logPrefix()} Attempted to send empty message, skipping`);
|
|
867
961
|
return;
|
|
868
962
|
}
|
|
869
963
|
let finalText = text;
|
|
@@ -879,6 +973,12 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
879
973
|
}
|
|
880
974
|
}
|
|
881
975
|
const payload = { type: 'text', text: finalText };
|
|
976
|
+
// 出站群消息:从正文提取 @aid 填入结构化 mentions(与 aun CLI 对齐,方便对端按 mentions 过滤)
|
|
977
|
+
if (this.isGroupId(channelId)) {
|
|
978
|
+
const extracted = this.extractMentionAidsFromText(finalText);
|
|
979
|
+
if (extracted.length > 0)
|
|
980
|
+
payload.mentions = extracted;
|
|
981
|
+
}
|
|
882
982
|
if (context?.threadId)
|
|
883
983
|
payload.thread_id = context.threadId;
|
|
884
984
|
if (context?.metadata?.taskId)
|
|
@@ -904,31 +1004,31 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
904
1004
|
if (!result || !result.message_id) {
|
|
905
1005
|
const dispatchStatus = result?.message_dispatch?.status;
|
|
906
1006
|
if (dispatchStatus === 'debounced' || dispatchStatus === 'dispatched') {
|
|
907
|
-
logger.info(
|
|
1007
|
+
logger.info(`${this.logPrefix()} group.send ok (${dispatchStatus}): group=${channelId} encrypt=${encrypt} text=${finalText.slice(0, 60)}`);
|
|
908
1008
|
}
|
|
909
1009
|
else {
|
|
910
|
-
logger.warn(
|
|
1010
|
+
logger.warn(`${this.logPrefix()} group.send returned no message_id: ${JSON.stringify(result)}`);
|
|
911
1011
|
}
|
|
912
1012
|
}
|
|
913
1013
|
else {
|
|
914
|
-
logger.info(
|
|
1014
|
+
logger.info(`${this.logPrefix()} group.send ok: group=${channelId} mid=${result.message_id} encrypt=${encrypt} text=${finalText.slice(0, 60)}`);
|
|
915
1015
|
}
|
|
916
1016
|
}
|
|
917
1017
|
else {
|
|
918
1018
|
params.to = targetAid;
|
|
919
1019
|
const result = await this.callAndTrace('message.send', params);
|
|
920
1020
|
if (!result || !result.message_id) {
|
|
921
|
-
logger.warn(
|
|
1021
|
+
logger.warn(`${this.logPrefix()} message.send returned no message_id: ${JSON.stringify(result)}`);
|
|
922
1022
|
}
|
|
923
1023
|
else {
|
|
924
|
-
logger.info(
|
|
1024
|
+
logger.info(`${this.logPrefix()} message.send ok: to=${this.peerLabel(targetAid)} mid=${result.message_id} encrypt=${encrypt} text=${finalText.slice(0, 60)}`);
|
|
925
1025
|
}
|
|
926
1026
|
}
|
|
927
1027
|
}
|
|
928
1028
|
catch (e) {
|
|
929
1029
|
if (encrypt && e instanceof E2EEError) {
|
|
930
1030
|
this.peerE2ee.set(encryptTarget, { ok: false, ts: Date.now() });
|
|
931
|
-
logger.warn(
|
|
1031
|
+
logger.warn(`${this.logPrefix()} E2EE send failed to ${channelId}, retrying plaintext: ${e}`);
|
|
932
1032
|
params.encrypt = false;
|
|
933
1033
|
try {
|
|
934
1034
|
if (isGroup) {
|
|
@@ -936,7 +1036,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
936
1036
|
const result = await this.client.call('group.send', params);
|
|
937
1037
|
this.trace('OUT', 'group.send.fallback.ok', { message_id: result?.message_id });
|
|
938
1038
|
if (!result || !result.message_id) {
|
|
939
|
-
logger.warn(
|
|
1039
|
+
logger.warn(`${this.logPrefix()} group.send fallback returned no message_id: ${JSON.stringify(result)}`);
|
|
940
1040
|
}
|
|
941
1041
|
}
|
|
942
1042
|
else {
|
|
@@ -944,18 +1044,18 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
944
1044
|
const result = await this.client.call('message.send', params);
|
|
945
1045
|
this.trace('OUT', 'message.send.fallback.ok', { message_id: result?.message_id });
|
|
946
1046
|
if (!result || !result.message_id) {
|
|
947
|
-
logger.warn(
|
|
1047
|
+
logger.warn(`${this.logPrefix()} message.send fallback returned no message_id: ${JSON.stringify(result)}`);
|
|
948
1048
|
}
|
|
949
1049
|
}
|
|
950
1050
|
}
|
|
951
1051
|
catch (e2) {
|
|
952
1052
|
this.trace('OUT', 'send.fallback.error', { channelId, error: String(e2) });
|
|
953
|
-
logger.error(
|
|
1053
|
+
logger.error(`${this.logPrefix()} Plaintext fallback also failed to ${channelId}: ${e2}`);
|
|
954
1054
|
}
|
|
955
1055
|
}
|
|
956
1056
|
else {
|
|
957
1057
|
this.trace('OUT', 'send.error', { channelId, error: String(e) });
|
|
958
|
-
logger.error(
|
|
1058
|
+
logger.error(`${this.logPrefix()} Send failed to ${channelId}: ${e}`);
|
|
959
1059
|
}
|
|
960
1060
|
}
|
|
961
1061
|
}
|
|
@@ -988,36 +1088,36 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
988
1088
|
if (this.isGroupId(channelId)) {
|
|
989
1089
|
params.group_id = targetId;
|
|
990
1090
|
await this.callAndTrace('group.thought.put', params);
|
|
991
|
-
logger.info(
|
|
1091
|
+
logger.info(`${this.logPrefix()} thought.put ok group=${targetId} task=${taskId} stage=${stage} encrypt=${encrypt}`);
|
|
992
1092
|
}
|
|
993
1093
|
else {
|
|
994
1094
|
params.to = targetId;
|
|
995
1095
|
await this.callAndTrace('message.thought.put', params);
|
|
996
|
-
logger.info(
|
|
1096
|
+
logger.info(`${this.logPrefix()} thought.put ok p2p=${this.peerLabel(targetId)} task=${taskId} stage=${stage} encrypt=${encrypt}`);
|
|
997
1097
|
}
|
|
998
1098
|
}
|
|
999
1099
|
catch (e) {
|
|
1000
1100
|
const err = e;
|
|
1001
|
-
logger.debug(
|
|
1101
|
+
logger.debug(`${this.logPrefix()} thought.put failed to ${channelId}: ${err?.name}(${err?.code})=${err?.message}`);
|
|
1002
1102
|
}
|
|
1003
1103
|
}
|
|
1004
1104
|
async sendFile(channelId, filePath, context) {
|
|
1005
1105
|
if (!this.connected || !this.client) {
|
|
1006
|
-
logger.warn(
|
|
1106
|
+
logger.warn(`${this.logPrefix()} Cannot sendFile: not connected`);
|
|
1007
1107
|
return;
|
|
1008
1108
|
}
|
|
1009
1109
|
const absPath = path.resolve(filePath);
|
|
1010
1110
|
if (!fs.existsSync(absPath)) {
|
|
1011
|
-
logger.warn(
|
|
1111
|
+
logger.warn(`${this.logPrefix()} sendFile: file not found: ${absPath}`);
|
|
1012
1112
|
return;
|
|
1013
1113
|
}
|
|
1014
1114
|
const stat = fs.statSync(absPath);
|
|
1015
1115
|
if (stat.size === 0) {
|
|
1016
|
-
logger.warn(
|
|
1116
|
+
logger.warn(`${this.logPrefix()} sendFile: file is empty`);
|
|
1017
1117
|
return;
|
|
1018
1118
|
}
|
|
1019
1119
|
if (stat.size > 10 * 1024 * 1024) {
|
|
1020
|
-
logger.warn(
|
|
1120
|
+
logger.warn(`${this.logPrefix()} sendFile: file too large (${formatSize(stat.size)}, max 10 MB)`);
|
|
1021
1121
|
return;
|
|
1022
1122
|
}
|
|
1023
1123
|
const filename = path.basename(absPath);
|
|
@@ -1099,7 +1199,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1099
1199
|
const result = await this.client.call('group.send', params);
|
|
1100
1200
|
this.trace('OUT', 'group.send.file.ok', { message_id: result?.message_id });
|
|
1101
1201
|
if (!result || !result.message_id) {
|
|
1102
|
-
logger.warn(
|
|
1202
|
+
logger.warn(`${this.logPrefix()} group.send.file returned no message_id: ${JSON.stringify(result)}`);
|
|
1103
1203
|
}
|
|
1104
1204
|
}
|
|
1105
1205
|
else {
|
|
@@ -1108,7 +1208,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1108
1208
|
const result = await this.client.call('message.send', params);
|
|
1109
1209
|
this.trace('OUT', 'message.send.file.ok', { message_id: result?.message_id });
|
|
1110
1210
|
if (!result || !result.message_id) {
|
|
1111
|
-
logger.warn(
|
|
1211
|
+
logger.warn(`${this.logPrefix()} message.send.file returned no message_id: ${JSON.stringify(result)}`);
|
|
1112
1212
|
}
|
|
1113
1213
|
}
|
|
1114
1214
|
}
|
|
@@ -1119,14 +1219,14 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1119
1219
|
});
|
|
1120
1220
|
if (encrypt && sendErr instanceof E2EEError) {
|
|
1121
1221
|
this.peerE2ee.set(encryptTarget, { ok: false, ts: Date.now() });
|
|
1122
|
-
logger.warn(
|
|
1222
|
+
logger.warn(`${this.logPrefix()} E2EE sendFile failed to ${channelId}, retrying plaintext: ${sendErr}`);
|
|
1123
1223
|
params.encrypt = false;
|
|
1124
1224
|
if (isGroup) {
|
|
1125
1225
|
this.trace('OUT', 'group.send.file.fallback', params);
|
|
1126
1226
|
const result = await this.client.call('group.send', params);
|
|
1127
1227
|
this.trace('OUT', 'group.send.file.fallback.ok', { message_id: result?.message_id });
|
|
1128
1228
|
if (!result || !result.message_id) {
|
|
1129
|
-
logger.warn(
|
|
1229
|
+
logger.warn(`${this.logPrefix()} group.send.file fallback returned no message_id: ${JSON.stringify(result)}`);
|
|
1130
1230
|
}
|
|
1131
1231
|
}
|
|
1132
1232
|
else {
|
|
@@ -1134,7 +1234,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1134
1234
|
const result = await this.client.call('message.send', params);
|
|
1135
1235
|
this.trace('OUT', 'message.send.file.fallback.ok', { message_id: result?.message_id });
|
|
1136
1236
|
if (!result || !result.message_id) {
|
|
1137
|
-
logger.warn(
|
|
1237
|
+
logger.warn(`${this.logPrefix()} message.send.file fallback returned no message_id: ${JSON.stringify(result)}`);
|
|
1138
1238
|
}
|
|
1139
1239
|
}
|
|
1140
1240
|
}
|
|
@@ -1142,11 +1242,11 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1142
1242
|
throw sendErr;
|
|
1143
1243
|
}
|
|
1144
1244
|
}
|
|
1145
|
-
logger.info(
|
|
1245
|
+
logger.info(`${this.logPrefix()} File sent: ${filename} (${formatSize(stat.size)}) → ${channelId}`);
|
|
1146
1246
|
}
|
|
1147
1247
|
catch (e) {
|
|
1148
1248
|
this.trace('OUT', 'sendFile.error', { channelId, filePath, error: String(e) });
|
|
1149
|
-
logger.error(
|
|
1249
|
+
logger.error(`${this.logPrefix()} sendFile failed for ${channelId}: ${e}`);
|
|
1150
1250
|
}
|
|
1151
1251
|
}
|
|
1152
1252
|
acknowledge(messageId) {
|
|
@@ -1190,14 +1290,14 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1190
1290
|
this.client.call(method, params).catch(e => {
|
|
1191
1291
|
if (encrypt && e instanceof E2EEError) {
|
|
1192
1292
|
this.peerE2ee.set(encryptTarget, { ok: false, ts: Date.now() });
|
|
1193
|
-
logger.warn(
|
|
1293
|
+
logger.warn(`${this.logPrefix()} E2EE status send failed to ${channelId}, retrying plaintext`);
|
|
1194
1294
|
params.encrypt = false;
|
|
1195
1295
|
this.client.call(method, params).catch(e2 => {
|
|
1196
|
-
logger.debug(
|
|
1296
|
+
logger.debug(`${this.logPrefix()} Processing status fallback failed: ${e2}`);
|
|
1197
1297
|
});
|
|
1198
1298
|
}
|
|
1199
1299
|
else {
|
|
1200
|
-
logger.debug(
|
|
1300
|
+
logger.debug(`${this.logPrefix()} Processing status failed: ${e}`);
|
|
1201
1301
|
}
|
|
1202
1302
|
});
|
|
1203
1303
|
};
|
|
@@ -1211,7 +1311,10 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1211
1311
|
this.trace('OUT', 'message.send.status', params);
|
|
1212
1312
|
sendWithFallback('message.send');
|
|
1213
1313
|
}
|
|
1214
|
-
|
|
1314
|
+
// 群聊显示 group id 简称,P2P 显示 peer label;从 context.metadata 读取 chatmode
|
|
1315
|
+
const targetLabel = this.isGroupId(channelId) ? channelId : this.peerLabel(channelId);
|
|
1316
|
+
const chatmode = context?.metadata?.chatmode ?? '?';
|
|
1317
|
+
logger.info(`${this.logPrefix()} task.${status} task=${taskId} session=${sessionId} chatmode=${chatmode} encrypt=${encrypt} target=${targetLabel}`);
|
|
1215
1318
|
}
|
|
1216
1319
|
sendCustomPayload(channelId, payload) {
|
|
1217
1320
|
if (!this.client || !this.connected)
|
|
@@ -1241,7 +1344,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1241
1344
|
this.trace('OUT', 'message.send.custom.ok', { message_id: result?.message_id });
|
|
1242
1345
|
}).catch(e => {
|
|
1243
1346
|
this.trace('OUT', 'message.send.custom.error', { error: String(e) });
|
|
1244
|
-
logger.warn(
|
|
1347
|
+
logger.warn(`${this.logPrefix()} Custom payload failed: ${e}`);
|
|
1245
1348
|
});
|
|
1246
1349
|
}
|
|
1247
1350
|
async disconnect() {
|
|
@@ -1262,12 +1365,12 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1262
1365
|
this.client = null;
|
|
1263
1366
|
}
|
|
1264
1367
|
this.connected = false;
|
|
1265
|
-
logger.info(
|
|
1368
|
+
logger.info(`${this.logPrefix()} Disconnected`);
|
|
1266
1369
|
if (this.traceStream) {
|
|
1267
1370
|
this.traceStream.end();
|
|
1268
1371
|
this.traceStream = null;
|
|
1269
1372
|
}
|
|
1270
|
-
logger.info(
|
|
1373
|
+
logger.info(`${this.logPrefix()} Disconnected`);
|
|
1271
1374
|
}
|
|
1272
1375
|
// ── TS-layer reconnect (fallback when SDK auto_reconnect exhausted) ──
|
|
1273
1376
|
scheduleReconnect() {
|
|
@@ -1277,25 +1380,25 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1277
1380
|
return;
|
|
1278
1381
|
const delays = AUNChannel.RECONNECT_DELAYS;
|
|
1279
1382
|
if (this.reconnectAttempt >= delays.length) {
|
|
1280
|
-
logger.error(
|
|
1383
|
+
logger.error(`${this.logPrefix()} All ${delays.length} reconnect attempts exhausted, giving up`);
|
|
1281
1384
|
this.onChannelDown?.();
|
|
1282
1385
|
return;
|
|
1283
1386
|
}
|
|
1284
1387
|
const delay = delays[this.reconnectAttempt];
|
|
1285
1388
|
this.reconnectAttempt++;
|
|
1286
|
-
logger.info(
|
|
1389
|
+
logger.info(`${this.logPrefix()} Scheduling reconnect #${this.reconnectAttempt}/${delays.length} in ${delay}s`);
|
|
1287
1390
|
this.reconnectTimer = setTimeout(async () => {
|
|
1288
1391
|
this.reconnectTimer = null;
|
|
1289
1392
|
try {
|
|
1290
|
-
logger.info(
|
|
1393
|
+
logger.info(`${this.logPrefix()} Reconnect #${this.reconnectAttempt} starting...`);
|
|
1291
1394
|
this.trace('OUT', 'reconnect.start', { attempt: this.reconnectAttempt });
|
|
1292
1395
|
await this.initClient();
|
|
1293
1396
|
this.trace('OUT', 'reconnect.ok', { attempt: this.reconnectAttempt });
|
|
1294
|
-
logger.info(
|
|
1397
|
+
logger.info(`${this.logPrefix()} Reconnect #${this.reconnectAttempt} succeeded`);
|
|
1295
1398
|
}
|
|
1296
1399
|
catch (err) {
|
|
1297
1400
|
this.trace('OUT', 'reconnect.error', { attempt: this.reconnectAttempt, error: String(err) });
|
|
1298
|
-
logger.error(
|
|
1401
|
+
logger.error(`${this.logPrefix()} Reconnect #${this.reconnectAttempt} failed:`, err);
|
|
1299
1402
|
this.scheduleReconnect();
|
|
1300
1403
|
}
|
|
1301
1404
|
}, delay * 1000);
|
|
@@ -1371,7 +1474,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1371
1474
|
return info;
|
|
1372
1475
|
}
|
|
1373
1476
|
catch (e) {
|
|
1374
|
-
logger.debug(
|
|
1477
|
+
logger.debug(`${this.logPrefix()} fetchPeerInfo failed for ${aid}: ${e}`);
|
|
1375
1478
|
return { type: null }; // no agent.md → unknown
|
|
1376
1479
|
}
|
|
1377
1480
|
}
|