@agentunion/fastaun 0.5.10 → 0.5.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/client.js CHANGED
@@ -124,7 +124,7 @@ const MAX_NOTIFY_PAYLOAD_SIZE = 64 * 1024;
124
124
  // P1-23: 非幂等方法使用更长超时(35s),避免 SDK 10s 超时 < gateway 30s 处理时间
125
125
  const NON_IDEMPOTENT_TIMEOUT_MS = 35_000;
126
126
  const NON_IDEMPOTENT_METHODS = new Set([
127
- 'message.send', 'mail.send', 'group.send', 'group.create', 'group.invite',
127
+ 'message.send', 'mail.send', 'group.send', 'group.create',
128
128
  'group.kick', 'group.remove_member', 'group.leave', 'group.dissolve',
129
129
  'group.set_settings',
130
130
  'group.update_announcement', 'group.update_rules',
@@ -150,6 +150,7 @@ const NON_IDEMPOTENT_METHODS = new Set([
150
150
  'auth.create_aid', 'auth.renew_cert', 'auth.rekey',
151
151
  'message.thought.put', 'group.thought.put',
152
152
  'group.add_member', 'group.bind_group_aid', 'group.complete_transfer',
153
+ 'group.accept_invite', 'group.reject_invite', 'group.revoke_invite',
153
154
  'collab.create', 'collab.commit', 'collab.clone',
154
155
  'collab.prune', 'collab.unregister',
155
156
  'collab.tag.create', 'collab.tag.restore',
@@ -174,6 +175,8 @@ const SIGNED_METHODS = new Set([
174
175
  'group.review_join_request',
175
176
  'group.batch_review_join_request',
176
177
  'group.request_join', 'group.use_invite_code',
178
+ 'group.invite_member', 'group.list_my_invites', 'group.accept_invite',
179
+ 'group.reject_invite', 'group.revoke_invite',
177
180
  'group.thought.put',
178
181
  'message.thought.put',
179
182
  'group.set_settings',
@@ -468,6 +471,9 @@ export class AUNClient {
468
471
  _seqTrackerContext = null;
469
472
  /** 惰性群同步:已同步过的 group_id 集合 */
470
473
  _groupSynced = new Set();
474
+ /** 群定向邀请按 group_id + invitee_aid 聚合去重。 */
475
+ _groupInviteAggregates = new Map();
476
+ _groupInviteAggregatesLoaded = false;
471
477
  /** P2P 撤回去重:原始 message_id -> 时间戳,保证应用层只回调一次 */
472
478
  _messageRecallSeen = new Map();
473
479
  /** 群撤回去重:group_id|sorted(message_ids) -> 时间戳,保证应用层只回调一次 */
@@ -680,6 +686,9 @@ export class AUNClient {
680
686
  this._dispatcher.subscribe('_raw.group.message_recalled', (data) => this._safeAsync(this._onRawGroupMessageRecalled(data)));
681
687
  // 群组变更事件:透传并触发 V2 state/SPK 维护
682
688
  this._dispatcher.subscribe('_raw.group.changed', (data) => this._onRawGroupChanged(data));
689
+ // 定向邀请底层事件:按群 + 目标 AID 聚合后向应用层只提示一次。
690
+ this._dispatcher.subscribe('_raw.group.invite_created', (data) => this._onRawGroupInviteCreated(data));
691
+ this._dispatcher.subscribe('_raw.group.invite_finalized', (data) => this._onRawGroupInviteFinalized(data));
683
692
  // V2 state proposal 服务平面事件:owner/admin 负责确认或重新提案
684
693
  this._dispatcher.subscribe('_raw.group.v2.state_proposed', (data) => this._safeAsync(this._onV2StateProposed(data)));
685
694
  this._dispatcher.subscribe('_raw.group.v2.state_retry_needed', (data) => this._safeAsync(this._onV2StateRetryNeeded(data)));
@@ -1068,6 +1077,62 @@ export class AUNClient {
1068
1077
  await this._agentMdManager.upload(agentMd);
1069
1078
  return true;
1070
1079
  }
1080
+ async _checkIdentityAdmissionAfterConnect() {
1081
+ const aid = String(this._aid ?? this._currentAid?.aid ?? '').trim();
1082
+ if (!aid)
1083
+ return;
1084
+ try {
1085
+ const checked = await this._agentMdManager.check(aid);
1086
+ if (!checked.remote_found) {
1087
+ const content = this._agentMdManager.readContent(aid) || buildDefaultAgentMd(aid);
1088
+ await this._agentMdManager.upload(content);
1089
+ }
1090
+ }
1091
+ catch (exc) {
1092
+ this._clientLog.warn(`post-connect agent.md check failed: ${formatCaughtError(exc)}`);
1093
+ }
1094
+ const store = this._aidStore;
1095
+ if (!store)
1096
+ return;
1097
+ let listed;
1098
+ try {
1099
+ listed = await this.call('group.list_my', {});
1100
+ }
1101
+ catch (exc) {
1102
+ this._clientLog.warn(`post-connect group identity check failed: ${formatCaughtError(exc)}`);
1103
+ return;
1104
+ }
1105
+ const result = isJsonObject(listed) ? listed : {};
1106
+ const groups = Array.isArray(result.groups) ? result.groups : (Array.isArray(result.items) ? result.items : []);
1107
+ for (const raw of groups) {
1108
+ if (!isJsonObject(raw))
1109
+ continue;
1110
+ const group = raw;
1111
+ const role = String(group.role ?? group.my_role ?? '').trim().toLowerCase();
1112
+ if (role !== 'owner')
1113
+ continue;
1114
+ const groupId = String(group.group_id ?? group.groupId ?? group.group_aid ?? group.groupAid ?? '').trim();
1115
+ const groupAid = String(group.group_aid ?? group.groupAid ?? groupId).trim();
1116
+ if (!groupId || !groupAid)
1117
+ continue;
1118
+ try {
1119
+ await this._runGroupIdentityOperation(groupId, async () => {
1120
+ const loaded = store.load(groupAid);
1121
+ if (!loaded.ok || !loaded.data?.aid?.isPrivateKeyValid()) {
1122
+ await this.bindGroupAid({ group_id: groupId, group_aid: groupAid }, { aidStore: store });
1123
+ return;
1124
+ }
1125
+ const checked = await this._agentMdManager.check(groupAid);
1126
+ if (!checked.remote_found) {
1127
+ await this._uploadGroupAgentMd(store, groupAid, {}, {});
1128
+ }
1129
+ });
1130
+ }
1131
+ catch (exc) {
1132
+ this._clientLog.warn(`post-connect group identity check failed: group=${groupId} err=${formatCaughtError(exc)}`);
1133
+ }
1134
+ }
1135
+ }
1071
1136
  async _runGroupIdentityOperation(groupId, operation) {
1072
1137
  const aid = String(this._aid ?? '').trim();
1073
1138
  const dot = aid.indexOf('.');
@@ -1097,6 +1162,8 @@ export class AUNClient {
1097
1162
  }
1098
1163
  this._aidStore = store;
1099
1164
  const payload = { ...params };
1165
+ const initialMembers = payload.members;
1166
+ delete payload.members;
1100
1167
  if (payload.group_name === undefined && payload.groupName !== undefined) {
1101
1168
  payload.group_name = payload.groupName;
1102
1169
  delete payload.groupName;
@@ -1124,7 +1191,8 @@ export class AUNClient {
1124
1191
  const result = { ...createdMap, ...boundMap, group: { ...createdGroup, ...boundGroup } };
1125
1192
  const postprocessParams = { ...payload };
1126
1193
  delete postprocessParams._defer_group_ready_postprocess;
1127
- return await this._groupState.postprocessResult('group.create', postprocessParams, result);
1194
+ const processed = await this._groupState.postprocessResult('group.create', postprocessParams, result);
1195
+ return await this._inviteInitialGroupMembers(processed, groupId, initialMembers);
1128
1196
  }
1129
1197
  const pendingKey = `create:${String(payload.group_name).trim().toLowerCase()}`;
1130
1198
  const keystore = store._keystore;
@@ -1167,7 +1235,32 @@ export class AUNClient {
1167
1235
  }
1168
1236
  const postprocessParams = { ...payload };
1169
1237
  delete postprocessParams._defer_group_ready_postprocess;
1170
- return await this._groupState.postprocessResult('group.create', postprocessParams, result);
1238
+ const processed = await this._groupState.postprocessResult('group.create', postprocessParams, result);
1239
+ return await this._inviteInitialGroupMembers(processed, String(group.group_id ?? groupAid).trim(), initialMembers);
1240
+ }
1241
+ async _inviteInitialGroupMembers(result, groupId, members) {
1242
+ const invites = [];
1243
+ const inviteErrors = [];
1244
+ const seen = new Set();
1245
+ if (Array.isArray(members)) {
1246
+ for (const item of members) {
1247
+ const aid = typeof item === 'string' ? item.trim() : (isJsonObject(item) ? String(item.aid ?? '').trim() : '');
1248
+ const memberType = isJsonObject(item) ? String(item.member_type ?? '').trim() : '';
1249
+ if (!aid || aid === String(this._aid ?? '').trim() || seen.has(aid))
1250
+ continue;
1251
+ seen.add(aid);
1252
+ const params = { group_id: groupId, invitee_aid: aid };
1253
+ if (memberType)
1254
+ params.member_type = memberType;
1255
+ try {
1256
+ invites.push(await this.group.inviteMember(params));
1257
+ }
1258
+ catch (err) {
1259
+ inviteErrors.push({ aid, error: err instanceof Error ? err.message : String(err) });
1260
+ }
1261
+ }
1262
+ }
1263
+ return { ...(isJsonObject(result) ? result : {}), invites, invite_errors: inviteErrors };
1171
1264
  }
1172
1265
  _groupAgentMdContent(groupAid, params, group) {
1173
1266
  const explicit = params.group_agent_md ?? params.groupAgentMd ?? params.content;
@@ -1949,8 +2042,8 @@ export class AUNClient {
1949
2042
  async _publishOrderedMessage(event, ns, seq, payload, source = 'push') {
1950
2043
  return this._delivery.publishOrderedMessage(event, ns, seq, payload, source);
1951
2044
  }
1952
- async _publishPulledMessage(event, ns, seq, payload, persist = true) {
1953
- return this._delivery.publishPulledMessage(event, ns, seq, payload, persist);
2045
+ async _publishPulledMessage(event, ns, seq, payload, persist = true, source = 'pull') {
2046
+ return this._delivery.publishPulledMessage(event, ns, seq, payload, persist, source);
1954
2047
  }
1955
2048
  _markOrderedSeqDelivered(ns, seq) {
1956
2049
  if (!ns || !Number.isFinite(seq) || !Number.isInteger(seq) || seq <= 0)
@@ -2325,6 +2418,225 @@ export class AUNClient {
2325
2418
  _persistSeq(ns, forceSeq) {
2326
2419
  return this._delivery.persistSeq(ns, forceSeq);
2327
2420
  }
2421
+ static GROUP_INVITE_METADATA_KEY = 'group_invites_pending_v1';
2422
+ _groupInviteArray(value) {
2423
+ if (Array.isArray(value))
2424
+ return value.map((item) => String(item ?? '').trim()).filter(Boolean);
2425
+ const item = String(value ?? '').trim();
2426
+ return item ? [item] : [];
2427
+ }
2428
+ _groupInvitePayload(data) {
2429
+ if (!isJsonObject(data))
2430
+ return null;
2431
+ const source = data;
2432
+ const groupId = normalizeGroupId(source.group_id ?? source.group_aid ?? source.groupId ?? source.groupAid);
2433
+ const inviteeAid = String(source.invitee_aid ?? source.inviteeAid ?? this._aid ?? '').trim();
2434
+ if (!groupId || !inviteeAid)
2435
+ return null;
2436
+ if (this._aid && inviteeAid.toLowerCase() !== this._aid.toLowerCase())
2437
+ return null;
2438
+ const inviteIds = this._groupInviteArray(source.invite_ids ?? source.invite_id ?? source.inviteIds);
2439
+ const inviterAids = this._groupInviteArray(source.inviter_aids ?? source.inviter_aid ?? source.inviterAids);
2440
+ return {
2441
+ ...source,
2442
+ group_id: groupId,
2443
+ group_aid: String(source.group_aid ?? groupId).trim() || groupId,
2444
+ invite_ids: inviteIds,
2445
+ inviter_aids: inviterAids,
2446
+ invitee_aid: inviteeAid,
2447
+ role: String(source.role ?? 'member'),
2448
+ member_type: String(source.member_type ?? 'human'),
2449
+ status: String(source.status ?? 'pending').toLowerCase() || 'pending',
2450
+ };
2451
+ }
2452
+ _groupInviteKey(payload) {
2453
+ return `${String(payload.group_id ?? '').trim().toLowerCase()}\u0000${String(payload.invitee_aid ?? '').trim().toLowerCase()}`;
2454
+ }
2455
+ _loadGroupInviteAggregates() {
2456
+ if (this._groupInviteAggregatesLoaded)
2457
+ return;
2458
+ this._groupInviteAggregatesLoaded = true;
2459
+ const aid = String(this._aid ?? '').trim();
2460
+ const store = this._tokenStore;
2461
+ if (!aid || !store || typeof store.loadMetadata !== 'function')
2462
+ return;
2463
+ try {
2464
+ const raw = store.loadMetadata(aid)?.[AUNClient.GROUP_INVITE_METADATA_KEY];
2465
+ const parsed = typeof raw === 'string' ? JSON.parse(raw) : raw;
2466
+ if (!isJsonObject(parsed))
2467
+ return;
2468
+ for (const [key, value] of Object.entries(parsed)) {
2469
+ if (isJsonObject(value) && String(value.status ?? 'pending').toLowerCase() === 'pending') {
2470
+ this._groupInviteAggregates.set(key, value);
2471
+ }
2472
+ }
2473
+ }
2474
+ catch (exc) {
2475
+ this._clientLog.debug(`group invite metadata restore failed: ${formatCaughtError(exc)}`);
2476
+ }
2477
+ }
2478
+ _persistGroupInviteAggregates() {
2479
+ const aid = String(this._aid ?? '').trim();
2480
+ const store = this._tokenStore;
2481
+ if (!aid || !store || typeof store.saveMetadata !== 'function')
2482
+ return;
2483
+ try {
2484
+ const values = {};
2485
+ for (const [key, value] of this._groupInviteAggregates)
2486
+ values[key] = value;
2487
+ store.saveMetadata(aid, { [AUNClient.GROUP_INVITE_METADATA_KEY]: JSON.stringify(values) });
2488
+ }
2489
+ catch (exc) {
2490
+ this._clientLog.debug(`group invite metadata persist failed: ${formatCaughtError(exc)}`);
2491
+ }
2492
+ }
2493
+ async _onRawGroupInviteCreated(data) {
2494
+ await this._handleGroupInviteAggregate(data);
2495
+ }
2496
+ async _onRawGroupInviteFinalized(data) {
2497
+ const payload = this._groupInvitePayload(data);
2498
+ if (!payload)
2499
+ return;
2500
+ this._loadGroupInviteAggregates();
2501
+ const key = this._groupInviteKey(payload);
2502
+ const existing = this._groupInviteAggregates.get(key);
2503
+ if (!existing)
2504
+ return;
2505
+ const source = data;
2506
+ const status = String(payload.status ?? '').toLowerCase();
2507
+ const aggregateStatus = String(source.aggregate_status ?? source.aggregateStatus ?? '').toLowerCase();
2508
+ const remainingPending = source.remaining_pending === true
2509
+ || source.remaining_pending === 1
2510
+ || String(source.remaining_pending ?? '').toLowerCase() === 'true';
2511
+ const pending = source.pending === true
2512
+ || source.pending === 1
2513
+ || String(source.pending ?? '').toLowerCase() === 'true';
2514
+ const pendingIds = this._groupInviteArray(source.pending_invite_ids ?? source.pendingInviteIds);
2515
+ const pendingInviters = this._groupInviteArray(source.pending_inviter_aids ?? source.pendingInviterAids);
2516
+ const currentIds = this._groupInviteArray(existing.invite_ids);
2517
+ const inviteId = String(source.invite_id ?? '').trim();
2518
+ const inferredPartialRevoke = status === 'revoked' && inviteId
2519
+ && currentIds.length > 1 && currentIds.includes(inviteId);
2520
+ const leavesPending = remainingPending || pending || status === 'pending'
2521
+ || aggregateStatus === 'pending' || pendingIds.length > 0 || inferredPartialRevoke;
2522
+ if (!leavesPending) {
2523
+ this._groupInviteAggregates.delete(key);
2524
+ }
2525
+ else {
2526
+ const finalizedIds = new Set(remainingPending && inviteId
2527
+ ? [inviteId]
2528
+ : this._groupInviteArray(payload.invite_ids));
2529
+ const nextIds = pendingIds.length > 0
2530
+ ? pendingIds
2531
+ : this._groupInviteArray(existing.invite_ids).filter((id) => !finalizedIds.has(id));
2532
+ if (nextIds.length === 0) {
2533
+ this._groupInviteAggregates.delete(key);
2534
+ }
2535
+ else {
2536
+ existing.invite_ids = nextIds;
2537
+ existing.inviter_aids = pendingInviters.length > 0
2538
+ ? pendingInviters
2539
+ : this._groupInviteArray(existing.inviter_aids);
2540
+ }
2541
+ }
2542
+ this._persistGroupInviteAggregates();
2543
+ }
2544
+ async _handleGroupInviteAggregate(data) {
2545
+ const payload = this._groupInvitePayload(data);
2546
+ if (!payload)
2547
+ return;
2548
+ this._loadGroupInviteAggregates();
2549
+ const status = String(payload.status ?? 'pending').toLowerCase();
2550
+ const key = this._groupInviteKey(payload);
2551
+ if (status !== 'pending') {
2552
+ if (this._groupInviteAggregates.delete(key))
2553
+ this._persistGroupInviteAggregates();
2554
+ return;
2555
+ }
2556
+ const existing = this._groupInviteAggregates.get(key);
2557
+ if (existing) {
2558
+ existing.invite_ids = [...new Set([
2559
+ ...this._groupInviteArray(existing.invite_ids),
2560
+ ...this._groupInviteArray(payload.invite_ids),
2561
+ ])];
2562
+ existing.inviter_aids = [...new Set([
2563
+ ...this._groupInviteArray(existing.inviter_aids),
2564
+ ...this._groupInviteArray(payload.inviter_aids),
2565
+ ])];
2566
+ this._persistGroupInviteAggregates();
2567
+ return;
2568
+ }
2569
+ const snapshot = {
2570
+ ...payload,
2571
+ invite_ids: this._groupInviteArray(payload.invite_ids),
2572
+ inviter_aids: this._groupInviteArray(payload.inviter_aids),
2573
+ };
2574
+ this._groupInviteAggregates.set(key, snapshot);
2575
+ this._persistGroupInviteAggregates();
2576
+ this._dispatcher.enqueue('group.invite_received', { ...snapshot, invite_ids: [...this._groupInviteArray(snapshot.invite_ids)], inviter_aids: [...this._groupInviteArray(snapshot.inviter_aids)] });
2577
+ }
2578
+ /** 连接建立/恢复后从事实列表补发尚未提示的群级邀请。 */
2579
+ async _recoverGroupInvites(result) {
2580
+ let response = result;
2581
+ let cursor = '';
2582
+ const seen = new Set();
2583
+ do {
2584
+ if (response === undefined) {
2585
+ response = await this.call('group.list_my_invites', cursor ? { cursor } : {});
2586
+ }
2587
+ let items = [];
2588
+ let nextCursor = '';
2589
+ if (Array.isArray(response))
2590
+ items = response;
2591
+ else if (isJsonObject(response)) {
2592
+ const responseObject = response;
2593
+ if (Array.isArray(responseObject.items))
2594
+ items = responseObject.items;
2595
+ else if (Array.isArray(responseObject.invites))
2596
+ items = responseObject.invites;
2597
+ else if (isJsonObject(responseObject.data) && Array.isArray(responseObject.data.items))
2598
+ items = responseObject.data.items;
2599
+ nextCursor = String(responseObject.next_cursor ?? responseObject.nextCursor ?? responseObject.cursor ?? '');
2600
+ }
2601
+ for (const item of items) {
2602
+ const payload = this._groupInvitePayload(item);
2603
+ if (payload)
2604
+ seen.add(this._groupInviteKey(payload));
2605
+ await this._handleGroupInviteAggregate(item);
2606
+ }
2607
+ cursor = nextCursor;
2608
+ response = undefined;
2609
+ } while (cursor);
2610
+ this._loadGroupInviteAggregates();
2611
+ let stale = false;
2612
+ for (const key of this._groupInviteAggregates.keys()) {
2613
+ if (!seen.has(key)) {
2614
+ this._groupInviteAggregates.delete(key);
2615
+ stale = true;
2616
+ }
2617
+ }
2618
+ if (stale)
2619
+ this._persistGroupInviteAggregates();
2620
+ }
2621
+ /** 接受/拒绝/撤回成功后清理本地待处理聚合项。 */
2622
+ clearGroupInviteAggregate(params) {
2623
+ this._loadGroupInviteAggregates();
2624
+ const input = params ?? {};
2625
+ const inviteId = String(input.invite_id ?? input.inviteId ?? '').trim();
2626
+ const groupId = normalizeGroupId(input.group_id ?? input.group_aid ?? input.groupId ?? input.groupAid);
2627
+ const inviteeAid = String(input.invitee_aid ?? input.inviteeAid ?? this._aid ?? '').trim();
2628
+ let removed = false;
2629
+ for (const [key, value] of this._groupInviteAggregates) {
2630
+ const matchesKey = groupId && inviteeAid && key === `${groupId.toLowerCase()}\u0000${inviteeAid.toLowerCase()}`;
2631
+ const matchesInvite = inviteId && this._groupInviteArray(value.invite_ids).includes(inviteId);
2632
+ if (matchesKey || matchesInvite) {
2633
+ this._groupInviteAggregates.delete(key);
2634
+ removed = true;
2635
+ }
2636
+ }
2637
+ if (removed)
2638
+ this._persistGroupInviteAggregates();
2639
+ }
2328
2640
  _commitSeqTrackerState(ns) {
2329
2641
  return this._delivery.commitSeqTrackerState(ns);
2330
2642
  }
@@ -2338,19 +2650,10 @@ export class AUNClient {
2338
2650
  return this._delivery.clampAckParams(method, params);
2339
2651
  }
2340
2652
  _repairPushContiguousBound(ns, pushSeq, hasPayload, label) {
2341
- if (!ns || !Number.isFinite(pushSeq) || pushSeq <= 0) {
2342
- return ns ? this._seqTracker.getContiguousSeq(ns) : 0;
2343
- }
2344
- const contig = this._seqTracker.getContiguousSeq(ns);
2345
- const shouldRepair = contig > pushSeq;
2346
- if (!shouldRepair)
2347
- return contig;
2348
- const repairedTo = Math.max(0, pushSeq - 1);
2349
- this._seqTracker.repairContiguousSeq(ns, repairedTo);
2350
- const repaired = this._seqTracker.getContiguousSeq(ns);
2351
- this._persistRepairedSeq(ns);
2352
- this._clientLog.warn(`${label} push repaired contiguous_seq: ns=${ns} payload=${hasPayload} push_seq=${pushSeq} contiguous=${contig}->${repaired}`);
2353
- return repaired;
2653
+ void pushSeq;
2654
+ void hasPayload;
2655
+ void label;
2656
+ return ns ? this._seqTracker.getContiguousSeq(ns) : 0;
2354
2657
  }
2355
2658
  // ── URL 辅助 ──────────────────────────────────────────────
2356
2659
  /** 跨域时将 Gateway URL 替换为 peer 所在域的 Gateway URL */
@@ -2502,6 +2805,12 @@ export class AUNClient {
2502
2805
  const hasExplicitBackgroundSync = Object.prototype.hasOwnProperty.call(params, 'background_sync');
2503
2806
  const backgroundSyncEnabled = this._sessionOptions.background_sync !== false
2504
2807
  && (!isShortConnection || hasExplicitBackgroundSync);
2808
+ if (backgroundSyncEnabled) {
2809
+ // 连接建立/恢复后先从事实列表恢复定向邀请提示;失败不影响连接本身。
2810
+ this._safeAsync(this._recoverGroupInvites().catch((exc) => {
2811
+ this._clientLog.debug(`group invite recovery failed: ${formatCaughtError(exc)}`);
2812
+ }));
2813
+ }
2505
2814
  if (!isShortConnection) {
2506
2815
  await this._v2E2EE.onConnected({ backgroundSync: backgroundSyncEnabled });
2507
2816
  this._assertReconnectOwner(reconnectOwner);
@@ -2517,6 +2826,7 @@ export class AUNClient {
2517
2826
  this._clientLog.warn(`schedule post-connect P2P gap fill failed: ${formatCaughtError(exc)}`);
2518
2827
  });
2519
2828
  }
2829
+ this._safeAsync(this._checkIdentityAdmissionAfterConnect());
2520
2830
  this._clientLog.debug(`_connectOnce exit: elapsed=${Date.now() - tStart}ms gateway=${gatewayUrl}, aid=${this._aid ?? ''}`);
2521
2831
  }
2522
2832
  catch (err) {
@@ -2730,7 +3040,7 @@ export class AUNClient {
2730
3040
  return await this._v2E2EE.ackGroupV2(groupId, upToSeq, groupAid);
2731
3041
  }
2732
3042
  /** 解密单条 V2 pull 消息。缺 sender IK 时先入 pending,后台补齐后重试。 */
2733
- async _decryptV2Message(msg, allowPending = true, emitUndecryptable = true, rotateKeys = true, observeAgentMd = true, deferStatus, deferKeyFetch = false) {
3043
+ async _decryptV2Message(msg, allowPending = true, emitUndecryptable = true, rotateKeys = true, observeAgentMd = true, deferStatus, deferKeyFetch = false, source = 'pull') {
2734
3044
  const session = this._v2Session;
2735
3045
  if (!session)
2736
3046
  return null;
@@ -2823,6 +3133,7 @@ export class AUNClient {
2823
3133
  _envelope_type: String(envelope.type ?? ''),
2824
3134
  _suite: String(envelope.suite ?? ''),
2825
3135
  _spk_id: spkId,
3136
+ source,
2826
3137
  };
2827
3138
  this._attachV2EnvelopeMetadata(event, e2eeMeta);
2828
3139
  this._logMessageDebug('decrypt-fail', 'v2.decrypt', undecryptableEvent, event);
@@ -2862,6 +3173,7 @@ export class AUNClient {
2862
3173
  _decrypt_stage: 'sender_ik',
2863
3174
  _envelope_type: String(envelope.type ?? ''),
2864
3175
  _suite: String(envelope.suite ?? ''),
3176
+ source,
2865
3177
  };
2866
3178
  this._attachV2EnvelopeMetadata(event, e2eeMeta);
2867
3179
  this._logMessageDebug('decrypt-fail', 'v2.decrypt', undecryptableEvent, event);
@@ -2891,6 +3203,7 @@ export class AUNClient {
2891
3203
  _decrypt_stage: 'decrypt',
2892
3204
  _envelope_type: String(envelope.type ?? ''),
2893
3205
  _suite: String(envelope.suite ?? ''),
3206
+ source,
2894
3207
  };
2895
3208
  this._attachV2EnvelopeMetadata(event, e2eeMeta);
2896
3209
  this._logMessageDebug('decrypt-fail', 'v2.decrypt', undecryptableEvent, event);