evolclaw 2.7.3 → 2.8.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.
@@ -35,16 +35,45 @@ export class AUNChannel {
35
35
  recallHandler;
36
36
  connected = false;
37
37
  traceStream = null;
38
- traceDate = ''; // 当前 trace 文件对应的日期 (YYYYMMDD)
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
- const line = JSON.stringify({ ts: localTimestamp(), dir, event, data });
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(`[AUN] rpc ${method} failed: ${e?.name ?? ''}(${e?.code ?? ''}) ${e?.message ?? e}`);
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 today = `${d.getFullYear()}${String(d.getMonth() + 1).padStart(2, '0')}${String(d.getDate()).padStart(2, '0')}`;
78
- if (this.traceDate === today && this.traceStream)
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.traceDate = today;
85
- const logPath = path.join(resolvePaths().logs, `aun-${today}.log`);
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;
@@ -131,8 +193,41 @@ export class AUNChannel {
131
193
  .replace(/[ \t]+/g, ' ')
132
194
  .trim();
133
195
  }
134
- buildGroupReplyContext(taskId, senderAid) {
135
- const replyContext = {};
196
+ extractMentionAids(mentions) {
197
+ const aids = [];
198
+ for (const m of mentions) {
199
+ if (typeof m === 'string')
200
+ aids.push(m);
201
+ else if (m && typeof m === 'object' && typeof m.aid === 'string')
202
+ aids.push(m.aid);
203
+ }
204
+ return aids;
205
+ }
206
+ hasMentionAll(mentions) {
207
+ for (const m of mentions) {
208
+ if (m === 'all')
209
+ return true;
210
+ if (m && typeof m === 'object' && m.scope === 'all')
211
+ return true;
212
+ }
213
+ return false;
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
+ }
229
+ buildGroupReplyContext(taskId, senderAid, encrypted) {
230
+ const replyContext = { metadata: { encrypted } };
136
231
  if (taskId)
137
232
  replyContext.threadId = taskId;
138
233
  replyContext.peerId = senderAid;
@@ -141,7 +236,7 @@ export class AUNChannel {
141
236
  acknowledgeImmediately(messageId, seq) {
142
237
  if (seq != null && this.client) {
143
238
  this.client.call('message.ack', { seq }).catch(e => {
144
- logger.debug(`[AUN] Immediate ack failed: ${e}`);
239
+ logger.debug(`${this.logPrefix()} Immediate ack failed: ${e}`);
145
240
  });
146
241
  }
147
242
  if (messageId)
@@ -167,6 +262,11 @@ export class AUNChannel {
167
262
  peerE2ee = new Map();
168
263
  static E2EE_PROBE_TTL = 10 * 60 * 1000; // 10min
169
264
  plaintextRecv = 0;
265
+ sessionModeResolver;
266
+ static PROACTIVE_ALLOW_TYPES = new Set([
267
+ 'text', 'quote', 'image', 'video', 'voice', 'file', 'json',
268
+ 'merge', 'link', 'location', 'personal_card',
269
+ ]);
170
270
  // Reconnect state (TS-layer fallback, on top of SDK auto_reconnect)
171
271
  intentionalDisconnect = false;
172
272
  reconnectAttempt = 0;
@@ -183,7 +283,7 @@ export class AUNChannel {
183
283
  this.config = config;
184
284
  if (config.aunTrace) {
185
285
  this.rotateTraceIfNeeded();
186
- logger.info(`[AUN] Trace logging enabled (daily rotation): ${resolvePaths().logs}/aun-YYYYMMDD.log`);
286
+ logger.info(`${this.logPrefix()} Trace logging enabled (hourly rotation, 12h retention): ${resolvePaths().logs}/aun-YYYYMMDD-HH.log`);
187
287
  }
188
288
  }
189
289
  async connect() {
@@ -216,17 +316,17 @@ export class AUNChannel {
216
316
  try {
217
317
  const discovery = new GatewayDiscovery({});
218
318
  gateway = await discovery.discover(wellKnownUrl);
219
- logger.info(`[AUN] Gateway discovered: ${gateway}`);
319
+ logger.info(`${this.logPrefix()} Gateway discovered: ${gateway}`);
220
320
  }
221
321
  catch (e) {
222
- logger.warn(`[AUN] Well-known discovery failed (${e}), no fallback available`);
322
+ logger.warn(`${this.logPrefix()} Well-known discovery failed (${e}), no fallback available`);
223
323
  }
224
324
  }
225
325
  if (!gateway) {
226
- logger.error('[AUN] Cannot resolve gateway URL from AID');
326
+ logger.error(`${this.logPrefix()} Cannot resolve gateway URL from AID`);
227
327
  throw new Error('Cannot resolve gateway URL from AID');
228
328
  }
229
- logger.info(`[AUN] Initializing: aid=${aidName}, gateway=${gateway}, aun_path=${aunPath}`);
329
+ logger.info(`${this.logPrefix()} Initializing: aid=${aidName}, gateway=${gateway}, aun_path=${aunPath}`);
230
330
  // Create client with FileSecretStore (AES-256-GCM)
231
331
  // 不传 encryption_seed 时,SDK 自动从 {aun_path}/.seed 文件派生密钥(与 aun_cli.py 对齐)
232
332
  const rootCaPath = path.join(aunPath, 'CA', 'root', 'root.crt');
@@ -242,14 +342,14 @@ export class AUNChannel {
242
342
  this.trace('IN', 'message.received', data);
243
343
  const kind = (data && typeof data === 'object') ? data.kind ?? '' : '';
244
344
  const keys = (data && typeof data === 'object') ? Object.keys(data).join(',') : typeof data;
245
- logger.debug(`[AUN][DIAG] message.received: kind=${kind} keys=${keys}`);
345
+ logger.debug(`${this.logPrefix()}[DIAG] message.received: kind=${kind} keys=${keys}`);
246
346
  this.handleIncomingPrivateMessage(data);
247
347
  });
248
348
  this.client.on('group.message_created', (data) => {
249
349
  this.trace('IN', 'group.message_created', data);
250
350
  const gid = (data && typeof data === 'object') ? data.group_id ?? '' : '';
251
351
  const sender = (data && typeof data === 'object') ? data.sender_aid ?? '' : '';
252
- logger.debug(`[AUN][DIAG] group.message_created: group_id=${gid} sender=${sender}`);
352
+ logger.debug(`${this.logPrefix()}[DIAG] group.message_created: group_id=${gid} sender=${sender}`);
253
353
  this.handleIncomingGroupMessage(data);
254
354
  });
255
355
  this.client.on('connection.state', (data) => {
@@ -263,7 +363,7 @@ export class AUNChannel {
263
363
  if (Array.isArray(ids)) {
264
364
  for (const id of ids) {
265
365
  if (typeof id === 'string') {
266
- logger.info(`[AUN] Message recalled: ${id}`);
366
+ logger.info(`${this.logPrefix()} Message recalled: ${id}`);
267
367
  this.recallHandler?.(id);
268
368
  }
269
369
  }
@@ -273,12 +373,12 @@ export class AUNChannel {
273
373
  this.client.on('message.undecryptable', (data) => {
274
374
  this.trace('IN', 'message.undecryptable', data);
275
375
  const d = data;
276
- logger.warn(`[AUN] Message undecryptable: from=${d.from} mid=${d.message_id} err=${d._decrypt_error}`);
376
+ logger.warn(`${this.logPrefix()} Message undecryptable: from=${d.from} mid=${d.message_id} err=${d._decrypt_error}`);
277
377
  });
278
378
  this.client.on('group.message_undecryptable', (data) => {
279
379
  this.trace('IN', 'group.message_undecryptable', data);
280
380
  const d = data;
281
- logger.warn(`[AUN] Group message undecryptable: group=${d.group_id} from=${d.from} mid=${d.message_id} err=${d._decrypt_error}`);
381
+ logger.warn(`${this.logPrefix()} Group message undecryptable: group=${d.group_id} from=${d.from} mid=${d.message_id} err=${d._decrypt_error}`);
282
382
  });
283
383
  // Authenticate
284
384
  // Workaround: SDK 0.3.x _loadIdentityOrRaise doesn't set identity.aid from requested aid,
@@ -295,7 +395,7 @@ export class AUNChannel {
295
395
  }
296
396
  let accessToken;
297
397
  try {
298
- logger.info(`[AUN] Authenticating as ${aidName}...`);
398
+ logger.info(`${this.logPrefix()} Authenticating as ${aidName}...`);
299
399
  this.trace('OUT', 'auth.authenticate', { aid: aidName });
300
400
  const auth = await this.client.auth.authenticate(aidName ? { aid: aidName } : undefined);
301
401
  this.trace('OUT', 'auth.authenticate.ok', { aid: auth.aid, gateway: auth.gateway, hasToken: !!auth.access_token });
@@ -303,23 +403,23 @@ export class AUNChannel {
303
403
  accessToken = auth.access_token;
304
404
  const resolvedGateway = auth.gateway || gateway;
305
405
  this.client._gatewayUrl = resolvedGateway;
306
- logger.info(`[AUN] Authenticated as ${auth.aid ?? '?'}, gateway=${resolvedGateway}`);
406
+ logger.info(`${this.logPrefix()} Authenticated as ${auth.aid ?? '?'}, gateway=${resolvedGateway}`);
307
407
  }
308
408
  catch (e) {
309
409
  const errMsg = e.message || String(e);
310
410
  const errName = e.constructor?.name || 'Error';
311
411
  this.trace('OUT', 'auth.authenticate.error', { error: errMsg, name: errName });
312
- logger.error(`[AUN] Authentication failed (${errName}): ${errMsg}`);
412
+ logger.error(`${this.logPrefix()} Authentication failed (${errName}): ${errMsg}`);
313
413
  if (e.stack)
314
- logger.debug(`[AUN] Auth stack: ${e.stack}`);
414
+ logger.debug(`${this.logPrefix()} Auth stack: ${e.stack}`);
315
415
  // Fallback: try direct token from env/config (legacy)
316
416
  accessToken = this.config.accessToken || process.env.AUN_ACCESS_TOKEN || '';
317
417
  if (!accessToken) {
318
- logger.error(`[AUN] No accessToken fallback available, scheduling retry`);
418
+ logger.error(`${this.logPrefix()} No accessToken fallback available, scheduling retry`);
319
419
  this.scheduleReconnect();
320
420
  throw new Error('Authentication failed and no accessToken fallback available');
321
421
  }
322
- logger.warn(`[AUN] Using accessToken fallback`);
422
+ logger.warn(`${this.logPrefix()} Using accessToken fallback`);
323
423
  }
324
424
  // Connect (SDK auto_reconnect handles transient failures)
325
425
  try {
@@ -340,16 +440,16 @@ export class AUNChannel {
340
440
  const cert = clientAny._keystore?.loadCert?.(aidName);
341
441
  if (cert) {
342
442
  clientAny._identity.cert = cert;
343
- logger.info('[AUN] Backfilled identity.cert from keystore for e2ee fingerprint');
443
+ logger.info(`${this.logPrefix()} Backfilled identity.cert from keystore for e2ee fingerprint`);
344
444
  }
345
445
  }
346
- logger.info(`[AUN] Connected as ${this._aid}`);
446
+ logger.info(`${this.logPrefix()} Connected as ${this._aid}`);
347
447
  // Send welcome message to owner after first connection
348
448
  await this.sendWelcomeMessage();
349
449
  }
350
450
  catch (e) {
351
451
  this.trace('OUT', 'client.connect.error', { error: String(e) });
352
- logger.error(`[AUN] Connection failed: ${e}`);
452
+ logger.error(`${this.logPrefix()} Connection failed: ${e}`);
353
453
  this.scheduleReconnect();
354
454
  throw e;
355
455
  }
@@ -358,7 +458,7 @@ export class AUNChannel {
358
458
  try {
359
459
  const owner = this.config.owner;
360
460
  if (!owner) {
361
- logger.info('[AUN] No owner configured, skipping welcome message');
461
+ logger.info(`${this.logPrefix()} No owner configured, skipping welcome message`);
362
462
  return;
363
463
  }
364
464
  // Check agent.md initialized field
@@ -366,25 +466,25 @@ export class AUNChannel {
366
466
  const aidName = aid.startsWith('@') ? aid.slice(1) : aid;
367
467
  const agentMdPath = path.join(os.homedir(), '.aun', 'AIDs', aidName, 'agent.md');
368
468
  if (!fs.existsSync(agentMdPath)) {
369
- logger.warn('[AUN] agent.md not found, skipping welcome message');
469
+ logger.warn(`${this.logPrefix()} agent.md not found, skipping welcome message`);
370
470
  return;
371
471
  }
372
472
  const agentMdContent = fs.readFileSync(agentMdPath, 'utf-8');
373
473
  const match = agentMdContent.match(/^---\n([\s\S]*?)\n---/);
374
474
  if (!match) {
375
- logger.warn('[AUN] agent.md frontmatter not found');
475
+ logger.warn(`${this.logPrefix()} agent.md frontmatter not found`);
376
476
  return;
377
477
  }
378
478
  const frontmatter = match[1];
379
479
  const initializedMatch = frontmatter.match(/^initialized:\s*(true|false)/m);
380
480
  if (!initializedMatch || initializedMatch[1] === 'true') {
381
- logger.info('[AUN] Agent already initialized, skipping welcome message');
481
+ logger.info(`${this.logPrefix()} Agent already initialized, skipping welcome message`);
382
482
  return;
383
483
  }
384
484
  // Fetch owner's agent.md to derive name and validate type
385
485
  const ownerInfo = await this.fetchPeerInfo(owner);
386
486
  if (ownerInfo.type !== null && ownerInfo.type !== 'human') {
387
- logger.warn(`[AUN] Owner ${owner} type is "${ownerInfo.type}" (not human). Consider using a human AID as owner.`);
487
+ logger.warn(`${this.logPrefix()} Owner ${owner} type is "${ownerInfo.type}" (not human). Consider using a human AID as owner.`);
388
488
  }
389
489
  // Name: prefer existing agent.md name if user has customized it,
390
490
  // otherwise generate "{ownerName}的Evol助手 ({aidLabel})" for disambiguation
@@ -428,14 +528,14 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
428
528
  `;
429
529
  // Write locally
430
530
  fs.writeFileSync(agentMdPath, newAgentMd, 'utf-8');
431
- logger.info('[AUN] Updated agent.md with initialized=true');
531
+ logger.info(`${this.logPrefix()} Updated agent.md with initialized=true`);
432
532
  // Publish to AUN network via auth.uploadAgentMd
433
533
  try {
434
534
  await this.client.auth.uploadAgentMd(newAgentMd);
435
- logger.info('[AUN] Published agent.md to AUN network');
535
+ logger.info(`${this.logPrefix()} Published agent.md to AUN network`);
436
536
  }
437
537
  catch (e) {
438
- logger.warn(`[AUN] Failed to publish agent.md: ${e}`);
538
+ logger.warn(`${this.logPrefix()} Failed to publish agent.md: ${e}`);
439
539
  }
440
540
  // Send welcome message
441
541
  const welcomeText = `🎉 欢迎使用 EvolClaw!
@@ -463,7 +563,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
463
563
  // pull if the initial E2EE push still arrives before the cert resolves.
464
564
  await new Promise(resolve => setTimeout(resolve, 3000));
465
565
  if (!this.client) {
466
- logger.warn('[AUN] Client disconnected before welcome message could be sent');
566
+ logger.warn(`${this.logPrefix()} Client disconnected before welcome message could be sent`);
467
567
  return;
468
568
  }
469
569
  await this.callAndTrace('message.send', {
@@ -472,10 +572,10 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
472
572
  encrypt: true,
473
573
  persist_required: true,
474
574
  });
475
- logger.info(`[AUN] Welcome message sent to owner: ${owner}`);
575
+ logger.info(`${this.logPrefix()} Welcome message sent to owner: ${owner}`);
476
576
  }
477
577
  catch (e) {
478
- logger.warn(`[AUN] Failed to send welcome message: ${e}`);
578
+ logger.warn(`${this.logPrefix()} Failed to send welcome message: ${e}`);
479
579
  }
480
580
  }
481
581
  // ── Event handlers ──────────────────────────────────────────
@@ -484,7 +584,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
484
584
  const objectKey = att.object_key;
485
585
  const filename = att.filename || objectKey.split('/').pop() || 'unknown';
486
586
  if (!objectKey) {
487
- logger.warn('[AUN] Attachment missing object_key, skipping');
587
+ logger.warn(`${this.logPrefix()} Attachment missing object_key, skipping`);
488
588
  return null;
489
589
  }
490
590
  let downloadUrl;
@@ -495,32 +595,32 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
495
595
  });
496
596
  downloadUrl = ticket.download_url || '';
497
597
  if (!downloadUrl) {
498
- logger.warn(`[AUN] No download_url for attachment: ${filename}`);
598
+ logger.warn(`${this.logPrefix()} No download_url for attachment: ${filename}`);
499
599
  return null;
500
600
  }
501
601
  }
502
602
  catch (e) {
503
- logger.warn(`[AUN] create_download_ticket failed for ${filename}: ${e}`);
603
+ logger.warn(`${this.logPrefix()} create_download_ticket failed for ${filename}: ${e}`);
504
604
  return null;
505
605
  }
506
606
  let buffer;
507
607
  try {
508
608
  const res = await fetch(downloadUrl);
509
609
  if (!res.ok) {
510
- logger.warn(`[AUN] Download failed for ${filename}: HTTP ${res.status}`);
610
+ logger.warn(`${this.logPrefix()} Download failed for ${filename}: HTTP ${res.status}`);
511
611
  return null;
512
612
  }
513
613
  buffer = Buffer.from(await res.arrayBuffer());
514
614
  }
515
615
  catch (e) {
516
- logger.warn(`[AUN] Download error for ${filename}: ${e}`);
616
+ logger.warn(`${this.logPrefix()} Download error for ${filename}: ${e}`);
517
617
  return null;
518
618
  }
519
619
  if (att.sha256) {
520
620
  const { createHash } = await import('node:crypto');
521
621
  const actual = createHash('sha256').update(buffer).digest('hex');
522
622
  if (actual !== att.sha256) {
523
- logger.warn(`[AUN] SHA256 mismatch for ${filename}: expected ${att.sha256.slice(0, 8)}… got ${actual.slice(0, 8)}…`);
623
+ logger.warn(`${this.logPrefix()} SHA256 mismatch for ${filename}: expected ${att.sha256.slice(0, 8)}… got ${actual.slice(0, 8)}…`);
524
624
  return null;
525
625
  }
526
626
  }
@@ -529,11 +629,11 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
529
629
  : process.cwd();
530
630
  try {
531
631
  const result = saveToUploads(buffer, filename, projectPath);
532
- logger.info(`[AUN] Saved attachment: ${result.filePath} (${result.size} bytes)`);
632
+ logger.info(`${this.logPrefix()} Saved attachment: ${result.filePath} (${result.size} bytes)`);
533
633
  return result.filePath;
534
634
  }
535
635
  catch (e) {
536
- logger.warn(`[AUN] saveToUploads failed for ${filename}: ${e}`);
636
+ logger.warn(`${this.logPrefix()} saveToUploads failed for ${filename}: ${e}`);
537
637
  return null;
538
638
  }
539
639
  }
@@ -552,19 +652,13 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
552
652
  const msgChatId = typeof payload === 'object' && payload !== null && payload.chat_id;
553
653
  if (this._aid && fromAid === this._aid && (!msgChatId || !this._chatId || msgChatId !== this._chatId)) {
554
654
  this.acknowledgeImmediately(messageId, seq);
555
- logger.debug(`[AUN] P2P dropped: echo from self (from=${fromAid} mid=${messageId})`);
655
+ logger.debug(`${this.logPrefix()} P2P dropped: echo from self (from=${fromAid} mid=${messageId})`);
556
656
  return;
557
657
  }
558
- // E2EE 能力探测:收到加密消息则标记对端支持,明文则计数审计
658
+ // 记录入站消息加密状态,透传到出站 ReplyContext
559
659
  const msgEncrypted = !!(msg.e2ee);
560
- if (fromAid) {
561
- if (msgEncrypted) {
562
- this.peerE2ee.set(fromAid, { ok: true, ts: Date.now() });
563
- }
564
- else {
565
- this.plaintextRecv++;
566
- }
567
- }
660
+ if (!msgEncrypted)
661
+ this.plaintextRecv++;
568
662
  // Detect @mentions
569
663
  const mentions = [];
570
664
  if (this._aid && text.includes(`@${this._aid}`)) {
@@ -600,7 +694,13 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
600
694
  const peerInfo = await this.fetchPeerInfo(fromAid);
601
695
  const shortAid = this.getShortAid(fromAid);
602
696
  const displayName = peerInfo.name || shortAid;
603
- logger.info(`[AUN] P2P dispatched: from=${shortAid}(${displayName}) mid=${messageId} text=${finalText.slice(0, 60)}`);
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)}`);
701
+ const replyContext = { metadata: { encrypted: msgEncrypted } };
702
+ if (taskId)
703
+ replyContext.threadId = taskId;
604
704
  this.dispatchMessage({
605
705
  channelId: chatId,
606
706
  userId: fromAid,
@@ -612,6 +712,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
612
712
  mentions,
613
713
  peerName: displayName || undefined,
614
714
  peerType: peerInfo.type || 'unknown',
715
+ replyContext,
615
716
  });
616
717
  }
617
718
  async handleIncomingGroupMessage(data) {
@@ -629,37 +730,56 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
629
730
  const payloadMentions = Array.isArray(payload?.mentions)
630
731
  ? payload.mentions.filter((m) => typeof m === 'string')
631
732
  : [];
632
- logger.debug(`[AUN][DIAG-GRP] full_msg=${JSON.stringify(msg).substring(0, 500)}`);
733
+ logger.debug(`${this.logPrefix()}[DIAG-GRP] full_msg=${JSON.stringify(msg).substring(0, 500)}`);
633
734
  if (!groupId || !senderAid) {
634
735
  this.acknowledgeImmediately(messageId, seq);
635
- logger.debug(`[AUN] Group dropped: missing groupId or senderAid (mid=${messageId})`);
736
+ logger.debug(`${this.logPrefix()} Group dropped: missing groupId or senderAid (mid=${messageId})`);
636
737
  return;
637
738
  }
638
739
  if (this._aid && senderAid === this._aid) {
639
740
  this.acknowledgeImmediately(messageId, seq);
640
- logger.debug(`[AUN] Group dropped: own message (group=${groupId} mid=${messageId})`);
741
+ logger.debug(`${this.logPrefix()} Group dropped: own message (group=${groupId} mid=${messageId})`);
641
742
  return;
642
743
  }
643
- // E2EE 能力探测:收到加密群消息则标记发送者支持
644
- const msgEncrypted = !!(msg.e2ee);
645
- if (senderAid) {
646
- if (msgEncrypted) {
647
- this.peerE2ee.set(senderAid, { ok: true, ts: Date.now() });
648
- }
649
- else {
650
- this.plaintextRecv++;
744
+ // ── proactive 模式入站白名单 ──
745
+ if (this.sessionModeResolver) {
746
+ const sessionMode = await this.sessionModeResolver(groupId).catch(() => undefined);
747
+ if (sessionMode === 'proactive') {
748
+ const payloadObj = (payload && typeof payload === 'object') ? payload : null;
749
+ const payloadType = payloadObj?.type ?? '';
750
+ if (!AUNChannel.PROACTIVE_ALLOW_TYPES.has(payloadType)) {
751
+ this.acknowledgeImmediately(messageId, seq);
752
+ logger.info(`${this.logPrefix()} Group dropped (proactive deny): type=${payloadType} group=${groupId} sender=${senderAid} mid=${messageId}`);
753
+ return;
754
+ }
755
+ const rawText = typeof payloadObj?.text === 'string' ? payloadObj.text : '';
756
+ const rawMentions = Array.isArray(payloadObj?.mentions) ? payloadObj.mentions : [];
757
+ const mentionAids = this.extractMentionAids(rawMentions);
758
+ const mentionsSelf = !!this._aid && (this.hasExplicitMention(rawText, this._aid) || mentionAids.includes(this._aid));
759
+ // @all 仅认结构化 mentions(payload.mentions),不扫描正文 — 避免引述性 "@all" 误判
760
+ const mentionsAll = this.hasMentionAll(rawMentions);
761
+ if (!mentionsSelf && !mentionsAll) {
762
+ this.acknowledgeImmediately(messageId, seq);
763
+ logger.info(`${this.logPrefix()} Group dropped (proactive whitelist): type=${payloadType} group=${groupId} sender=${senderAid} mid=${messageId} textPreview=${JSON.stringify(rawText.slice(0, 80))}`);
764
+ return;
765
+ }
651
766
  }
652
767
  }
768
+ // 记录入站消息加密状态,透传到出站 ReplyContext
769
+ const msgEncrypted = !!(msg.e2ee);
770
+ if (!msgEncrypted)
771
+ this.plaintextRecv++;
653
772
  // dispatch_mode from server tells agent how to work in this group
654
773
  const dispatchMode = msg.dispatch_mode ?? payload?.dispatch_mode ?? 'mention';
655
774
  const mentionedSelf = this._aid
656
775
  ? (this.hasExplicitMention(text, this._aid) || payloadMentions.includes(this._aid))
657
776
  : false;
658
- const mentionedAll = this.hasExplicitMention(text, 'all') || payloadMentions.includes('all');
777
+ // @all 仅认结构化 mentions(payload.mentions),不扫描正文 避免引述性 "@all" 误判
778
+ const mentionedAll = payloadMentions.includes('all');
659
779
  // In mention mode, only respond when explicitly mentioned; in broadcast mode, respond to all
660
780
  if (dispatchMode === 'mention' && !mentionedSelf && !mentionedAll) {
661
781
  this.acknowledgeImmediately(messageId, seq);
662
- logger.debug(`[AUN] Group missed: unmentioned in mention-mode (group=${groupId} sender=${senderAid} mid=${messageId})`);
782
+ logger.info(`${this.logPrefix()} Group dropped: unmentioned in mention-mode (group=${groupId} sender=${senderAid} mid=${messageId} textPreview=${JSON.stringify(text.slice(0, 80))})`);
663
783
  return;
664
784
  }
665
785
  const strippedText = this.stripSelfMentionIfOnly(text, this._aid);
@@ -671,7 +791,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
671
791
  // Allow through if there's text OR attachments; both-empty messages are silently dropped
672
792
  if (!strippedText && !hasAttachments) {
673
793
  this.acknowledgeImmediately(messageId, seq);
674
- logger.debug(`[AUN] Group dropped: empty text and no attachments (group=${groupId} sender=${senderAid} mid=${messageId})`);
794
+ logger.debug(`${this.logPrefix()} Group dropped: empty text and no attachments (group=${groupId} sender=${senderAid} mid=${messageId})`);
675
795
  return;
676
796
  }
677
797
  const mentions = mentionedAll
@@ -700,7 +820,19 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
700
820
  const peerInfo = await this.fetchPeerInfo(senderAid);
701
821
  const shortAid = this.getShortAid(senderAid);
702
822
  const displayName = peerInfo.name || shortAid;
703
- logger.info(`[AUN] Group dispatched: group=${groupId} sender=${shortAid}(${displayName}) mode=${dispatchMode} mid=${messageId} text=${finalText.slice(0, 60)}`);
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)}`);
704
836
  this.dispatchMessage({
705
837
  channelId: groupId,
706
838
  userId: senderAid,
@@ -712,7 +844,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
712
844
  seq,
713
845
  taskId,
714
846
  mentions,
715
- replyContext: this.buildGroupReplyContext(taskId, senderAid),
847
+ replyContext: this.buildGroupReplyContext(taskId, senderAid, msgEncrypted),
716
848
  });
717
849
  }
718
850
  dispatchMessage(event) {
@@ -748,7 +880,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
748
880
  mentions: mentionObjects,
749
881
  replyContext,
750
882
  }).catch(err => {
751
- logger.error('[AUN] Message handler error:', err);
883
+ logger.error(`${this.logPrefix()} Message handler error:`, err);
752
884
  });
753
885
  }
754
886
  handleConnectionState(data) {
@@ -761,14 +893,15 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
761
893
  this.lastReconnectLogTime = 0;
762
894
  this.lastReconnectLogAttempt = 0;
763
895
  this.trace('IN', 'connection.state', data);
764
- logger.info('[AUN] Connected');
896
+ logger.info(`${this.logPrefix()} Connected`);
765
897
  }
766
898
  else if (state === 'disconnected') {
767
899
  this.connected = false;
768
900
  this.trace('IN', 'connection.state', data);
769
- logger.warn(`[AUN] Disconnected: ${data.error ?? 'unknown'}`);
901
+ logger.warn(`${this.logPrefix()} Disconnected: ${data.error ?? 'unknown'}`);
770
902
  }
771
903
  else if (state === 'reconnecting') {
904
+ this.connected = false;
772
905
  const attempt = data.attempt ?? 0;
773
906
  const now = Date.now();
774
907
  // Throttled logging: first attempt, every N attempts, or every M seconds
@@ -778,14 +911,14 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
778
911
  if (isFirst || isStep || isInterval) {
779
912
  const suppressed = attempt - this.lastReconnectLogAttempt - 1;
780
913
  const suffix = suppressed > 0 ? `, ${suppressed} suppressed since last log` : '';
781
- logger.info(`[AUN] SDK reconnecting (attempt ${attempt}${suffix})`);
914
+ logger.info(`${this.logPrefix()} SDK reconnecting (attempt ${attempt}${suffix})`);
782
915
  this.lastReconnectLogTime = now;
783
916
  this.lastReconnectLogAttempt = attempt;
784
917
  this.trace('IN', 'connection.state', data);
785
918
  }
786
919
  // Detect runaway SDK reconnect loop: force disconnect and use TS-layer backoff
787
920
  if (attempt >= AUNChannel.SDK_RECONNECT_GIVEUP && !this.intentionalDisconnect) {
788
- logger.warn(`[AUN] SDK reconnect stuck at attempt ${attempt}, forcing TS-layer reconnect with backoff`);
921
+ logger.warn(`${this.logPrefix()} SDK reconnect stuck at attempt ${attempt}, forcing TS-layer reconnect with backoff`);
789
922
  this.connected = false;
790
923
  if (this.client) {
791
924
  this.trace('OUT', 'client.close', { reason: 'sdk_reconnect_stuck' });
@@ -799,7 +932,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
799
932
  this.connected = false;
800
933
  this.trace('IN', 'connection.state', data);
801
934
  const reason = data.reason ?? '';
802
- logger.error(`[AUN] Terminal failure: ${data.error ?? 'unknown'}${reason ? ` (${reason})` : ''}`);
935
+ logger.error(`${this.logPrefix()} Terminal failure: ${data.error ?? 'unknown'}${reason ? ` (${reason})` : ''}`);
803
936
  if (!this.intentionalDisconnect) {
804
937
  this.scheduleReconnect();
805
938
  }
@@ -812,16 +945,19 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
812
945
  onMessage(handler) {
813
946
  this.messageHandler = handler;
814
947
  }
948
+ setSessionModeResolver(resolver) {
949
+ this.sessionModeResolver = resolver;
950
+ }
815
951
  onRecall(handler) {
816
952
  this.recallHandler = handler;
817
953
  }
818
954
  async sendMessage(channelId, text, context) {
819
955
  if (!this.connected || !this.client) {
820
- logger.warn('[AUN] Cannot send: not connected');
956
+ logger.warn(`${this.logPrefix()} Cannot send: not connected`);
821
957
  return;
822
958
  }
823
959
  if (!text?.trim()) {
824
- logger.warn('[AUN] Attempted to send empty message, skipping');
960
+ logger.warn(`${this.logPrefix()} Attempted to send empty message, skipping`);
825
961
  return;
826
962
  }
827
963
  let finalText = text;
@@ -837,6 +973,12 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
837
973
  }
838
974
  }
839
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
+ }
840
982
  if (context?.threadId)
841
983
  payload.thread_id = context.threadId;
842
984
  if (context?.metadata?.taskId)
@@ -851,34 +993,42 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
851
993
  payload.chat_id = channelId;
852
994
  }
853
995
  const encryptTarget = isGroup ? channelId : targetAid;
854
- const encrypt = this.shouldEncrypt(encryptTarget);
996
+ const encrypt = context?.metadata?.encrypted != null
997
+ ? !!(context.metadata.encrypted)
998
+ : this.shouldEncrypt(encryptTarget);
855
999
  const params = { payload, encrypt };
856
1000
  try {
857
1001
  if (isGroup) {
858
1002
  params.group_id = channelId;
859
1003
  const result = await this.callAndTrace('group.send', params);
860
1004
  if (!result || !result.message_id) {
861
- logger.warn(`[AUN] group.send returned no message_id: ${JSON.stringify(result)}`);
1005
+ const dispatchStatus = result?.message_dispatch?.status;
1006
+ if (dispatchStatus === 'debounced' || dispatchStatus === 'dispatched') {
1007
+ logger.info(`${this.logPrefix()} group.send ok (${dispatchStatus}): group=${channelId} encrypt=${encrypt} text=${finalText.slice(0, 60)}`);
1008
+ }
1009
+ else {
1010
+ logger.warn(`${this.logPrefix()} group.send returned no message_id: ${JSON.stringify(result)}`);
1011
+ }
862
1012
  }
863
1013
  else {
864
- logger.info(`[AUN] group.send ok: group=${channelId} mid=${result.message_id} text=${finalText.slice(0, 60)}`);
1014
+ logger.info(`${this.logPrefix()} group.send ok: group=${channelId} mid=${result.message_id} encrypt=${encrypt} text=${finalText.slice(0, 60)}`);
865
1015
  }
866
1016
  }
867
1017
  else {
868
1018
  params.to = targetAid;
869
1019
  const result = await this.callAndTrace('message.send', params);
870
1020
  if (!result || !result.message_id) {
871
- logger.warn(`[AUN] message.send returned no message_id: ${JSON.stringify(result)}`);
1021
+ logger.warn(`${this.logPrefix()} message.send returned no message_id: ${JSON.stringify(result)}`);
872
1022
  }
873
1023
  else {
874
- logger.info(`[AUN] message.send ok: to=${targetAid} mid=${result.message_id} text=${finalText.slice(0, 60)}`);
1024
+ logger.info(`${this.logPrefix()} message.send ok: to=${this.peerLabel(targetAid)} mid=${result.message_id} encrypt=${encrypt} text=${finalText.slice(0, 60)}`);
875
1025
  }
876
1026
  }
877
1027
  }
878
1028
  catch (e) {
879
1029
  if (encrypt && e instanceof E2EEError) {
880
1030
  this.peerE2ee.set(encryptTarget, { ok: false, ts: Date.now() });
881
- logger.warn(`[AUN] E2EE send failed to ${channelId}, retrying plaintext: ${e}`);
1031
+ logger.warn(`${this.logPrefix()} E2EE send failed to ${channelId}, retrying plaintext: ${e}`);
882
1032
  params.encrypt = false;
883
1033
  try {
884
1034
  if (isGroup) {
@@ -886,7 +1036,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
886
1036
  const result = await this.client.call('group.send', params);
887
1037
  this.trace('OUT', 'group.send.fallback.ok', { message_id: result?.message_id });
888
1038
  if (!result || !result.message_id) {
889
- logger.warn(`[AUN] group.send fallback returned no message_id: ${JSON.stringify(result)}`);
1039
+ logger.warn(`${this.logPrefix()} group.send fallback returned no message_id: ${JSON.stringify(result)}`);
890
1040
  }
891
1041
  }
892
1042
  else {
@@ -894,18 +1044,18 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
894
1044
  const result = await this.client.call('message.send', params);
895
1045
  this.trace('OUT', 'message.send.fallback.ok', { message_id: result?.message_id });
896
1046
  if (!result || !result.message_id) {
897
- logger.warn(`[AUN] message.send fallback returned no message_id: ${JSON.stringify(result)}`);
1047
+ logger.warn(`${this.logPrefix()} message.send fallback returned no message_id: ${JSON.stringify(result)}`);
898
1048
  }
899
1049
  }
900
1050
  }
901
1051
  catch (e2) {
902
1052
  this.trace('OUT', 'send.fallback.error', { channelId, error: String(e2) });
903
- logger.error(`[AUN] Plaintext fallback also failed to ${channelId}: ${e2}`);
1053
+ logger.error(`${this.logPrefix()} Plaintext fallback also failed to ${channelId}: ${e2}`);
904
1054
  }
905
1055
  }
906
1056
  else {
907
1057
  this.trace('OUT', 'send.error', { channelId, error: String(e) });
908
- logger.error(`[AUN] Send failed to ${channelId}: ${e}`);
1058
+ logger.error(`${this.logPrefix()} Send failed to ${channelId}: ${e}`);
909
1059
  }
910
1060
  }
911
1061
  }
@@ -917,7 +1067,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
917
1067
  * selector 使用 context: { type: 'task', id: taskId }
918
1068
  * 存储键:group_id/peer_aid + sender_aid + context.type + context.id
919
1069
  */
920
- async sendThought(channelId, taskId, payload) {
1070
+ async sendThought(channelId, taskId, payload, context) {
921
1071
  if (!this.connected || !this.client)
922
1072
  return;
923
1073
  if (!taskId)
@@ -925,46 +1075,49 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
925
1075
  // Multi-instance routing
926
1076
  const colonIdx = channelId.indexOf(':');
927
1077
  const targetId = colonIdx > 0 ? channelId.substring(0, colonIdx) : channelId;
1078
+ const encrypt = context?.metadata?.encrypted != null
1079
+ ? !!(context.metadata.encrypted)
1080
+ : this.shouldEncrypt(targetId);
928
1081
  const params = {
929
1082
  context: { type: 'task', id: taskId },
930
1083
  payload,
931
- encrypt: true,
1084
+ encrypt,
932
1085
  };
933
1086
  try {
934
1087
  const stage = payload?.stage ?? 'unknown';
935
1088
  if (this.isGroupId(channelId)) {
936
1089
  params.group_id = targetId;
937
1090
  await this.callAndTrace('group.thought.put', params);
938
- logger.info(`[AUN] thought.put ok group=${targetId} task=${taskId} stage=${stage}`);
1091
+ logger.info(`${this.logPrefix()} thought.put ok group=${targetId} task=${taskId} stage=${stage} encrypt=${encrypt}`);
939
1092
  }
940
1093
  else {
941
1094
  params.to = targetId;
942
1095
  await this.callAndTrace('message.thought.put', params);
943
- logger.info(`[AUN] thought.put ok p2p=${targetId} task=${taskId} stage=${stage}`);
1096
+ logger.info(`${this.logPrefix()} thought.put ok p2p=${this.peerLabel(targetId)} task=${taskId} stage=${stage} encrypt=${encrypt}`);
944
1097
  }
945
1098
  }
946
1099
  catch (e) {
947
1100
  const err = e;
948
- logger.debug(`[AUN] thought.put failed to ${channelId}: ${err?.name}(${err?.code})=${err?.message}`);
1101
+ logger.debug(`${this.logPrefix()} thought.put failed to ${channelId}: ${err?.name}(${err?.code})=${err?.message}`);
949
1102
  }
950
1103
  }
951
1104
  async sendFile(channelId, filePath, context) {
952
1105
  if (!this.connected || !this.client) {
953
- logger.warn('[AUN] Cannot sendFile: not connected');
1106
+ logger.warn(`${this.logPrefix()} Cannot sendFile: not connected`);
954
1107
  return;
955
1108
  }
956
1109
  const absPath = path.resolve(filePath);
957
1110
  if (!fs.existsSync(absPath)) {
958
- logger.warn(`[AUN] sendFile: file not found: ${absPath}`);
1111
+ logger.warn(`${this.logPrefix()} sendFile: file not found: ${absPath}`);
959
1112
  return;
960
1113
  }
961
1114
  const stat = fs.statSync(absPath);
962
1115
  if (stat.size === 0) {
963
- logger.warn('[AUN] sendFile: file is empty');
1116
+ logger.warn(`${this.logPrefix()} sendFile: file is empty`);
964
1117
  return;
965
1118
  }
966
1119
  if (stat.size > 10 * 1024 * 1024) {
967
- logger.warn(`[AUN] sendFile: file too large (${formatSize(stat.size)}, max 10 MB)`);
1120
+ logger.warn(`${this.logPrefix()} sendFile: file too large (${formatSize(stat.size)}, max 10 MB)`);
968
1121
  return;
969
1122
  }
970
1123
  const filename = path.basename(absPath);
@@ -1035,7 +1188,9 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
1035
1188
  filePayload.chat_id = channelId;
1036
1189
  }
1037
1190
  const encryptTarget = isGroup ? channelId : fileTargetAid;
1038
- const encrypt = this.shouldEncrypt(encryptTarget);
1191
+ const encrypt = context?.metadata?.encrypted != null
1192
+ ? !!(context.metadata.encrypted)
1193
+ : this.shouldEncrypt(encryptTarget);
1039
1194
  const params = { payload: filePayload, encrypt };
1040
1195
  try {
1041
1196
  if (isGroup) {
@@ -1044,7 +1199,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
1044
1199
  const result = await this.client.call('group.send', params);
1045
1200
  this.trace('OUT', 'group.send.file.ok', { message_id: result?.message_id });
1046
1201
  if (!result || !result.message_id) {
1047
- logger.warn(`[AUN] group.send.file returned no message_id: ${JSON.stringify(result)}`);
1202
+ logger.warn(`${this.logPrefix()} group.send.file returned no message_id: ${JSON.stringify(result)}`);
1048
1203
  }
1049
1204
  }
1050
1205
  else {
@@ -1053,7 +1208,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
1053
1208
  const result = await this.client.call('message.send', params);
1054
1209
  this.trace('OUT', 'message.send.file.ok', { message_id: result?.message_id });
1055
1210
  if (!result || !result.message_id) {
1056
- logger.warn(`[AUN] message.send.file returned no message_id: ${JSON.stringify(result)}`);
1211
+ logger.warn(`${this.logPrefix()} message.send.file returned no message_id: ${JSON.stringify(result)}`);
1057
1212
  }
1058
1213
  }
1059
1214
  }
@@ -1064,14 +1219,14 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
1064
1219
  });
1065
1220
  if (encrypt && sendErr instanceof E2EEError) {
1066
1221
  this.peerE2ee.set(encryptTarget, { ok: false, ts: Date.now() });
1067
- logger.warn(`[AUN] E2EE sendFile failed to ${channelId}, retrying plaintext: ${sendErr}`);
1222
+ logger.warn(`${this.logPrefix()} E2EE sendFile failed to ${channelId}, retrying plaintext: ${sendErr}`);
1068
1223
  params.encrypt = false;
1069
1224
  if (isGroup) {
1070
1225
  this.trace('OUT', 'group.send.file.fallback', params);
1071
1226
  const result = await this.client.call('group.send', params);
1072
1227
  this.trace('OUT', 'group.send.file.fallback.ok', { message_id: result?.message_id });
1073
1228
  if (!result || !result.message_id) {
1074
- logger.warn(`[AUN] group.send.file fallback returned no message_id: ${JSON.stringify(result)}`);
1229
+ logger.warn(`${this.logPrefix()} group.send.file fallback returned no message_id: ${JSON.stringify(result)}`);
1075
1230
  }
1076
1231
  }
1077
1232
  else {
@@ -1079,7 +1234,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
1079
1234
  const result = await this.client.call('message.send', params);
1080
1235
  this.trace('OUT', 'message.send.file.fallback.ok', { message_id: result?.message_id });
1081
1236
  if (!result || !result.message_id) {
1082
- logger.warn(`[AUN] message.send.file fallback returned no message_id: ${JSON.stringify(result)}`);
1237
+ logger.warn(`${this.logPrefix()} message.send.file fallback returned no message_id: ${JSON.stringify(result)}`);
1083
1238
  }
1084
1239
  }
1085
1240
  }
@@ -1087,11 +1242,11 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
1087
1242
  throw sendErr;
1088
1243
  }
1089
1244
  }
1090
- logger.info(`[AUN] File sent: ${filename} (${formatSize(stat.size)}) → ${channelId}`);
1245
+ logger.info(`${this.logPrefix()} File sent: ${filename} (${formatSize(stat.size)}) → ${channelId}`);
1091
1246
  }
1092
1247
  catch (e) {
1093
1248
  this.trace('OUT', 'sendFile.error', { channelId, filePath, error: String(e) });
1094
- logger.error(`[AUN] sendFile failed for ${channelId}: ${e}`);
1249
+ logger.error(`${this.logPrefix()} sendFile failed for ${channelId}: ${e}`);
1095
1250
  }
1096
1251
  }
1097
1252
  acknowledge(messageId) {
@@ -1127,20 +1282,22 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
1127
1282
  payload.chat_id = channelId;
1128
1283
  }
1129
1284
  const encryptTarget = isGroup ? channelId : statusTargetAid;
1130
- const encrypt = this.shouldEncrypt(encryptTarget);
1285
+ const encrypt = context?.metadata?.encrypted != null
1286
+ ? !!(context.metadata.encrypted)
1287
+ : this.shouldEncrypt(encryptTarget);
1131
1288
  const params = { payload, encrypt };
1132
1289
  const sendWithFallback = (method) => {
1133
1290
  this.client.call(method, params).catch(e => {
1134
1291
  if (encrypt && e instanceof E2EEError) {
1135
1292
  this.peerE2ee.set(encryptTarget, { ok: false, ts: Date.now() });
1136
- logger.warn(`[AUN] E2EE status send failed to ${channelId}, retrying plaintext`);
1293
+ logger.warn(`${this.logPrefix()} E2EE status send failed to ${channelId}, retrying plaintext`);
1137
1294
  params.encrypt = false;
1138
1295
  this.client.call(method, params).catch(e2 => {
1139
- logger.debug(`[AUN] Processing status fallback failed: ${e2}`);
1296
+ logger.debug(`${this.logPrefix()} Processing status fallback failed: ${e2}`);
1140
1297
  });
1141
1298
  }
1142
1299
  else {
1143
- logger.debug(`[AUN] Processing status failed: ${e}`);
1300
+ logger.debug(`${this.logPrefix()} Processing status failed: ${e}`);
1144
1301
  }
1145
1302
  });
1146
1303
  };
@@ -1154,7 +1311,10 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
1154
1311
  this.trace('OUT', 'message.send.status', params);
1155
1312
  sendWithFallback('message.send');
1156
1313
  }
1157
- logger.info(`[AUN] task.${status} task=${taskId} session=${sessionId} target=${channelId}`);
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}`);
1158
1318
  }
1159
1319
  sendCustomPayload(channelId, payload) {
1160
1320
  if (!this.client || !this.connected)
@@ -1184,7 +1344,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
1184
1344
  this.trace('OUT', 'message.send.custom.ok', { message_id: result?.message_id });
1185
1345
  }).catch(e => {
1186
1346
  this.trace('OUT', 'message.send.custom.error', { error: String(e) });
1187
- logger.warn(`[AUN] Custom payload failed: ${e}`);
1347
+ logger.warn(`${this.logPrefix()} Custom payload failed: ${e}`);
1188
1348
  });
1189
1349
  }
1190
1350
  async disconnect() {
@@ -1205,12 +1365,12 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
1205
1365
  this.client = null;
1206
1366
  }
1207
1367
  this.connected = false;
1208
- logger.info('[AUN] Disconnected');
1368
+ logger.info(`${this.logPrefix()} Disconnected`);
1209
1369
  if (this.traceStream) {
1210
1370
  this.traceStream.end();
1211
1371
  this.traceStream = null;
1212
1372
  }
1213
- logger.info('[AUN] Disconnected');
1373
+ logger.info(`${this.logPrefix()} Disconnected`);
1214
1374
  }
1215
1375
  // ── TS-layer reconnect (fallback when SDK auto_reconnect exhausted) ──
1216
1376
  scheduleReconnect() {
@@ -1220,25 +1380,25 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
1220
1380
  return;
1221
1381
  const delays = AUNChannel.RECONNECT_DELAYS;
1222
1382
  if (this.reconnectAttempt >= delays.length) {
1223
- logger.error(`[AUN] All ${delays.length} reconnect attempts exhausted, giving up`);
1383
+ logger.error(`${this.logPrefix()} All ${delays.length} reconnect attempts exhausted, giving up`);
1224
1384
  this.onChannelDown?.();
1225
1385
  return;
1226
1386
  }
1227
1387
  const delay = delays[this.reconnectAttempt];
1228
1388
  this.reconnectAttempt++;
1229
- logger.info(`[AUN] Scheduling reconnect #${this.reconnectAttempt}/${delays.length} in ${delay}s`);
1389
+ logger.info(`${this.logPrefix()} Scheduling reconnect #${this.reconnectAttempt}/${delays.length} in ${delay}s`);
1230
1390
  this.reconnectTimer = setTimeout(async () => {
1231
1391
  this.reconnectTimer = null;
1232
1392
  try {
1233
- logger.info(`[AUN] Reconnect #${this.reconnectAttempt} starting...`);
1393
+ logger.info(`${this.logPrefix()} Reconnect #${this.reconnectAttempt} starting...`);
1234
1394
  this.trace('OUT', 'reconnect.start', { attempt: this.reconnectAttempt });
1235
1395
  await this.initClient();
1236
1396
  this.trace('OUT', 'reconnect.ok', { attempt: this.reconnectAttempt });
1237
- logger.info(`[AUN] Reconnect #${this.reconnectAttempt} succeeded`);
1397
+ logger.info(`${this.logPrefix()} Reconnect #${this.reconnectAttempt} succeeded`);
1238
1398
  }
1239
1399
  catch (err) {
1240
1400
  this.trace('OUT', 'reconnect.error', { attempt: this.reconnectAttempt, error: String(err) });
1241
- logger.error(`[AUN] Reconnect #${this.reconnectAttempt} failed:`, err);
1401
+ logger.error(`${this.logPrefix()} Reconnect #${this.reconnectAttempt} failed:`, err);
1242
1402
  this.scheduleReconnect();
1243
1403
  }
1244
1404
  }, delay * 1000);
@@ -1314,7 +1474,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
1314
1474
  return info;
1315
1475
  }
1316
1476
  catch (e) {
1317
- logger.debug(`[AUN] fetchPeerInfo failed for ${aid}: ${e}`);
1477
+ logger.debug(`${this.logPrefix()} fetchPeerInfo failed for ${aid}: ${e}`);
1318
1478
  return { type: null }; // no agent.md → unknown
1319
1479
  }
1320
1480
  }
@@ -1367,7 +1527,7 @@ export class AUNChannelPlugin {
1367
1527
  sendCustomPayload: (id, payload) => channel.sendCustomPayload(id, payload),
1368
1528
  uploadAgentMd: (content) => channel.uploadAgentMd(content),
1369
1529
  downloadAgentMd: (aid) => channel.downloadAgentMd(aid),
1370
- putThought: (id, taskId, payload) => channel.sendThought(id, taskId, payload),
1530
+ putThought: (id, taskId, payload, context) => channel.sendThought(id, taskId, payload, context),
1371
1531
  _selfAid: () => channel.getStatus().aid,
1372
1532
  _selfName: () => channel.getSelfName(),
1373
1533
  };