@agentunion/fastaun 0.2.16 → 0.2.17

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.
Files changed (44) hide show
  1. package/dist/auth.d.ts +3 -0
  2. package/dist/auth.js +24 -24
  3. package/dist/auth.js.map +1 -1
  4. package/dist/client.d.ts +7 -0
  5. package/dist/client.js +219 -159
  6. package/dist/client.js.map +1 -1
  7. package/dist/config.d.ts +2 -0
  8. package/dist/config.js +2 -0
  9. package/dist/config.js.map +1 -1
  10. package/dist/e2ee-group.d.ts +5 -1
  11. package/dist/e2ee-group.js +14 -10
  12. package/dist/e2ee-group.js.map +1 -1
  13. package/dist/e2ee.d.ts +3 -0
  14. package/dist/e2ee.js +12 -4
  15. package/dist/e2ee.js.map +1 -1
  16. package/dist/events.d.ts +3 -0
  17. package/dist/events.js +11 -1
  18. package/dist/events.js.map +1 -1
  19. package/dist/group-id.d.ts +23 -0
  20. package/dist/group-id.js +94 -0
  21. package/dist/group-id.js.map +1 -0
  22. package/dist/keystore/aid-db.d.ts +1 -0
  23. package/dist/keystore/aid-db.js +4 -0
  24. package/dist/keystore/aid-db.js.map +1 -1
  25. package/dist/keystore/file.d.ts +5 -0
  26. package/dist/keystore/file.js +12 -6
  27. package/dist/keystore/file.js.map +1 -1
  28. package/dist/keystore/index.d.ts +2 -0
  29. package/dist/keystore/sqlite-backup.d.ts +5 -1
  30. package/dist/keystore/sqlite-backup.js +9 -6
  31. package/dist/keystore/sqlite-backup.js.map +1 -1
  32. package/dist/logger.d.ts +26 -3
  33. package/dist/logger.js +117 -40
  34. package/dist/logger.js.map +1 -1
  35. package/dist/secret-store/file-store.d.ts +4 -0
  36. package/dist/secret-store/file-store.js +5 -2
  37. package/dist/secret-store/file-store.js.map +1 -1
  38. package/dist/secret-store/index.d.ts +3 -0
  39. package/dist/secret-store/index.js +2 -2
  40. package/dist/secret-store/index.js.map +1 -1
  41. package/dist/transport.d.ts +3 -0
  42. package/dist/transport.js +9 -1
  43. package/dist/transport.js.map +1 -1
  44. package/package.json +1 -1
package/dist/client.js CHANGED
@@ -25,6 +25,7 @@ import { EventDispatcher } from './events.js';
25
25
  import { FileKeyStore } from './keystore/file.js';
26
26
  import { AUNLogger } from './logger.js';
27
27
  import { SQLiteBackup } from './keystore/sqlite-backup.js';
28
+ import { normalizeGroupId } from './group-id.js';
28
29
  import { AuthNamespace } from './namespaces/auth.js';
29
30
  import { CustodyNamespace } from './namespaces/custody.js';
30
31
  import { MetaNamespace } from './namespaces/meta.js';
@@ -32,18 +33,6 @@ import { RPCTransport } from './transport.js';
32
33
  import { AuthFlow } from './auth.js';
33
34
  import { SeqTracker } from './seq-tracker.js';
34
35
  import { isJsonObject, } from './types.js';
35
- // ── 日志辅助 ──────────────────────────────────────────────────
36
- /** 文件日志(模块级单例) */
37
- let _debugLogger = null;
38
- /** 简易日志:前缀 [aun_core.client] */
39
- function _clientLog(level, msg, ...args) {
40
- const ts = new Date().toISOString();
41
- const formatted = args.reduce((s, a) => s.replace('%s', String(a)), msg);
42
- // eslint-disable-next-line no-console
43
- console.log(`[${ts}] [aun_core.client] ${level}: ${formatted}`);
44
- if (_debugLogger)
45
- _debugLogger.log(`${level}: ${formatted}`);
46
- }
47
36
  /**
48
37
  * 递归排序键的 JSON 序列化(Canonical JSON for AUN)
49
38
  * 等价于 Python json.dumps(sort_keys=True, separators=(",",":"), ensure_ascii=False)
@@ -386,6 +375,8 @@ export class AUNClient {
386
375
  _reconnectActive = false;
387
376
  _reconnectAbort = null;
388
377
  _serverKicked = false;
378
+ _logger;
379
+ _clientLog;
389
380
  constructor(config, debug = false) {
390
381
  const rawConfig = { ...(config ?? {}) };
391
382
  this._configModel = configFromMap(rawConfig);
@@ -394,20 +385,27 @@ export class AUNClient {
394
385
  root_ca_path: this._configModel.rootCaPath,
395
386
  seed_password: this._configModel.seedPassword,
396
387
  };
397
- this._dispatcher = new EventDispatcher();
388
+ // 初始化 Logger(per-client 单例,必须最早创建)
389
+ const debugFlag = this._configModel.debug || debug;
390
+ this._logger = new AUNLogger({
391
+ debug: debugFlag,
392
+ aunPath: this._configModel.aunPath,
393
+ });
394
+ this._clientLog = this._logger.for('aun_core.client');
395
+ if (debugFlag) {
396
+ this._clientLog.info(`AUNClient 初始化完成 (debug=true, aunPath=${this._configModel.aunPath})`);
397
+ }
398
+ this._dispatcher = new EventDispatcher(this._logger.for('aun_core.events'));
398
399
  this._discovery = new GatewayDiscovery({ verifySsl: this._configModel.verifySsl });
399
- const defaultSQLiteBackup = new SQLiteBackup(join(this._configModel.aunPath, '.aun_backup', 'aun_backup.db'));
400
+ const defaultSQLiteBackup = new SQLiteBackup(join(this._configModel.aunPath, '.aun_backup', 'aun_backup.db'), { logger: this._logger.for('aun_core.keystore') });
400
401
  const keystore = new FileKeyStore(this._configModel.aunPath, {
401
402
  encryptionSeed: this._configModel.seedPassword ?? undefined,
402
403
  sqliteBackup: defaultSQLiteBackup,
404
+ logger: this._logger.for('aun_core.keystore'),
405
+ secretStoreLogger: this._logger.for('aun_core.secret-store'),
403
406
  });
404
407
  this._keystore = keystore;
405
408
  this._deviceId = getDeviceId(this._configModel.aunPath);
406
- // 初始化文件日志(仅 debug 模式)
407
- if (debug) {
408
- _debugLogger = new AUNLogger();
409
- _clientLog('info', 'AUNClient 初始化完成 (debug=true, aunPath=%s)', this._configModel.aunPath);
410
- }
411
409
  this._slotId = '';
412
410
  this._connectDeliveryMode = normalizeDeliveryModeConfig({ mode: 'fanout' });
413
411
  this._defaultConnectDeliveryMode = { ...this._connectDeliveryMode };
@@ -419,24 +417,28 @@ export class AUNClient {
419
417
  slotId: this._slotId,
420
418
  rootCaPath: this._configModel.rootCaPath ?? undefined,
421
419
  verifySsl: this._configModel.verifySsl,
420
+ logger: this._logger.for('aun_core.auth'),
422
421
  });
423
422
  this._transport = new RPCTransport({
424
423
  eventDispatcher: this._dispatcher,
425
424
  timeout: 10_000,
426
425
  onDisconnect: (err, closeCode) => this._handleTransportDisconnect(err, closeCode),
427
426
  verifySsl: this._configModel.verifySsl,
427
+ logger: this._logger.for('aun_core.transport'),
428
428
  });
429
429
  this._e2ee = new E2EEManager({
430
430
  identityFn: () => this._identity ?? {},
431
431
  deviceIdFn: () => this._deviceId,
432
432
  keystore,
433
433
  replayWindowSeconds: this._configModel.replayWindowSeconds,
434
+ logger: this._logger.for('aun_core.e2ee'),
434
435
  });
435
436
  this._groupE2ee = new GroupE2EEManager({
436
437
  identityFn: () => this._identity ?? {},
437
438
  keystore,
438
439
  senderCertResolver: (aid) => this._getVerifiedPeerCert(aid),
439
440
  initiatorCertResolver: (aid) => this._getVerifiedPeerCert(aid),
441
+ logger: this._logger.for('aun_core.e2ee-group'),
440
442
  });
441
443
  this.auth = new AuthNamespace(this);
442
444
  this.custody = new CustodyNamespace(this);
@@ -523,6 +525,7 @@ export class AUNClient {
523
525
  const closableKeyStore = this._keystore;
524
526
  closableKeyStore.close?.();
525
527
  this._state = 'closed';
528
+ this._logger.close();
526
529
  this._resetSeqTrackingState();
527
530
  return;
528
531
  }
@@ -530,6 +533,7 @@ export class AUNClient {
530
533
  const closableKeyStore = this._keystore;
531
534
  closableKeyStore.close?.();
532
535
  this._state = 'closed';
536
+ this._logger.close();
533
537
  await this._dispatcher.publish('connection.state', { state: this._state });
534
538
  this._resetSeqTrackingState();
535
539
  }
@@ -589,6 +593,11 @@ export class AUNClient {
589
593
  const p = { ...(params ?? {}) };
590
594
  this._validateOutboundCall(method, p);
591
595
  this._injectMessageCursorContext(method, p);
596
+ // group.* 方法的 group_id 归一化为 canonical 格式(兼容老/污染数据)
597
+ // 不对无域输入补本域——保持与历史行为兼容,交给服务端归一化
598
+ if (method.startsWith('group.') && p.group_id !== undefined && p.group_id !== null && p.group_id !== '') {
599
+ p.group_id = normalizeGroupId(p.group_id);
600
+ }
592
601
  // group.* 方法注入 device_id(服务端用于多设备消息路由)
593
602
  if (method.startsWith('group.') && this._deviceId && p.device_id === undefined) {
594
603
  p.device_id = this._deviceId;
@@ -603,8 +612,7 @@ export class AUNClient {
603
612
  if (encrypt) {
604
613
  return await this._sendEncrypted(p);
605
614
  }
606
- delete p.protected_headers;
607
- delete p.headers;
615
+ // encrypt=false:明文走通用 RPC 路径;protected_headers/headers 是信封元数据,加密与否都保留
608
616
  }
609
617
  // 自动加密:group.send 默认加密(encrypt 默认 True)
610
618
  if (method === 'group.send') {
@@ -613,24 +621,20 @@ export class AUNClient {
613
621
  if (encrypt) {
614
622
  return await this._sendGroupEncrypted(p);
615
623
  }
616
- delete p.protected_headers;
617
- delete p.headers;
618
624
  }
619
625
  if (method === 'group.thought.put') {
620
626
  const encrypt = p.encrypt ?? true;
621
627
  delete p.encrypt;
622
- if (!encrypt) {
623
- throw new ValidationError('group.thought.put requires encrypt=true');
628
+ if (encrypt) {
629
+ return await this._putGroupThoughtEncrypted(p);
624
630
  }
625
- return await this._putGroupThoughtEncrypted(p);
626
631
  }
627
632
  if (method === 'message.thought.put') {
628
633
  const encrypt = p.encrypt ?? true;
629
634
  delete p.encrypt;
630
- if (!encrypt) {
631
- throw new ValidationError('message.thought.put requires encrypt=true');
635
+ if (encrypt) {
636
+ return await this._putMessageThoughtEncrypted(p);
632
637
  }
633
- return await this._putMessageThoughtEncrypted(p);
634
638
  }
635
639
  // 关键操作自动附加客户端签名
636
640
  if (SIGNED_METHODS.has(method)) {
@@ -662,7 +666,7 @@ export class AUNClient {
662
666
  if (serverAck > 0) {
663
667
  const contig = this._seqTracker.getContiguousSeq(ns);
664
668
  if (contig < serverAck) {
665
- _clientLog('info', 'message.pull retention-floor 推进: ns=%s contiguous=%d -> server_ack_seq=%d', ns, contig, serverAck);
669
+ this._clientLog.info(`message.pull retention-floor 推进: ns=${ns} contiguous=${contig} -> server_ack_seq=${serverAck}`);
666
670
  this._seqTracker.forceContiguousSeq(ns, serverAck);
667
671
  }
668
672
  }
@@ -674,7 +678,7 @@ export class AUNClient {
674
678
  seq: contig,
675
679
  device_id: this._deviceId,
676
680
  slot_id: this._slotId,
677
- }).catch((e) => { _clientLog('debug', 'message.pull auto-ack 失败: %s', formatCaughtError(e)); });
681
+ }).catch((e) => { this._clientLog.debug(`message.pull auto-ack 失败: ${formatCaughtError(e)}`); });
678
682
  }
679
683
  }
680
684
  }
@@ -701,7 +705,7 @@ export class AUNClient {
701
705
  if (serverAck > 0) {
702
706
  const contig = this._seqTracker.getContiguousSeq(ns);
703
707
  if (contig < serverAck) {
704
- _clientLog('info', 'group.pull retention-floor 推进: ns=%s contiguous=%d -> cursor.current_seq=%d', ns, contig, serverAck);
708
+ this._clientLog.info(`group.pull retention-floor 推进: ns=${ns} contiguous=${contig} -> cursor.current_seq=${serverAck}`);
705
709
  this._seqTracker.forceContiguousSeq(ns, serverAck);
706
710
  }
707
711
  }
@@ -716,7 +720,7 @@ export class AUNClient {
716
720
  msg_seq: contig,
717
721
  device_id: this._deviceId,
718
722
  slot_id: this._slotId,
719
- }).catch((e) => { _clientLog('debug', 'group.pull auto-ack 失败: group=%s %s', gid, formatCaughtError(e)); });
723
+ }).catch((e) => { this._clientLog.debug(`group.pull auto-ack 失败: group=${gid} ${formatCaughtError(e)}`); });
720
724
  }
721
725
  }
722
726
  }
@@ -762,7 +766,7 @@ export class AUNClient {
762
766
  // P0-12: await rotation 完成(带超时兜底),确保后续 group.send 使用新 epoch
763
767
  const rotationPromise = this._maybeLeadRotateGroupEpoch(groupId, this._membershipRotationTriggerId(groupId, result), expectedEpoch, allowMember);
764
768
  const timeoutPromise = new Promise((resolve) => globalThis.setTimeout(resolve, 5000));
765
- await Promise.race([rotationPromise, timeoutPromise]).catch((exc) => _clientLog('warn', 'membership RPC epoch rotation fallback failed: %s', formatCaughtError(exc)));
769
+ await Promise.race([rotationPromise, timeoutPromise]).catch((exc) => this._clientLog.warn(`membership RPC epoch rotation fallback failed: ${formatCaughtError(exc)}`));
766
770
  }
767
771
  }
768
772
  return result;
@@ -881,7 +885,7 @@ export class AUNClient {
881
885
  catch (exc) {
882
886
  if (!isRetryablePeerMaterialError(exc))
883
887
  throw exc;
884
- _clientLog('warn', 'peer cert/prekey mismatch for %s, refreshing and retrying once', toAid);
888
+ this._clientLog.warn(`peer cert/prekey mismatch for ${toAid}, refreshing and retrying once`);
885
889
  }
886
890
  return await sendAttempt(true);
887
891
  }
@@ -993,7 +997,7 @@ export class AUNClient {
993
997
  }
994
998
  catch (e) {
995
999
  // 旧设备的 prekey 可能携带已轮换的证书指纹,跳过该设备的自同步副本
996
- _clientLog('warn', `self-sync 跳过设备 ${deviceId}: 证书解析失败 (${e}),可能是旧 prekey`);
1000
+ this._clientLog.warn(`self-sync 跳过设备 ${deviceId}: 证书解析失败 (${e}),可能是旧 prekey`);
997
1001
  continue;
998
1002
  }
999
1003
  const [envelope, encryptResult] = this._encryptCopyPayload({
@@ -1031,7 +1035,7 @@ export class AUNClient {
1031
1035
  mode: encryptResult.mode,
1032
1036
  reason: encryptResult.degradation_reason,
1033
1037
  }).catch((exc) => {
1034
- _clientLog('warn', '发布 e2ee.degraded 事件失败: %s', formatCaughtError(exc));
1038
+ this._clientLog.warn(`发布 e2ee.degraded 事件失败: ${formatCaughtError(exc)}`);
1035
1039
  });
1036
1040
  }
1037
1041
  }
@@ -1096,7 +1100,7 @@ export class AUNClient {
1096
1100
  }
1097
1101
  catch (exc) {
1098
1102
  if (attempt === 0 && this._isRecoverableGroupEpochError(exc)) {
1099
- _clientLog('warn', '群 %s 调用 %s 时 epoch 已过旧,恢复密钥后重加密重试一次: %s', groupId, method, formatCaughtError(exc));
1103
+ this._clientLog.warn(`群 ${groupId} 调用 ${method} 时 epoch 已过旧,恢复密钥后重加密重试一次: ${formatCaughtError(exc)}`);
1100
1104
  ({ sendParams, groupId } = await this._prepareGroupEncryptedRpcParams(method, params, options, true));
1101
1105
  continue;
1102
1106
  }
@@ -1182,11 +1186,11 @@ export class AUNClient {
1182
1186
  }
1183
1187
  if (messages.length > 0) {
1184
1188
  this._saveSeqTrackerState();
1185
- _clientLog('info', '惰性同步群 %s: pull %d 条消息, after_seq=%d', groupId, messages.length, afterSeq);
1189
+ this._clientLog.info(`惰性同步群 ${groupId}: pull ${messages.length} 条消息, after_seq=${afterSeq}`);
1186
1190
  }
1187
1191
  }
1188
1192
  catch (exc) {
1189
- _clientLog('warn', '惰性同步群 %s 失败: %s', groupId, formatCaughtError(exc));
1193
+ this._clientLog.warn(`惰性同步群 ${groupId} 失败: ${formatCaughtError(exc)}`);
1190
1194
  }
1191
1195
  }
1192
1196
  /** 惰性同步:首次激活 P2P 通道时 pull 最近消息,建立 seq 基线 */
@@ -1209,11 +1213,11 @@ export class AUNClient {
1209
1213
  }
1210
1214
  if (messages.length > 0) {
1211
1215
  this._saveSeqTrackerState();
1212
- _clientLog('info', '惰性同步 P2P: pull %d 条消息, after_seq=%d', messages.length, afterSeq);
1216
+ this._clientLog.info(`惰性同步 P2P: pull ${messages.length} 条消息, after_seq=${afterSeq}`);
1213
1217
  }
1214
1218
  }
1215
1219
  catch (exc) {
1216
- _clientLog('warn', '惰性同步 P2P 失败: %s', formatCaughtError(exc));
1220
+ this._clientLog.warn(`惰性同步 P2P 失败: ${formatCaughtError(exc)}`);
1217
1221
  }
1218
1222
  }
1219
1223
  _isGroupEpochTooOldError(exc) {
@@ -1269,10 +1273,10 @@ export class AUNClient {
1269
1273
  encrypt: true,
1270
1274
  persist_required: true,
1271
1275
  });
1272
- _clientLog('info', '已向 %s 请求群 %s 的 epoch %s 密钥', targetAid, groupId, epoch);
1276
+ this._clientLog.info(`已向 ${targetAid} 请求群 ${groupId} 的 epoch ${epoch} 密钥`);
1273
1277
  }
1274
1278
  catch (exc) {
1275
- _clientLog('warn', '向 %s 请求群 %s 密钥失败: %s', targetAid, groupId, formatCaughtError(exc));
1279
+ this._clientLog.warn(`向 ${targetAid} 请求群 ${groupId} 密钥失败: ${formatCaughtError(exc)}`);
1276
1280
  }
1277
1281
  }
1278
1282
  async _requestGroupKeyFromCandidates(groupId, serverEpoch, epochResult) {
@@ -1287,7 +1291,7 @@ export class AUNClient {
1287
1291
  const secretData = this._groupE2ee.loadSecret(groupId, 1);
1288
1292
  if (!secretData || secretData.pending_rotation_id)
1289
1293
  return epochResult;
1290
- _clientLog('warn', '群 %s 检测到本地 epoch 1 已存在但服务端 epoch 仍为 0,尝试补同步初始 epoch', groupId);
1294
+ this._clientLog.warn(`群 ${groupId} 检测到本地 epoch 1 已存在但服务端 epoch 仍为 0,尝试补同步初始 epoch`);
1291
1295
  await this._syncEpochToServer(groupId);
1292
1296
  try {
1293
1297
  const refreshed = await this.call('group.e2ee.get_epoch', { group_id: groupId });
@@ -1295,7 +1299,7 @@ export class AUNClient {
1295
1299
  return refreshed;
1296
1300
  }
1297
1301
  catch (exc) {
1298
- _clientLog('warn', '群 %s 初始 epoch 补同步后刷新服务端 epoch 失败: %s', groupId, formatCaughtError(exc));
1302
+ this._clientLog.warn(`群 ${groupId} 初始 epoch 补同步后刷新服务端 epoch 失败: ${formatCaughtError(exc)}`);
1299
1303
  }
1300
1304
  return epochResult;
1301
1305
  }
@@ -1312,7 +1316,7 @@ export class AUNClient {
1312
1316
  catch (exc) {
1313
1317
  if (strict)
1314
1318
  throw new StateError(`group ${groupId} failed to query server epoch before retry: ${formatCaughtError(exc)}`);
1315
- _clientLog('warn', 'group %s epoch precheck failed: %s', groupId, formatCaughtError(exc));
1319
+ this._clientLog.warn(`group ${groupId} epoch precheck failed: ${formatCaughtError(exc)}`);
1316
1320
  return;
1317
1321
  }
1318
1322
  let serverEpoch = Number(epochResult.committed_epoch ?? epochResult.epoch ?? 0);
@@ -1360,7 +1364,7 @@ export class AUNClient {
1360
1364
  throw new StateError(`group ${groupId} epoch rotation has not completed`);
1361
1365
  }
1362
1366
  }
1363
- _clientLog('warn', 'group %s local epoch=%s < server epoch=%s; requesting key recovery', groupId, effectiveLocalEpoch, serverEpoch);
1367
+ this._clientLog.warn(`group ${groupId} local epoch=${effectiveLocalEpoch} < server epoch=${serverEpoch}; requesting key recovery`);
1364
1368
  await this._recoverGroupEpochKey(groupId, serverEpoch, '', 5000);
1365
1369
  const deadline = Date.now() + 5000;
1366
1370
  while (Date.now() < deadline) {
@@ -1384,7 +1388,7 @@ export class AUNClient {
1384
1388
  members = isJsonObject(membersResult) ? membersResult.members : null;
1385
1389
  }
1386
1390
  catch (exc) {
1387
- _clientLog('debug', '群 %s 成员 epoch floor 预检跳过: %s', groupId, formatCaughtError(exc));
1391
+ this._clientLog.debug(`群 ${groupId} 成员 epoch floor 预检跳过: ${formatCaughtError(exc)}`);
1388
1392
  return;
1389
1393
  }
1390
1394
  let maxMinReadEpoch = 0;
@@ -1399,7 +1403,7 @@ export class AUNClient {
1399
1403
  }
1400
1404
  if (maxMinReadEpoch <= committedEpoch)
1401
1405
  return;
1402
- _clientLog('warn', '群 %s 成员 min_read_epoch 高于 committed epoch,按 committed epoch 继续发送: committed=%s floor=%s', groupId, committedEpoch, maxMinReadEpoch);
1406
+ this._clientLog.warn(`群 ${groupId} 成员 min_read_epoch 高于 committed epoch,按 committed epoch 继续发送: committed=${committedEpoch} floor=${maxMinReadEpoch}`);
1403
1407
  return;
1404
1408
  }
1405
1409
  }
@@ -1410,7 +1414,7 @@ export class AUNClient {
1410
1414
  return epochResult;
1411
1415
  }
1412
1416
  catch (exc) {
1413
- _clientLog('warn', '群 %s 查询 committed epoch 状态失败,回退本地 epoch: %s', groupId, formatCaughtError(exc));
1417
+ this._clientLog.warn(`群 ${groupId} 查询 committed epoch 状态失败,回退本地 epoch: ${formatCaughtError(exc)}`);
1414
1418
  }
1415
1419
  const localEpoch = await this._groupE2ee.currentEpoch(groupId);
1416
1420
  return { epoch: localEpoch ?? 0, committed_epoch: localEpoch ?? 0 };
@@ -1438,7 +1442,7 @@ export class AUNClient {
1438
1442
  let committedRotation = isJsonObject(epochResult.committed_rotation) ? epochResult.committed_rotation : null;
1439
1443
  if (await this._committedRotationMembershipGap(groupId, committedEpoch, committedRotation)) {
1440
1444
  const allowMember = await this._groupAllowsMemberEpochRotation(groupId);
1441
- _clientLog('warn', '群 %s committed epoch %s 的成员快照与当前成员不一致,触发成员变更轮换修复', groupId, committedEpoch);
1445
+ this._clientLog.warn(`群 ${groupId} committed epoch ${committedEpoch} 的成员快照与当前成员不一致,触发成员变更轮换修复`);
1442
1446
  await this._maybeLeadRotateGroupEpoch(groupId, `${groupId}:committed_membership_gap:aid:${this._aid}:epoch:${committedEpoch}`, committedEpoch, allowMember);
1443
1447
  const refreshed = await this._committedGroupEpochState(groupId);
1444
1448
  const refreshedCommittedEpoch = Number(refreshed.committed_epoch ?? refreshed.epoch ?? committedEpoch);
@@ -1455,7 +1459,7 @@ export class AUNClient {
1455
1459
  return committedEpoch;
1456
1460
  }
1457
1461
  const pendingRotationId = secretData ? String(secretData.pending_rotation_id ?? '') : '';
1458
- _clientLog('warn', '群 %s epoch %s 本地 pending key 未匹配服务端 committed rotation,先恢复密钥: local_rotation=%s', groupId, committedEpoch, pendingRotationId || '-');
1462
+ this._clientLog.warn(`群 ${groupId} epoch ${committedEpoch} 本地 pending key 未匹配服务端 committed rotation,先恢复密钥: local_rotation=${pendingRotationId || '-'}`);
1459
1463
  await this._recoverGroupEpochKey(groupId, committedEpoch, '', 5000);
1460
1464
  let refreshed = await this._committedGroupEpochState(groupId);
1461
1465
  const refreshedCommittedEpoch = Number(refreshed.committed_epoch ?? refreshed.epoch ?? committedEpoch);
@@ -1500,13 +1504,13 @@ export class AUNClient {
1500
1504
  if (activeMembers.join('\n') !== expectedMembers.join('\n')) {
1501
1505
  const missing = activeMembers.filter((aid) => !expectedMembers.includes(aid));
1502
1506
  const extra = expectedMembers.filter((aid) => !activeMembers.includes(aid));
1503
- _clientLog('info', '群 %s committed membership gap: epoch=%s missing=%s extra=%s', groupId, committedEpoch, JSON.stringify(missing), JSON.stringify(extra));
1507
+ this._clientLog.info(`群 ${groupId} committed membership gap: epoch=${committedEpoch} missing=${JSON.stringify(missing)} extra=${JSON.stringify(extra)}`);
1504
1508
  return true;
1505
1509
  }
1506
1510
  return false;
1507
1511
  }
1508
1512
  catch (exc) {
1509
- _clientLog('debug', '查询当前成员失败,无法判断 committed membership gap: group=%s err=%s', groupId, formatCaughtError(exc));
1513
+ this._clientLog.debug(`查询当前成员失败,无法判断 committed membership gap: group=${groupId} err=${formatCaughtError(exc)}`);
1510
1514
  return false;
1511
1515
  }
1512
1516
  }
@@ -1558,7 +1562,7 @@ export class AUNClient {
1558
1562
  async _onRawMessageReceived(data) {
1559
1563
  // 异步处理,不阻塞事件调度
1560
1564
  this._processAndPublishMessage(data).catch((exc) => {
1561
- _clientLog('warn', 'P2P 消息解密失败: %s', formatCaughtError(exc));
1565
+ this._clientLog.warn(`P2P 消息解密失败: ${formatCaughtError(exc)}`);
1562
1566
  // H26: 不再投递原始密文 payload;改发 message.undecryptable 事件,仅携带安全 header
1563
1567
  if (isJsonObject(data)) {
1564
1568
  const safeEvent = {
@@ -1594,7 +1598,7 @@ export class AUNClient {
1594
1598
  const ns = `p2p:${this._aid}`;
1595
1599
  const needPull = this._seqTracker.onMessageSeq(ns, seq);
1596
1600
  if (needPull) {
1597
- this._fillP2pGap().catch(exc => _clientLog('warn', '后台补洞触发失败: %s', formatCaughtError(exc)));
1601
+ this._fillP2pGap().catch(exc => this._clientLog.warn(`后台补洞触发失败: ${formatCaughtError(exc)}`));
1598
1602
  }
1599
1603
  // auto-ack contiguous_seq
1600
1604
  const contig = this._seqTracker.getContiguousSeq(ns);
@@ -1603,7 +1607,7 @@ export class AUNClient {
1603
1607
  seq: contig,
1604
1608
  device_id: this._deviceId,
1605
1609
  slot_id: this._slotId,
1606
- }).catch((e) => { _clientLog('debug', 'P2P auto-ack 失败: %s', formatCaughtError(e)); });
1610
+ }).catch((e) => { this._clientLog.debug(`P2P auto-ack 失败: ${formatCaughtError(e)}`); });
1607
1611
  }
1608
1612
  // 即时持久化 cursor,异常断连后不回退
1609
1613
  this._saveSeqTrackerState();
@@ -1620,7 +1624,7 @@ export class AUNClient {
1620
1624
  /** 处理群组消息推送:自动解密后 re-publish */
1621
1625
  async _onRawGroupMessageCreated(data) {
1622
1626
  this._processAndPublishGroupMessage(data).catch((exc) => {
1623
- _clientLog('warn', '群消息解密失败: %s', formatCaughtError(exc));
1627
+ this._clientLog.warn(`群消息解密失败: ${formatCaughtError(exc)}`);
1624
1628
  // H26: 不再投递原始密文 payload;改发 group.message_undecryptable 事件
1625
1629
  if (isJsonObject(data)) {
1626
1630
  const safeEvent = {
@@ -1665,7 +1669,7 @@ export class AUNClient {
1665
1669
  const ns = `group:${groupId}`;
1666
1670
  const needPull = this._seqTracker.onMessageSeq(ns, seq);
1667
1671
  if (needPull) {
1668
- this._fillGroupGap(groupId).catch(exc => _clientLog('warn', '后台补洞触发失败: %s', formatCaughtError(exc)));
1672
+ this._fillGroupGap(groupId).catch(exc => this._clientLog.warn(`后台补洞触发失败: ${formatCaughtError(exc)}`));
1669
1673
  }
1670
1674
  const contig = this._seqTracker.getContiguousSeq(ns);
1671
1675
  if (contig > 0) {
@@ -1674,7 +1678,7 @@ export class AUNClient {
1674
1678
  msg_seq: contig,
1675
1679
  device_id: this._deviceId,
1676
1680
  slot_id: this._slotId,
1677
- }).catch((e) => { _clientLog('debug', '群消息 auto-ack 失败: group=%s %s', groupId, formatCaughtError(e)); });
1681
+ }).catch((e) => { this._clientLog.debug(`群消息 auto-ack 失败: group=${groupId} ${formatCaughtError(e)}`); });
1678
1682
  }
1679
1683
  this._saveSeqTrackerState();
1680
1684
  }
@@ -1742,7 +1746,7 @@ export class AUNClient {
1742
1746
  }
1743
1747
  }
1744
1748
  catch (exc) {
1745
- _clientLog('debug', '自动 pull 群消息失败: %s', formatCaughtError(exc));
1749
+ this._clientLog.debug(`自动 pull 群消息失败: ${formatCaughtError(exc)}`);
1746
1750
  }
1747
1751
  await this._publishAppEvent('group.message_created', notification);
1748
1752
  }
@@ -1785,7 +1789,7 @@ export class AUNClient {
1785
1789
  }
1786
1790
  }
1787
1791
  catch (exc) {
1788
- _clientLog('warn', '群消息补洞失败: %s', formatCaughtError(exc));
1792
+ this._clientLog.warn(`群消息补洞失败: ${formatCaughtError(exc)}`);
1789
1793
  }
1790
1794
  finally {
1791
1795
  this._gapFillDone.delete(dedupKey);
@@ -1830,7 +1834,7 @@ export class AUNClient {
1830
1834
  }
1831
1835
  }
1832
1836
  catch (exc) {
1833
- _clientLog('warn', 'P2P 消息补洞失败: %s', formatCaughtError(exc));
1837
+ this._clientLog.warn(`P2P 消息补洞失败: ${formatCaughtError(exc)}`);
1834
1838
  }
1835
1839
  finally {
1836
1840
  this._gapFillDone.delete(dedupKey);
@@ -1987,7 +1991,7 @@ export class AUNClient {
1987
1991
  if (serverAck > 0) {
1988
1992
  const contigBefore = this._seqTracker.getContiguousSeq(ns);
1989
1993
  if (contigBefore < serverAck) {
1990
- _clientLog('info', 'group.pull_events retention-floor 推进: ns=%s contiguous=%d -> cursor.current_seq=%d', ns, contigBefore, serverAck);
1994
+ this._clientLog.info(`group.pull_events retention-floor 推进: ns=${ns} contiguous=${contigBefore} -> cursor.current_seq=${serverAck}`);
1991
1995
  this._seqTracker.forceContiguousSeq(ns, serverAck);
1992
1996
  }
1993
1997
  }
@@ -2000,7 +2004,7 @@ export class AUNClient {
2000
2004
  event_seq: contig,
2001
2005
  device_id: this._deviceId,
2002
2006
  slot_id: this._slotId,
2003
- }).catch((e) => { _clientLog('debug', '群事件 auto-ack 失败: group=%s %s', groupId, formatCaughtError(e)); });
2007
+ }).catch((e) => { this._clientLog.debug(`群事件 auto-ack 失败: group=${groupId} ${formatCaughtError(e)}`); });
2004
2008
  }
2005
2009
  for (const evt of events) {
2006
2010
  if (isJsonObject(evt)) {
@@ -2022,7 +2026,7 @@ export class AUNClient {
2022
2026
  }
2023
2027
  }
2024
2028
  catch (exc) {
2025
- _clientLog('warn', '群事件补洞失败: %s', formatCaughtError(exc));
2029
+ this._clientLog.warn(`群事件补洞失败: ${formatCaughtError(exc)}`);
2026
2030
  }
2027
2031
  finally {
2028
2032
  this._gapFillDone.delete(dedupKey);
@@ -2179,12 +2183,12 @@ export class AUNClient {
2179
2183
  event_seq: contig,
2180
2184
  device_id: this._deviceId,
2181
2185
  slot_id: this._slotId,
2182
- }).catch((e) => { _clientLog('debug', '群事件推送 auto-ack 失败: group=%s %s', groupId, formatCaughtError(e)); });
2186
+ }).catch((e) => { this._clientLog.debug(`群事件推送 auto-ack 失败: group=${groupId} ${formatCaughtError(e)}`); });
2183
2187
  }
2184
2188
  }
2185
2189
  // 仅在真实 event gap 时才触发补拉(补洞回来的事件不再触发新补洞)
2186
2190
  if (needPull && groupId && !d._from_gap_fill) {
2187
- this._fillGroupEventGap(groupId).catch(exc => _clientLog('warn', '后台补洞触发失败: %s', formatCaughtError(exc)));
2191
+ this._fillGroupEventGap(groupId).catch(exc => this._clientLog.warn(`后台补洞触发失败: ${formatCaughtError(exc)}`));
2188
2192
  }
2189
2193
  // 成员退出或被踢 → 剩余 admin/owner 自动补位轮换
2190
2194
  // H21: 避免 epoch 轮换风暴——所有剩余 admin 同时收到事件不能都发起轮换,
@@ -2195,7 +2199,7 @@ export class AUNClient {
2195
2199
  {
2196
2200
  const expectedEpoch = this._membershipRotationExpectedEpoch(d);
2197
2201
  if (expectedEpoch === null) {
2198
- _clientLog('debug', 'membership event without old_epoch skipped for epoch rotation: aid=%s group=%s action=%s event_seq=%s', this._aid ?? '', groupId, String(d.action ?? ''), String(d.event_seq ?? ''));
2202
+ this._clientLog.debug(`membership event without old_epoch skipped for epoch rotation: aid=${this._aid ?? ''} group=${groupId} action=${String(d.action ?? '')} event_seq=${String(d.event_seq ?? '')}`);
2199
2203
  }
2200
2204
  else {
2201
2205
  this._maybeLeadRotateGroupEpoch(groupId, this._membershipRotationTriggerId(groupId, d), expectedEpoch).catch((exc) => this._logE2eeError('rotate_epoch', groupId, '', exc));
@@ -2213,7 +2217,7 @@ export class AUNClient {
2213
2217
  const expectedEpoch = this._membershipRotationExpectedEpoch(d);
2214
2218
  const joinedAids = this._joinedMemberAidsFromPayload(d);
2215
2219
  const isSelfJoining = joinedAids.includes(this._aid ?? '') && (action === 'joined' || action === 'invite_code_used');
2216
- _clientLog('warn', 'DEBUG: group.changed action=%s groupId=%s joinedAids=%s myAid=%s isSelfJoining=%s expectedEpoch=%s', action, groupId, JSON.stringify(joinedAids), this._aid, String(isSelfJoining), String(expectedEpoch));
2220
+ this._clientLog.warn(`DEBUG: group.changed action=${action} groupId=${groupId} joinedAids=${JSON.stringify(joinedAids)} myAid=${this._aid} isSelfJoining=${String(isSelfJoining)} expectedEpoch=${String(expectedEpoch)}`);
2217
2221
  if (isSelfJoining || (action === 'joined' || action === 'invite_code_used')) {
2218
2222
  // open/invite_code 群:所有在线成员都参与延迟轮换
2219
2223
  // 新成员自己延迟更长,优先让其他在线成员先轮换
@@ -2267,7 +2271,7 @@ export class AUNClient {
2267
2271
  if (cs && isJsonObject(cs)) {
2268
2272
  const verified = await this._verifyEventSignatureAsync(d, cs);
2269
2273
  if (verified === false) {
2270
- _clientLog('warn', 'state_committed 提交者签名验证失败 group=%s', groupId);
2274
+ this._clientLog.warn(`state_committed 提交者签名验证失败 group=${groupId}`);
2271
2275
  return;
2272
2276
  }
2273
2277
  d._verified = verified;
@@ -2282,7 +2286,7 @@ export class AUNClient {
2282
2286
  const loadFn = this._keystore.loadGroupState;
2283
2287
  const localState = loadFn ? loadFn.call(this._keystore, groupId) : null;
2284
2288
  if (localState && localState.state_hash && localState.state_hash !== prevStateHash) {
2285
- _clientLog('warn', 'state_hash 链不连续 group=%s local_sv=%d event_sv=%d', groupId, localState.state_version, stateVersion);
2289
+ this._clientLog.warn(`state_hash 链不连续 group=${groupId} local_sv=${localState.state_version} event_sv=${stateVersion}`);
2286
2290
  // 回源同步
2287
2291
  try {
2288
2292
  const serverState = await this._transport.call('group.get_state', { group_id: groupId });
@@ -2302,7 +2306,7 @@ export class AUNClient {
2302
2306
  members: sMembers, policy: sPolicy, prevStateHash: sPrev,
2303
2307
  });
2304
2308
  if (computed !== sHash) {
2305
- _clientLog('warn', '回源 state_hash 验证失败 group=%s sv=%d expected=%s got=%s', groupId, sv, sHash, computed);
2309
+ this._clientLog.warn(`回源 state_hash 验证失败 group=${groupId} sv=${sv} expected=${sHash} got=${computed}`);
2306
2310
  return;
2307
2311
  }
2308
2312
  }
@@ -2313,7 +2317,7 @@ export class AUNClient {
2313
2317
  }
2314
2318
  }
2315
2319
  catch (exc) {
2316
- _clientLog('warn', 'state 回源失败 group=%s: %s', groupId, formatCaughtError(exc));
2320
+ this._clientLog.warn(`state 回源失败 group=${groupId}: ${formatCaughtError(exc)}`);
2317
2321
  }
2318
2322
  return;
2319
2323
  }
@@ -2325,7 +2329,7 @@ export class AUNClient {
2325
2329
  members, policy, prevStateHash,
2326
2330
  });
2327
2331
  if (computed !== stateHash) {
2328
- _clientLog('warn', 'state_hash 重算不匹配 group=%s sv=%d expected=%s got=%s', groupId, stateVersion, stateHash, computed);
2332
+ this._clientLog.warn(`state_hash 重算不匹配 group=${groupId} sv=${stateVersion} expected=${stateHash} got=${computed}`);
2329
2333
  return;
2330
2334
  }
2331
2335
  // 3. 更新本地存储
@@ -2349,7 +2353,7 @@ export class AUNClient {
2349
2353
  if (this._closing || this._state !== 'connected')
2350
2354
  return;
2351
2355
  if (Date.now() - started > 20000) {
2352
- _clientLog('warn', 'group epoch rotation still in-flight; skip pending trigger (group=%s trigger=%s)', groupId, triggerId || '-');
2356
+ this._clientLog.warn(`group epoch rotation still in-flight; skip pending trigger (group=${groupId} trigger=${triggerId || '-'})`);
2353
2357
  return;
2354
2358
  }
2355
2359
  await new Promise((resolve) => setTimeout(resolve, 200));
@@ -2444,11 +2448,11 @@ export class AUNClient {
2444
2448
  });
2445
2449
  return;
2446
2450
  }
2447
- _clientLog('info', '[H21] leader 未完成 epoch 轮换,非 leader 兜底: group=%s myAid=%s', groupId, myAid);
2451
+ this._clientLog.info(`[H21] leader 未完成 epoch 轮换,非 leader 兜底: group=${groupId} myAid=${myAid}`);
2448
2452
  await this._rotateGroupEpoch(groupId, triggerId, expectedEpoch);
2449
2453
  }
2450
2454
  catch (exc) {
2451
- _clientLog('warn', '_maybeLeadRotateGroupEpoch 失败: %s', formatCaughtError(exc));
2455
+ this._clientLog.warn(`_maybeLeadRotateGroupEpoch 失败: ${formatCaughtError(exc)}`);
2452
2456
  }
2453
2457
  finally {
2454
2458
  this._groupEpochRotationInflight.delete(groupId);
@@ -2466,7 +2470,7 @@ export class AUNClient {
2466
2470
  this._groupE2ee.removeGroup(groupId);
2467
2471
  }
2468
2472
  catch (exc) {
2469
- _clientLog('warn', '清理解散群组 %s epoch 密钥失败: %s', groupId, formatCaughtError(exc));
2473
+ this._clientLog.warn(`清理解散群组 ${groupId} epoch 密钥失败: ${formatCaughtError(exc)}`);
2470
2474
  }
2471
2475
  // 2. 清理 seq_tracker 中的群消息和群事件命名空间
2472
2476
  this._seqTracker.removeNamespace(`group:${groupId}`);
@@ -2483,7 +2487,7 @@ export class AUNClient {
2483
2487
  this._pushedSeqs.delete(`group_event:${groupId}`);
2484
2488
  this._pendingOrderedMsgs.delete(`group:${groupId}`);
2485
2489
  this._pendingDecryptMsgs.delete(`group:${groupId}`);
2486
- _clientLog('info', '已清理解散群组 %s 的本地状态', groupId);
2490
+ this._clientLog.info(`已清理解散群组 ${groupId} 的本地状态`);
2487
2491
  }
2488
2492
  /** 同步验签群事件 client_signature。返回 true/false/"pending"。 */
2489
2493
  /**
@@ -2518,7 +2522,7 @@ export class AUNClient {
2518
2522
  if (expectedFP) {
2519
2523
  const actualFP = 'sha256:' + certObj.fingerprint256.replace(/:/g, '').toLowerCase();
2520
2524
  if (actualFP !== expectedFP) {
2521
- _clientLog('warn', '验签失败:证书指纹不匹配 aid=%s', sigAid);
2525
+ this._clientLog.warn(`验签失败:证书指纹不匹配 aid=${sigAid}`);
2522
2526
  return false;
2523
2527
  }
2524
2528
  }
@@ -2529,7 +2533,7 @@ export class AUNClient {
2529
2533
  const pubKey = certObj.publicKey;
2530
2534
  const ok = crypto.verify('SHA256', signData, pubKey, Buffer.from(sigB64, 'base64'));
2531
2535
  if (!ok) {
2532
- _clientLog('warn', '群事件验签失败 aid=%s method=%s', sigAid, method);
2536
+ this._clientLog.warn(`群事件验签失败 aid=${sigAid} method=${method}`);
2533
2537
  // P1-16: 签名失败统一发布事件
2534
2538
  this._dispatcher.publish('signature.verification_failed', {
2535
2539
  aid: sigAid, method, error: 'ECDSA verification failed',
@@ -2538,7 +2542,7 @@ export class AUNClient {
2538
2542
  return ok;
2539
2543
  }
2540
2544
  catch (exc) {
2541
- _clientLog('warn', '群事件验签异常: %s', formatCaughtError(exc));
2545
+ this._clientLog.warn(`群事件验签异常: ${formatCaughtError(exc)}`);
2542
2546
  // P1-16: 签名失败统一发布事件
2543
2547
  this._dispatcher.publish('signature.verification_failed', {
2544
2548
  aid: String(cs.aid ?? ''), method: String(cs._method ?? ''),
@@ -2622,7 +2626,7 @@ export class AUNClient {
2622
2626
  members = memberList.map((m) => String(m.aid));
2623
2627
  }
2624
2628
  catch (exc) {
2625
- _clientLog('warn', '群组 %s 成员列表回源失败: %s', groupId, formatCaughtError(exc));
2629
+ this._clientLog.warn(`群组 ${groupId} 成员列表回源失败: ${formatCaughtError(exc)}`);
2626
2630
  }
2627
2631
  }
2628
2632
  const response = this._groupE2ee.handleKeyRequestMsg(actualPayload, members);
@@ -2636,7 +2640,7 @@ export class AUNClient {
2636
2640
  });
2637
2641
  }
2638
2642
  catch (exc) {
2639
- _clientLog('warn', '向 %s 回复群组密钥失败: %s', requester, formatCaughtError(exc));
2643
+ this._clientLog.warn(`向 ${requester} 回复群组密钥失败: ${formatCaughtError(exc)}`);
2640
2644
  }
2641
2645
  }
2642
2646
  }
@@ -2647,7 +2651,7 @@ export class AUNClient {
2647
2651
  const keyCommitment = String(actualPayload.commitment ?? '');
2648
2652
  if (rotationId && keyCommitment) {
2649
2653
  this._ackGroupRotationKey(rotationId, keyCommitment)
2650
- .catch((exc) => _clientLog('warn', '提交 epoch key ack 失败: %s', formatCaughtError(exc)));
2654
+ .catch((exc) => this._clientLog.warn(`提交 epoch key ack 失败: ${formatCaughtError(exc)}`));
2651
2655
  }
2652
2656
  this._scheduleRetryPendingDecryptMsgs(groupId);
2653
2657
  }
@@ -2722,7 +2726,7 @@ export class AUNClient {
2722
2726
  this._keystore.saveCert(aid, certPem, certFingerprint, { makeActive: false });
2723
2727
  }
2724
2728
  catch (exc) {
2725
- _clientLog('error', '写入证书到 keystore 失败 (aid=%s, fp=%s): %s', aid, certFingerprint ?? '', formatCaughtError(exc));
2729
+ this._clientLog.error(`写入证书到 keystore 失败 (aid=${aid}, fp=${certFingerprint ?? ''}): ${formatCaughtError(exc)}`, exc instanceof Error ? exc : undefined);
2726
2730
  }
2727
2731
  return certPem;
2728
2732
  }
@@ -2873,10 +2877,10 @@ export class AUNClient {
2873
2877
  catch (exc) {
2874
2878
  // 刷新失败时:若内存缓存有 PKI 验证过的证书(未过期 x2 倍 TTL)则继续用
2875
2879
  if (cached && now < cached.validatedAt + PEER_CERT_CACHE_TTL * 2) {
2876
- _clientLog('debug', '刷新发送方 %s 证书失败,继续使用已验证的内存缓存: %s', aid, formatCaughtError(exc));
2880
+ this._clientLog.debug(`刷新发送方 ${aid} 证书失败,继续使用已验证的内存缓存: ${formatCaughtError(exc)}`);
2877
2881
  return true;
2878
2882
  }
2879
- _clientLog('warn', '获取发送方 %s 证书失败且无已验证缓存,拒绝信任: %s', aid, formatCaughtError(exc));
2883
+ this._clientLog.warn(`获取发送方 ${aid} 证书失败且无已验证缓存,拒绝信任: ${formatCaughtError(exc)}`);
2880
2884
  return false;
2881
2885
  }
2882
2886
  }
@@ -2912,7 +2916,7 @@ export class AUNClient {
2912
2916
  if (fromAid) {
2913
2917
  const certReady = await this._ensureSenderCertCached(fromAid, senderCertFingerprint || undefined);
2914
2918
  if (!certReady) {
2915
- _clientLog('warn', '无法获取发送方 %s 的证书,跳过解密', fromAid);
2919
+ this._clientLog.warn(`无法获取发送方 ${fromAid} 的证书,跳过解密`);
2916
2920
  throw new Error(`发送方证书不可用: from=${fromAid}, mid=${message.message_id}`);
2917
2921
  }
2918
2922
  }
@@ -2948,7 +2952,7 @@ export class AUNClient {
2948
2952
  if (fromAid) {
2949
2953
  const certReady = await this._ensureSenderCertCached(fromAid, senderCertFingerprint || undefined);
2950
2954
  if (!certReady) {
2951
- _clientLog('warn', '无法获取发送方 %s 的证书,跳过解密', fromAid);
2955
+ this._clientLog.warn(`无法获取发送方 ${fromAid} 的证书,跳过解密`);
2952
2956
  continue;
2953
2957
  }
2954
2958
  }
@@ -2959,7 +2963,7 @@ export class AUNClient {
2959
2963
  }
2960
2964
  else {
2961
2965
  // TS-015: 解密失败不回退到密文,跳过该消息并记录
2962
- _clientLog('warn', 'pull 消息解密失败,跳过: from=%s mid=%s', msg.from, msg.message_id);
2966
+ this._clientLog.warn(`pull 消息解密失败,跳过: from=${msg.from} mid=${msg.message_id}`);
2963
2967
  }
2964
2968
  }
2965
2969
  else {
@@ -3012,7 +3016,7 @@ export class AUNClient {
3012
3016
  _scheduleRetryPendingDecryptMsgs(groupId) {
3013
3017
  if (!groupId || !this._pendingDecryptMsgs.has(`group:${groupId}`))
3014
3018
  return;
3015
- this._retryPendingDecryptMsgs(groupId).catch((exc) => _clientLog('warn', '群 %s pending 消息重试失败: %s', groupId, formatCaughtError(exc)));
3019
+ this._retryPendingDecryptMsgs(groupId).catch((exc) => this._clientLog.warn(`群 ${groupId} pending 消息重试失败: ${formatCaughtError(exc)}`));
3016
3020
  }
3017
3021
  async _recoverGroupEpochKey(groupId, epoch, senderAid = '', timeoutMs = 5000) {
3018
3022
  const existing = await this._groupE2ee.loadSecret(groupId, epoch);
@@ -3121,13 +3125,13 @@ export class AUNClient {
3121
3125
  const myAid = this._aid || '';
3122
3126
  const keyPair = this._keystore.loadKeyPair(myAid);
3123
3127
  if (!keyPair?.private_key_pem) {
3124
- _clientLog('warn', '无法加载 AID 私钥用于 ECIES 解密: aid=%s', myAid);
3128
+ this._clientLog.warn(`无法加载 AID 私钥用于 ECIES 解密: aid=${myAid}`);
3125
3129
  return false;
3126
3130
  }
3127
3131
  const { eciesDecrypt } = await import('./e2ee-group.js');
3128
3132
  const groupSecret = eciesDecrypt(keyPair.private_key_pem, encryptedBytes);
3129
3133
  if (!groupSecret || groupSecret.length !== 32) {
3130
- _clientLog('warn', '服务端 epoch key ECIES 解密结果长度异常: group=%s epoch=%d len=%d', groupId, serverEpoch, groupSecret?.length ?? 0);
3134
+ this._clientLog.warn(`服务端 epoch key ECIES 解密结果长度异常: group=${groupId} epoch=${serverEpoch} len=${groupSecret?.length ?? 0}`);
3131
3135
  return false;
3132
3136
  }
3133
3137
  // 获取成员列表和 committed_rotation 用于 commitment / epoch_chain 验证
@@ -3164,7 +3168,7 @@ export class AUNClient {
3164
3168
  }
3165
3169
  catch { /* best effort */ }
3166
3170
  if (memberAids.length === 0) {
3167
- _clientLog('warn', '服务端 epoch key 恢复缺少成员快照: group=%s epoch=%d', groupId, serverEpoch);
3171
+ this._clientLog.warn(`服务端 epoch key 恢复缺少成员快照: group=${groupId} epoch=${serverEpoch}`);
3168
3172
  return false;
3169
3173
  }
3170
3174
  const commitment = computeMembershipCommitment(memberAids, serverEpoch, groupId, groupSecret);
@@ -3175,7 +3179,7 @@ export class AUNClient {
3175
3179
  const committedEpoch = Number(committedRotation.target_epoch ?? serverEpoch);
3176
3180
  const committedCommitment = String(committedRotation.key_commitment ?? '').trim();
3177
3181
  if (committedEpoch === serverEpoch && committedCommitment && committedCommitment !== commitment) {
3178
- _clientLog('warn', '服务端 epoch key 恢复 commitment 不匹配: group=%s epoch=%d', groupId, serverEpoch);
3182
+ this._clientLog.warn(`服务端 epoch key 恢复 commitment 不匹配: group=${groupId} epoch=${serverEpoch}`);
3179
3183
  return false;
3180
3184
  }
3181
3185
  if (epochChain && committedEpoch === serverEpoch) {
@@ -3191,7 +3195,7 @@ export class AUNClient {
3191
3195
  const prevChain = String(prevData?.epoch_chain ?? '').trim();
3192
3196
  if (prevChain && rotatorAid) {
3193
3197
  if (!verifyEpochChain(epochChain, prevChain, serverEpoch, commitment, rotatorAid)) {
3194
- _clientLog('warn', '服务端 epoch key 恢复 epoch_chain 验证失败: group=%s epoch=%d rotator=%s', groupId, serverEpoch, rotatorAid);
3198
+ this._clientLog.warn(`服务端 epoch key 恢复 epoch_chain 验证失败: group=${groupId} epoch=${serverEpoch} rotator=${rotatorAid}`);
3195
3199
  return false;
3196
3200
  }
3197
3201
  epochChainUnverified = false;
@@ -3204,14 +3208,14 @@ export class AUNClient {
3204
3208
  }
3205
3209
  const stored = storeGroupSecretEpoch(this._keystore, myAid, groupId, serverEpoch, groupSecret, commitment, memberAids, epochChain || undefined, '', epochChainUnverified, epochChainUnverifiedReason);
3206
3210
  if (!stored) {
3207
- _clientLog('warn', '服务端 epoch key 恢复存储失败: group=%s epoch=%d', groupId, serverEpoch);
3211
+ this._clientLog.warn(`服务端 epoch key 恢复存储失败: group=${groupId} epoch=${serverEpoch}`);
3208
3212
  return false;
3209
3213
  }
3210
- _clientLog('info', '从服务端恢复 epoch key 成功: group=%s epoch=%d', groupId, serverEpoch);
3214
+ this._clientLog.info(`从服务端恢复 epoch key 成功: group=${groupId} epoch=${serverEpoch}`);
3211
3215
  return true;
3212
3216
  }
3213
3217
  catch (exc) {
3214
- _clientLog('debug', '从服务端恢复 epoch key 失败: group=%s epoch=%d err=%s', groupId, epoch, formatCaughtError(exc));
3218
+ this._clientLog.debug(`从服务端恢复 epoch key 失败: group=${groupId} epoch=${epoch} err=${formatCaughtError(exc)}`);
3215
3219
  return false;
3216
3220
  }
3217
3221
  }
@@ -3238,7 +3242,7 @@ export class AUNClient {
3238
3242
  groupSecretBytes = loaded.secret;
3239
3243
  }
3240
3244
  else {
3241
- _clientLog('debug', '无法获取 group_secret 用于 ECIES 加密: group=%s epoch=%d', groupId, targetEpoch);
3245
+ this._clientLog.debug(`无法获取 group_secret 用于 ECIES 加密: group=${groupId} epoch=${targetEpoch}`);
3242
3246
  return {};
3243
3247
  }
3244
3248
  }
@@ -3259,14 +3263,14 @@ export class AUNClient {
3259
3263
  encryptedKeys[aid] = ciphertext.toString('base64');
3260
3264
  }
3261
3265
  catch (exc) {
3262
- _clientLog('debug', '为成员 %s 构建 ECIES epoch key 失败: %s', aid, formatCaughtError(exc));
3266
+ this._clientLog.debug(`为成员 ${aid} 构建 ECIES epoch key 失败: ${formatCaughtError(exc)}`);
3263
3267
  continue;
3264
3268
  }
3265
3269
  }
3266
3270
  return encryptedKeys;
3267
3271
  }
3268
3272
  catch (exc) {
3269
- _clientLog('debug', '构建 encrypted_keys 失败: group=%s err=%s', groupId, formatCaughtError(exc));
3273
+ this._clientLog.debug(`构建 encrypted_keys 失败: group=${groupId} err=${formatCaughtError(exc)}`);
3270
3274
  return {};
3271
3275
  }
3272
3276
  }
@@ -3304,11 +3308,11 @@ export class AUNClient {
3304
3308
  }
3305
3309
  }
3306
3310
  catch {
3307
- _clientLog('debug', '群 %s 查询在线成员失败,回退全量候选', groupId);
3311
+ this._clientLog.debug(`群 ${groupId} 查询在线成员失败,回退全量候选`);
3308
3312
  }
3309
3313
  if (onlineAids !== null) {
3310
3314
  if (onlineAids.length === 0) {
3311
- _clientLog('info', '群 %s epoch %s 恢复失败:无在线成员可请求密钥', groupId, String(epoch));
3315
+ this._clientLog.info(`群 ${groupId} epoch ${String(epoch)} 恢复失败:无在线成员可请求密钥`);
3312
3316
  return false;
3313
3317
  }
3314
3318
  await this._requestGroupKeyFromOnline(groupId, epoch, onlineAids, epochResult);
@@ -3389,7 +3393,7 @@ export class AUNClient {
3389
3393
  if (senderAid) {
3390
3394
  const certOk = await this._ensureSenderCertCached(senderAid);
3391
3395
  if (!certOk) {
3392
- _clientLog('warn', '群消息解密跳过:发送方 %s 证书不可用', senderAid);
3396
+ this._clientLog.warn(`群消息解密跳过:发送方 ${senderAid} 证书不可用`);
3393
3397
  return message;
3394
3398
  }
3395
3399
  }
@@ -3416,7 +3420,7 @@ export class AUNClient {
3416
3420
  }
3417
3421
  }
3418
3422
  catch (exc) {
3419
- _clientLog('debug', '群 %s epoch %s 同步恢复失败: %s', groupId, epoch, formatCaughtError(exc));
3423
+ this._clientLog.debug(`群 ${groupId} epoch ${epoch} 同步恢复失败: ${formatCaughtError(exc)}`);
3420
3424
  }
3421
3425
  }
3422
3426
  return message;
@@ -3537,7 +3541,7 @@ export class AUNClient {
3537
3541
  if (fromAid) {
3538
3542
  const certReady = await this._ensureSenderCertCached(fromAid, senderCertFingerprint || undefined);
3539
3543
  if (!certReady) {
3540
- _clientLog('warn', '无法获取发送方 %s 的证书,跳过 message.thought.get 解密', fromAid);
3544
+ this._clientLog.warn(`无法获取发送方 ${fromAid} 的证书,跳过 message.thought.get 解密`);
3541
3545
  decryptFailed = true;
3542
3546
  }
3543
3547
  }
@@ -3622,7 +3626,7 @@ export class AUNClient {
3622
3626
  }
3623
3627
  else {
3624
3628
  failed.push(String(dist.to));
3625
- _clientLog('warn', 'epoch 密钥分发失败 (to=%s): %s', dist.to, formatCaughtError(exc));
3629
+ this._clientLog.warn(`epoch 密钥分发失败 (to=${dist.to}): ${formatCaughtError(exc)}`);
3626
3630
  }
3627
3631
  }
3628
3632
  }
@@ -3640,7 +3644,7 @@ export class AUNClient {
3640
3644
  return isJsonObject(result) && result.success === true;
3641
3645
  }
3642
3646
  catch (exc) {
3643
- _clientLog('warn', '刷新 epoch rotation lease 失败: rotation=%s err=%s', rotationId, formatCaughtError(exc));
3647
+ this._clientLog.warn(`刷新 epoch rotation lease 失败: rotation=${rotationId} err=${formatCaughtError(exc)}`);
3644
3648
  return false;
3645
3649
  }
3646
3650
  }
@@ -3656,7 +3660,7 @@ export class AUNClient {
3656
3660
  return isJsonObject(result) && result.success === true;
3657
3661
  }
3658
3662
  catch (exc) {
3659
- _clientLog('warn', '提交 epoch key ack 失败: rotation=%s err=%s', rotationId, formatCaughtError(exc));
3663
+ this._clientLog.warn(`提交 epoch key ack 失败: rotation=${rotationId} err=${formatCaughtError(exc)}`);
3660
3664
  return false;
3661
3665
  }
3662
3666
  }
@@ -3684,7 +3688,7 @@ export class AUNClient {
3684
3688
  ? committedRotation.expected_members.map((item) => String(item ?? '').trim()).filter(Boolean)
3685
3689
  : [];
3686
3690
  if (this._aid && !expectedMembers.includes(this._aid)) {
3687
- _clientLog('debug', '放行 group key 分发:新成员恢复 commitment 不匹配属正常 group=%s epoch=%s', groupId, epoch);
3691
+ this._clientLog.debug(`放行 group key 分发:新成员恢复 commitment 不匹配属正常 group=${groupId} epoch=${epoch}`);
3688
3692
  }
3689
3693
  else {
3690
3694
  return false;
@@ -3693,7 +3697,7 @@ export class AUNClient {
3693
3697
  }
3694
3698
  return true;
3695
3699
  }
3696
- _clientLog('info', '拒绝缺少 rotation_id 的未来 epoch key 分发: group=%s epoch=%s committed=%s', groupId, epoch, committedEpoch);
3700
+ this._clientLog.info(`拒绝缺少 rotation_id 的未来 epoch key 分发: group=${groupId} epoch=${epoch} committed=${committedEpoch}`);
3697
3701
  return false;
3698
3702
  }
3699
3703
  const pending = isJsonObject(epochResult.pending_rotation) ? epochResult.pending_rotation : null;
@@ -3714,10 +3718,10 @@ export class AUNClient {
3714
3718
  }
3715
3719
  }
3716
3720
  catch (exc) {
3717
- _clientLog('warn', '拒绝无法校验 active rotation 的 epoch key 分发: group=%s rotation=%s err=%s', groupId, rotationId, formatCaughtError(exc));
3721
+ this._clientLog.warn(`拒绝无法校验 active rotation 的 epoch key 分发: group=${groupId} rotation=${rotationId} err=${formatCaughtError(exc)}`);
3718
3722
  return false;
3719
3723
  }
3720
- _clientLog('info', '拒绝非 pending/committed 状态的 epoch key 分发: group=%s rotation=%s epoch=%s', groupId, rotationId, epoch);
3724
+ this._clientLog.info(`拒绝非 pending/committed 状态的 epoch key 分发: group=${groupId} rotation=${rotationId} epoch=${epoch}`);
3721
3725
  return false;
3722
3726
  }
3723
3727
  async _discardGroupDistributionIfStale(payload) {
@@ -3732,10 +3736,10 @@ export class AUNClient {
3732
3736
  return;
3733
3737
  try {
3734
3738
  this._groupE2ee.discardPendingSecret(groupId, epoch, rotationId);
3735
- _clientLog('info', '丢弃 verify 后变为 stale 的 group epoch key: group=%s epoch=%s rotation=%s', groupId, epoch, rotationId);
3739
+ this._clientLog.info(`丢弃 verify 后变为 stale 的 group epoch key: group=${groupId} epoch=${epoch} rotation=${rotationId}`);
3736
3740
  }
3737
3741
  catch (exc) {
3738
- _clientLog('debug', '清理 stale group epoch key 失败: group=%s epoch=%s rotation=%s err=%s', groupId, epoch, rotationId, formatCaughtError(exc));
3742
+ this._clientLog.debug(`清理 stale group epoch key 失败: group=${groupId} epoch=${epoch} rotation=${rotationId} err=${formatCaughtError(exc)}`);
3739
3743
  }
3740
3744
  }
3741
3745
  async _verifyGroupKeyResponseEpoch(payload) {
@@ -3752,7 +3756,7 @@ export class AUNClient {
3752
3756
  return false;
3753
3757
  const committedEpoch = Number(epochResult.committed_epoch ?? epochResult.epoch ?? 0);
3754
3758
  if (epoch > committedEpoch) {
3755
- _clientLog('info', '拒绝未提交 epoch 的 group key response: group=%s epoch=%s committed=%s', groupId, epoch, committedEpoch);
3759
+ this._clientLog.info(`拒绝未提交 epoch 的 group key response: group=${groupId} epoch=${epoch} committed=${committedEpoch}`);
3756
3760
  return false;
3757
3761
  }
3758
3762
  const committedRotation = isJsonObject(epochResult.committed_rotation) ? epochResult.committed_rotation : null;
@@ -3763,7 +3767,7 @@ export class AUNClient {
3763
3767
  ? committedRotation.expected_members.map((item) => String(item ?? '').trim()).filter(Boolean)
3764
3768
  : [];
3765
3769
  if (this._aid && !expectedMembers.includes(this._aid)) {
3766
- _clientLog('debug', '放行 group key response:新成员恢复 commitment 不匹配属正常 group=%s epoch=%s', groupId, epoch);
3770
+ this._clientLog.debug(`放行 group key response:新成员恢复 commitment 不匹配属正常 group=${groupId} epoch=${epoch}`);
3767
3771
  }
3768
3772
  else {
3769
3773
  return false;
@@ -3773,7 +3777,7 @@ export class AUNClient {
3773
3777
  return true;
3774
3778
  }
3775
3779
  catch (exc) {
3776
- _clientLog('warn', '拒绝无法校验 committed epoch 的 group key response: group=%s epoch=%s err=%s', groupId, epoch, formatCaughtError(exc));
3780
+ this._clientLog.warn(`拒绝无法校验 committed epoch 的 group key response: group=${groupId} epoch=${epoch} err=${formatCaughtError(exc)}`);
3777
3781
  return false;
3778
3782
  }
3779
3783
  }
@@ -3788,7 +3792,7 @@ export class AUNClient {
3788
3792
  return isJsonObject(result) && result.success === true;
3789
3793
  }
3790
3794
  catch (exc) {
3791
- _clientLog('warn', '中止 epoch rotation 失败: rotation=%s err=%s', rotationId, formatCaughtError(exc));
3795
+ this._clientLog.warn(`中止 epoch rotation 失败: rotation=${rotationId} err=${formatCaughtError(exc)}`);
3792
3796
  return false;
3793
3797
  }
3794
3798
  }
@@ -3822,7 +3826,7 @@ export class AUNClient {
3822
3826
  if (this._closing || this._state !== 'connected')
3823
3827
  return;
3824
3828
  this._maybeLeadRotateGroupEpoch(groupId, opts.triggerId, opts.expectedEpoch)
3825
- .catch((exc) => _clientLog('warn', 'group epoch rotation retry failed: %s', formatCaughtError(exc)));
3829
+ .catch((exc) => this._clientLog.warn(`group epoch rotation retry failed: ${formatCaughtError(exc)}`));
3826
3830
  }, this._rotationRetryDelayMs(opts.pending));
3827
3831
  this._groupEpochRotationRetryTimers.set(retryKey, timer);
3828
3832
  this._unrefTimer(timer);
@@ -3834,7 +3838,7 @@ export class AUNClient {
3834
3838
  if (this._closing || this._state !== 'connected')
3835
3839
  return;
3836
3840
  if (Date.now() - started > 20000) {
3837
- _clientLog('warn', 'group epoch create sync still in-flight; skip duplicate sync (group=%s)', groupId);
3841
+ this._clientLog.warn(`group epoch create sync still in-flight; skip duplicate sync (group=${groupId})`);
3838
3842
  return;
3839
3843
  }
3840
3844
  await new Promise((resolve) => setTimeout(resolve, 200));
@@ -3867,12 +3871,12 @@ export class AUNClient {
3867
3871
  const beginResult = await this.call('group.e2ee.begin_rotation', rotateParams);
3868
3872
  const rotation = isJsonObject(beginResult) && isJsonObject(beginResult.rotation) ? beginResult.rotation : null;
3869
3873
  if (!isJsonObject(beginResult) || beginResult.success !== true || !rotation) {
3870
- _clientLog('warn', 'group epoch begin failed; stop key distribution (group=%s, returned=%s)', groupId, JSON.stringify(beginResult));
3874
+ this._clientLog.warn(`group epoch begin failed; stop key distribution (group=${groupId}, returned=${JSON.stringify(beginResult)})`);
3871
3875
  return;
3872
3876
  }
3873
3877
  const activeRotationId = String(rotation.rotation_id ?? rotationId);
3874
3878
  if (!await this._ackGroupRotationKey(activeRotationId, secretData.commitment)) {
3875
- _clientLog('warn', 'group epoch self ack failed (group=%s, rotation=%s)', groupId, activeRotationId);
3879
+ this._clientLog.warn(`group epoch self ack failed (group=${groupId}, rotation=${activeRotationId})`);
3876
3880
  await this._abortGroupRotation(activeRotationId, 'self_ack_failed');
3877
3881
  return;
3878
3882
  }
@@ -3889,17 +3893,17 @@ export class AUNClient {
3889
3893
  storeGroupSecret(this._keystore, this._aid, groupId, 1, secretData.secret, secretData.commitment, secretData.member_aids, secretData.epoch_chain);
3890
3894
  return;
3891
3895
  }
3892
- _clientLog('warn', 'group epoch commit failed (group=%s, returned=%s)', groupId, JSON.stringify(commitResult));
3896
+ this._clientLog.warn(`group epoch commit failed (group=${groupId}, returned=${JSON.stringify(commitResult)})`);
3893
3897
  return;
3894
3898
  }
3895
3899
  catch (exc) {
3896
3900
  if (attempt < maxRetries) {
3897
3901
  const delay = 500 * Math.pow(2, attempt - 1);
3898
- _clientLog('warn', '同步 epoch 到服务端失败 (group=%s, 第%d/%d次): %s, %dms后重试', groupId, attempt, maxRetries, formatCaughtError(exc), delay);
3902
+ this._clientLog.warn(`同步 epoch 到服务端失败 (group=${groupId}, 第${attempt}/${maxRetries}次): ${formatCaughtError(exc)}, ${delay}ms后重试`);
3899
3903
  await new Promise(r => setTimeout(r, delay));
3900
3904
  }
3901
3905
  else {
3902
- _clientLog('error', '同步 epoch 到服务端最终失败 (group=%s, 已重试%d次): %s', groupId, maxRetries, formatCaughtError(exc));
3906
+ this._clientLog.error(`同步 epoch 到服务端最终失败 (group=${groupId}, 已重试${maxRetries}次): ${formatCaughtError(exc)}`, exc instanceof Error ? exc : undefined);
3903
3907
  }
3904
3908
  }
3905
3909
  }
@@ -3930,7 +3934,7 @@ export class AUNClient {
3930
3934
  && serverEpoch === expectedEpoch
3931
3935
  && this._rotationExpectedMembersStale(pendingRotation, memberAids));
3932
3936
  if (stalePending && await this._abortGroupRotation(pendingRotationId, 'membership_changed_during_rotation')) {
3933
- _clientLog('info', 'aborted stale pending group epoch rotation: group=%s rotation=%s', groupId, pendingRotationId || '-');
3937
+ this._clientLog.info(`aborted stale pending group epoch rotation: group=${groupId} rotation=${pendingRotationId || '-'}`);
3934
3938
  }
3935
3939
  else {
3936
3940
  this._scheduleGroupRotationRetry(groupId, {
@@ -3945,7 +3949,7 @@ export class AUNClient {
3945
3949
  if (expectedEpoch !== null && serverEpoch !== expectedEpoch) {
3946
3950
  if (triggerId)
3947
3951
  this._groupMembershipRotationDone.add(triggerId);
3948
- _clientLog('info', 'skip membership epoch rotation: group=%s expected_epoch=%d server_epoch=%d trigger=%s', groupId, expectedEpoch, serverEpoch, triggerId || '-');
3952
+ this._clientLog.info(`skip membership epoch rotation: group=${groupId} expected_epoch=${expectedEpoch} server_epoch=${serverEpoch} trigger=${triggerId || '-'}`);
3949
3953
  return;
3950
3954
  }
3951
3955
  const currentEpoch = expectedEpoch ?? serverEpoch;
@@ -3961,7 +3965,7 @@ export class AUNClient {
3961
3965
  const rawChain = String(cr.epoch_chain ?? '').trim();
3962
3966
  if (rawChain) {
3963
3967
  prevChainHint = rawChain;
3964
- _clientLog('info', '新成员轮换补充 prev epoch chain from server: group=%s epoch=%d', groupId, currentEpoch);
3968
+ this._clientLog.info(`新成员轮换补充 prev epoch chain from server: group=${groupId} epoch=${currentEpoch}`);
3965
3969
  }
3966
3970
  }
3967
3971
  }
@@ -3973,7 +3977,7 @@ export class AUNClient {
3973
3977
  this._groupE2ee.discardPendingSecret(groupId, targetEpoch, rotationId);
3974
3978
  }
3975
3979
  catch (cleanupExc) {
3976
- _clientLog('debug', '清理本地 pending group key 失败: group=%s epoch=%s rotation=%s err=%s', groupId, targetEpoch, rotationId, formatCaughtError(cleanupExc));
3980
+ this._clientLog.debug(`清理本地 pending group key 失败: group=${groupId} epoch=${targetEpoch} rotation=${rotationId} err=${formatCaughtError(cleanupExc)}`);
3977
3981
  }
3978
3982
  };
3979
3983
  const rotateParams = {
@@ -4027,14 +4031,14 @@ export class AUNClient {
4027
4031
  pending: null,
4028
4032
  });
4029
4033
  }
4030
- _clientLog('warn', 'group epoch begin failed; stop key distribution (group=%s, current_epoch=%d, returned=%s)', groupId, currentEpoch, JSON.stringify(beginResult));
4034
+ this._clientLog.warn(`group epoch begin failed; stop key distribution (group=${groupId}, current_epoch=${currentEpoch}, returned=${JSON.stringify(beginResult)})`);
4031
4035
  discardGeneratedPending();
4032
4036
  return;
4033
4037
  }
4034
4038
  const activeRotationId = String(rotation.rotation_id ?? rotationId);
4035
4039
  const distributeResult = await this._distributeGroupEpochKey(info, activeRotationId);
4036
4040
  if (distributeResult.failed.length > 0) {
4037
- _clientLog('warn', 'group epoch key distribution incomplete; abort rotation before retry (group=%s rotation=%s failed=%s)', groupId, activeRotationId, distributeResult.failed.join(','));
4041
+ this._clientLog.warn(`group epoch key distribution incomplete; abort rotation before retry (group=${groupId} rotation=${activeRotationId} failed=${distributeResult.failed.join(',')})`);
4038
4042
  await this._abortGroupRotation(activeRotationId, 'distribution_failed');
4039
4043
  this._scheduleGroupRotationRetry(groupId, {
4040
4044
  reason: 'membership_changed',
@@ -4047,7 +4051,7 @@ export class AUNClient {
4047
4051
  }
4048
4052
  await this._heartbeatGroupRotation(activeRotationId);
4049
4053
  if (!await this._ackGroupRotationKey(activeRotationId, String(info.commitment ?? ''))) {
4050
- _clientLog('warn', 'group epoch self ack failed; abort rotation before retry (group=%s rotation=%s)', groupId, activeRotationId);
4054
+ this._clientLog.warn(`group epoch self ack failed; abort rotation before retry (group=${groupId} rotation=${activeRotationId})`);
4051
4055
  await this._abortGroupRotation(activeRotationId, 'self_ack_failed');
4052
4056
  this._scheduleGroupRotationRetry(groupId, {
4053
4057
  reason: 'membership_changed',
@@ -4068,7 +4072,7 @@ export class AUNClient {
4068
4072
  }
4069
4073
  const commitResult = await this.call('group.e2ee.commit_rotation', commitParams);
4070
4074
  if (!isJsonObject(commitResult) || commitResult.success !== true) {
4071
- _clientLog('warn', 'group epoch commit failed (group=%s, rotation=%s, returned=%s)', groupId, activeRotationId, JSON.stringify(commitResult));
4075
+ this._clientLog.warn(`group epoch commit failed (group=${groupId}, rotation=${activeRotationId}, returned=${JSON.stringify(commitResult)})`);
4072
4076
  this._scheduleGroupRotationRetry(groupId, {
4073
4077
  reason: 'membership_changed',
4074
4078
  triggerId,
@@ -4092,7 +4096,7 @@ export class AUNClient {
4092
4096
  storeGroupSecret(this._keystore, this._aid, groupId, targetEpoch, committedSecret.secret, committedSecret.commitment, committedSecret.member_aids.length > 0 ? committedSecret.member_aids : memberAids, committedSecret.epoch_chain);
4093
4097
  }
4094
4098
  else {
4095
- _clientLog('warn', 'group epoch commit succeeded but local target key does not match committed rotation; keep pending blocked (group=%s rotation=%s epoch=%s)', groupId, activeRotationId, targetEpoch);
4099
+ this._clientLog.warn(`group epoch commit succeeded but local target key does not match committed rotation; keep pending blocked (group=${groupId} rotation=${activeRotationId} epoch=${targetEpoch})`);
4096
4100
  }
4097
4101
  }
4098
4102
  if (triggerId) {
@@ -4285,8 +4289,9 @@ export class AUNClient {
4285
4289
  // 优先从 seq_tracker 表按行读取
4286
4290
  const loadAll = this._keystore.loadAllSeqs;
4287
4291
  if (typeof loadAll === 'function') {
4288
- const state = loadAll.call(this._keystore, this._aid, this._deviceId, this._slotId);
4292
+ let state = loadAll.call(this._keystore, this._aid, this._deviceId, this._slotId);
4289
4293
  if (state && Object.keys(state).length > 0) {
4294
+ state = this._migrateSeqStateGroupIds(state);
4290
4295
  this._seqTracker.restoreState(state);
4291
4296
  return;
4292
4297
  }
@@ -4296,12 +4301,14 @@ export class AUNClient {
4296
4301
  if (typeof loader === 'function') {
4297
4302
  const instanceState = loader.call(this._keystore, this._aid, this._deviceId, this._slotId);
4298
4303
  if (instanceState && typeof instanceState.seq_tracker_state === 'object') {
4299
- this._seqTracker.restoreState(instanceState.seq_tracker_state);
4304
+ let state = instanceState.seq_tracker_state;
4305
+ state = this._migrateSeqStateGroupIds(state);
4306
+ this._seqTracker.restoreState(state);
4300
4307
  }
4301
4308
  }
4302
4309
  }
4303
4310
  catch (exc) {
4304
- _clientLog('warn', '恢复 SeqTracker 状态失败: %s', formatCaughtError(exc));
4311
+ this._clientLog.warn(`恢复 SeqTracker 状态失败: ${formatCaughtError(exc)}`);
4305
4312
  // 通过内部 dispatcher 发布可观测事件,便于上层监控
4306
4313
  this._dispatcher.publish('seq_tracker.persist_error', {
4307
4314
  phase: 'restore',
@@ -4312,6 +4319,59 @@ export class AUNClient {
4312
4319
  }).catch(() => { });
4313
4320
  }
4314
4321
  }
4322
+ /**
4323
+ * 把 seq_tracker state 里 group_event:/group_msg: 前缀的老/污染 group_id 归一化为 canonical。
4324
+ * 冲突取 max。同时落盘删除老 ns、写入新 ns,避免下次启动重复迁移。
4325
+ */
4326
+ _migrateSeqStateGroupIds(state) {
4327
+ if (!state || Object.keys(state).length === 0)
4328
+ return state;
4329
+ const renameMap = {};
4330
+ for (const ns of Object.keys(state)) {
4331
+ for (const prefix of ['group_event:', 'group_msg:']) {
4332
+ if (ns.startsWith(prefix)) {
4333
+ const oldGid = ns.slice(prefix.length);
4334
+ const newGid = normalizeGroupId(oldGid);
4335
+ if (newGid && newGid !== oldGid) {
4336
+ renameMap[ns] = `${prefix}${newGid}`;
4337
+ }
4338
+ break;
4339
+ }
4340
+ }
4341
+ }
4342
+ if (Object.keys(renameMap).length === 0)
4343
+ return state;
4344
+ const newState = { ...state };
4345
+ for (const [oldNs, newNs] of Object.entries(renameMap)) {
4346
+ const oldVal = Number(newState[oldNs] ?? 0);
4347
+ const curVal = Number(newState[newNs] ?? 0);
4348
+ delete newState[oldNs];
4349
+ newState[newNs] = Math.max(oldVal, curVal);
4350
+ }
4351
+ this._clientLog.info(`SeqTracker group_id 迁移:${Object.keys(renameMap).length} 个命名空间重写`);
4352
+ // 落盘
4353
+ const saver = this._keystore.saveSeq;
4354
+ const deleter = this._keystore.deleteSeq;
4355
+ if (typeof saver === 'function' && this._aid) {
4356
+ for (const [oldNs, newNs] of Object.entries(renameMap)) {
4357
+ if (typeof deleter === 'function') {
4358
+ try {
4359
+ deleter.call(this._keystore, this._aid, this._deviceId, this._slotId, oldNs);
4360
+ }
4361
+ catch (e) {
4362
+ this._clientLog.debug(`删除旧 seq ns 失败: ns=${oldNs} err=${formatCaughtError(e)}`);
4363
+ }
4364
+ }
4365
+ try {
4366
+ saver.call(this._keystore, this._aid, this._deviceId, this._slotId, newNs, newState[newNs]);
4367
+ }
4368
+ catch (e) {
4369
+ this._clientLog.debug(`写入新 seq ns 失败: ns=${newNs} err=${formatCaughtError(e)}`);
4370
+ }
4371
+ }
4372
+ }
4373
+ return newState;
4374
+ }
4315
4375
  _currentSeqTrackerContext() {
4316
4376
  if (!this._aid)
4317
4377
  return null;
@@ -4366,7 +4426,7 @@ export class AUNClient {
4366
4426
  }
4367
4427
  }
4368
4428
  catch (exc) {
4369
- _clientLog('warn', '保存 SeqTracker 状态失败: %s', formatCaughtError(exc));
4429
+ this._clientLog.warn(`保存 SeqTracker 状态失败: ${formatCaughtError(exc)}`);
4370
4430
  // 通过内部 dispatcher 发布可观测事件,便于上层监控
4371
4431
  this._dispatcher.publish('seq_tracker.persist_error', {
4372
4432
  phase: 'save',
@@ -4448,8 +4508,8 @@ export class AUNClient {
4448
4508
  if (identity && isJsonObject(identity)) {
4449
4509
  this._identity = identity;
4450
4510
  this._aid = String(identity.aid ?? this._aid ?? '');
4451
- if (_debugLogger)
4452
- _debugLogger.setAid(` [${this._aid}]`);
4511
+ if (this._aid)
4512
+ this._logger.bindAid(this._aid);
4453
4513
  if (this._sessionParams !== null) {
4454
4514
  this._sessionParams.access_token = String(auth.token ?? params.access_token ?? '');
4455
4515
  }
@@ -4478,7 +4538,7 @@ export class AUNClient {
4478
4538
  await this._uploadPrekey();
4479
4539
  }
4480
4540
  catch (exc) {
4481
- _clientLog('warn', 'prekey 上传失败: %s', formatCaughtError(exc));
4541
+ this._clientLog.warn(`prekey 上传失败: ${formatCaughtError(exc)}`);
4482
4542
  }
4483
4543
  }
4484
4544
  catch (err) {
@@ -4515,8 +4575,8 @@ export class AUNClient {
4515
4575
  identity.access_token = accessToken;
4516
4576
  this._identity = identity;
4517
4577
  this._aid = String(identity.aid ?? this._aid ?? '');
4518
- if (_debugLogger)
4519
- _debugLogger.setAid(` [${this._aid}]`);
4578
+ if (this._aid)
4579
+ this._logger.bindAid(this._aid);
4520
4580
  const persistIdentity = this._auth._persistIdentity;
4521
4581
  if (typeof persistIdentity === 'function') {
4522
4582
  persistIdentity.call(this._auth, identity);
@@ -4648,10 +4708,10 @@ export class AUNClient {
4648
4708
  consecutiveFailures = 0;
4649
4709
  }).catch((exc) => {
4650
4710
  consecutiveFailures++;
4651
- _clientLog('warn', '心跳失败 (%s/%s): %s', consecutiveFailures, maxFailures, formatCaughtError(exc));
4711
+ this._clientLog.warn(`心跳失败 (${consecutiveFailures}/${maxFailures}): ${formatCaughtError(exc)}`);
4652
4712
  this._dispatcher.publish('connection.error', { error: formatCaughtError(exc) }).catch(() => { });
4653
4713
  if (consecutiveFailures >= maxFailures) {
4654
- _clientLog('warn', '连续 %s 次心跳失败,触发断线重连', maxFailures);
4714
+ this._clientLog.warn(`连续 ${maxFailures} 次心跳失败,触发断线重连`);
4655
4715
  this._handleTransportDisconnect(exc instanceof Error ? exc : new Error(String(exc)));
4656
4716
  }
4657
4717
  });
@@ -4715,7 +4775,7 @@ export class AUNClient {
4715
4775
  if (exc instanceof AuthError) {
4716
4776
  this._tokenRefreshFailures++;
4717
4777
  if (this._tokenRefreshFailures >= 3) {
4718
- _clientLog('warn', 'token 刷新连续失败 %d 次,停止刷新循环并触发重连', this._tokenRefreshFailures);
4778
+ this._clientLog.warn(`token 刷新连续失败 ${this._tokenRefreshFailures} 次,停止刷新循环并触发重连`);
4719
4779
  await this._dispatcher.publish('token.refresh_exhausted', {
4720
4780
  aid: this._identity?.aid ?? null,
4721
4781
  consecutive_failures: this._tokenRefreshFailures,
@@ -4725,7 +4785,7 @@ export class AUNClient {
4725
4785
  this._handleTransportDisconnect(new Error('token refresh exhausted, triggering reconnect'));
4726
4786
  return;
4727
4787
  }
4728
- _clientLog('debug', 'token 刷新失败 (%d/3),下次重试: %s', this._tokenRefreshFailures, exc);
4788
+ this._clientLog.debug(`token 刷新失败 (${this._tokenRefreshFailures}/3),下次重试: ${exc}`);
4729
4789
  }
4730
4790
  else {
4731
4791
  await this._dispatcher.publish('connection.error', { error: formatCaughtError(exc) });
@@ -4827,7 +4887,7 @@ export class AUNClient {
4827
4887
  this._prekeyReplenished.add(prekeyId);
4828
4888
  }
4829
4889
  catch (exc) {
4830
- _clientLog('warn', '消费 prekey %s 后补充 current prekey 失败: %s', prekeyId, formatCaughtError(exc));
4890
+ this._clientLog.warn(`消费 prekey ${prekeyId} 后补充 current prekey 失败: ${formatCaughtError(exc)}`);
4831
4891
  }
4832
4892
  finally {
4833
4893
  this._prekeyReplenishInflight.delete(prekeyId);
@@ -4852,7 +4912,7 @@ export class AUNClient {
4852
4912
  }
4853
4913
  }
4854
4914
  catch (exc) {
4855
- _clientLog('warn', 'epoch 清理失败: %s', formatCaughtError(exc));
4915
+ this._clientLog.warn(`epoch 清理失败: ${formatCaughtError(exc)}`);
4856
4916
  }
4857
4917
  }, 3600_000);
4858
4918
  this._unrefTimer(this._groupEpochCleanupTimer);
@@ -4868,11 +4928,11 @@ export class AUNClient {
4868
4928
  ? this._keystore.listGroupSecretIds(this._aid)
4869
4929
  : [];
4870
4930
  for (const gid of groupIds) {
4871
- this._maybeLeadRotateGroupEpoch(gid).catch((exc) => _clientLog('warn', 'epoch 轮换失败: %s', formatCaughtError(exc)));
4931
+ this._maybeLeadRotateGroupEpoch(gid).catch((exc) => this._clientLog.warn(`epoch 轮换失败: ${formatCaughtError(exc)}`));
4872
4932
  }
4873
4933
  }
4874
4934
  catch (exc) {
4875
- _clientLog('warn', 'epoch 轮换失败: %s', formatCaughtError(exc));
4935
+ this._clientLog.warn(`epoch 轮换失败: ${formatCaughtError(exc)}`);
4876
4936
  }
4877
4937
  }, rotateInterval * 1000);
4878
4938
  this._unrefTimer(this._groupEpochRotateTimer);
@@ -4920,7 +4980,7 @@ export class AUNClient {
4920
4980
  _onGatewayDisconnect(data) {
4921
4981
  const code = data?.code;
4922
4982
  const reason = data?.reason ?? '';
4923
- _clientLog('warn', '服务端主动断开: code=%s, reason=%s', code, reason);
4983
+ this._clientLog.warn(`服务端主动断开: code=${code}, reason=${reason}`);
4924
4984
  this._serverKicked = true;
4925
4985
  }
4926
4986
  /** 传输层断线回调 */
@@ -4941,7 +5001,7 @@ export class AUNClient {
4941
5001
  if (this._serverKicked || (closeCode !== undefined && AUNClient._NO_RECONNECT_CODES.has(closeCode))) {
4942
5002
  this._state = 'terminal_failed';
4943
5003
  const reason = this._serverKicked ? 'server kicked' : `close code ${closeCode}`;
4944
- _clientLog('warn', '抑制自动重连: %s', reason);
5004
+ this._clientLog.warn(`抑制自动重连: ${reason}`);
4945
5005
  await this._dispatcher.publish('connection.state', {
4946
5006
  state: this._state, error, reason,
4947
5007
  });
@@ -4958,7 +5018,7 @@ export class AUNClient {
4958
5018
  this._reconnectActive = true;
4959
5019
  this._reconnectAbort = new AbortController();
4960
5020
  this._reconnectLoop(serverInitiated).catch((exc) => {
4961
- _clientLog('warn', '重连循环异常: %s', formatCaughtError(exc));
5021
+ this._clientLog.warn(`重连循环异常: ${formatCaughtError(exc)}`);
4962
5022
  });
4963
5023
  }
4964
5024
  /** 重连循环(for 循环 + AbortController,与 JS/Python 对齐) */