@agentunion/fastaun-browser 0.5.10 → 0.5.11

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.
@@ -902,7 +902,7 @@ export class V2E2EECoordinator {
902
902
  let plaintext = null;
903
903
  const retryStatus = {};
904
904
  try {
905
- plaintext = await client._decryptV2Message(entry.msg, false, true, true, true, retryStatus);
905
+ plaintext = await client._decryptV2Message(entry.msg, false, true, true, true, retryStatus, false, 'pending_retry');
906
906
  }
907
907
  catch (exc) {
908
908
  if (!generationCurrent())
@@ -932,10 +932,10 @@ export class V2E2EECoordinator {
932
932
  if (entry.groupId) {
933
933
  plaintext = normalizeGroupMentionMode(plaintext, entry.msg, entry.msg.envelope_json);
934
934
  plaintext.group_id = entry.groupId;
935
- await client._publishPulledMessage('group.message_created', `group:${entry.groupId}`, seq, plaintext);
935
+ await client._publishPulledMessage('group.message_created', `group:${entry.groupId}`, seq, plaintext, false, 'pending_retry');
936
936
  }
937
937
  else {
938
- await client._publishPulledMessage('message.received', `p2p:${client._aid ?? ''}`, seq, plaintext);
938
+ await client._publishPulledMessage('message.received', `p2p:${client._aid ?? ''}`, seq, plaintext, false, 'pending_retry');
939
939
  }
940
940
  client._clientLog.debug(`V2 sender IK pending retry delivered: key=${key}`);
941
941
  }
@@ -1014,10 +1014,10 @@ export class V2E2EECoordinator {
1014
1014
  if (String(msg.version ?? '') === 'v1') {
1015
1015
  const legacy = isJsonObject(msg.legacy_v1)
1016
1016
  ? msg.legacy_v1 : {};
1017
- const payload = legacy.payload;
1017
+ const payload = legacy.payload !== undefined ? legacy.payload : msg.payload;
1018
1018
  const payloadType = isJsonObject(payload)
1019
1019
  ? String(payload.type ?? '').trim() : '';
1020
- if (payload !== undefined && payload !== null && !['e2ee.encrypted', 'e2ee.group_encrypted'].includes(payloadType)) {
1020
+ if (payload !== undefined && payload !== null && !['e2ee.encrypted', 'e2ee.group_encrypted', 'e2ee.p2p_encrypted'].includes(payloadType)) {
1021
1021
  plaintext = {
1022
1022
  message_id: String(msg.message_id ?? ''), from: String(msg.from_aid ?? ''),
1023
1023
  to: String(legacy.to ?? client._aid ?? ''), seq, type: String(msg.type ?? ''),
@@ -1025,6 +1025,11 @@ export class V2E2EECoordinator {
1025
1025
  };
1026
1026
  attachGatewayProximity(plaintext, msg);
1027
1027
  }
1028
+ else if (isJsonObject(payload)
1029
+ && ['e2ee.p2p_encrypted', 'e2ee.encrypted'].includes(payloadType)) {
1030
+ msg = { ...msg, envelope_json: JSON.stringify(payload) };
1031
+ plaintext = await client._decryptV2Message(msg, false, !history, true, true, undefined, false, source);
1032
+ }
1028
1033
  }
1029
1034
  else {
1030
1035
  plaintext = source === 'inline_push'
@@ -1054,26 +1059,48 @@ export class V2E2EECoordinator {
1054
1059
  let plaintext = null;
1055
1060
  if (String(msg.version ?? '') === 'v1') {
1056
1061
  const payload = msg.payload;
1062
+ const recall = typeof client._delivery?.recallEventFromGroupMessage === 'function'
1063
+ ? client._delivery.recallEventFromGroupMessage(msg)
1064
+ : null;
1065
+ if (recall) {
1066
+ const recallPayload = {
1067
+ ...recall,
1068
+ group_id: groupId,
1069
+ group_aid: groupAid,
1070
+ seq,
1071
+ };
1072
+ if (publish) {
1073
+ if (orderedPublish) {
1074
+ await client._delivery.publishOrderedMessage('group.message_recalled', `group:${groupId}`, seq, recallPayload, source);
1075
+ }
1076
+ else {
1077
+ await client._publishAppEvent('group.message_recalled', recallPayload, source);
1078
+ }
1079
+ }
1080
+ return recallPayload;
1081
+ }
1057
1082
  if (payload === undefined || payload === null) {
1058
1083
  if (history)
1059
1084
  return this.historyDecryptFailure(msg, 'legacy payload is missing');
1060
1085
  client._clientLog.warn(`Group Tail 缺少 payload 的 V1 行已跳过,连续性证明仍保留: group=${groupId}, seq=${seq}`);
1061
1086
  return null;
1062
1087
  }
1088
+ const payloadType = isJsonObject(payload)
1089
+ ? String(payload.type ?? '').trim() : '';
1063
1090
  if (isJsonObject(payload)
1064
- && ['e2ee.encrypted', 'e2ee.group_encrypted'].includes(String(payload.type ?? ''))) {
1065
- if (history)
1066
- return this.historyDecryptFailure(msg, 'unsupported legacy encrypted envelope');
1067
- client._clientLog.warn(`Group Tail 不支持的旧加密消息已跳过,连续性证明仍保留: group=${groupId}, seq=${seq}`);
1068
- return null;
1091
+ && ['e2ee.group_encrypted', 'e2ee.p2p_encrypted', 'e2ee.encrypted'].includes(payloadType)) {
1092
+ msg = { ...msg, envelope_json: JSON.stringify(payload) };
1093
+ plaintext = await client._decryptV2Message(msg, false, !history, true, true, undefined, false, source);
1094
+ }
1095
+ else {
1096
+ plaintext = {
1097
+ message_id: String(msg.message_id ?? ''), from: String(msg.from_aid ?? ''),
1098
+ group_id: groupId, group_aid: groupAid, seq, type: String(msg.type ?? ''),
1099
+ timestamp: msg.t_server, payload: payload, encrypted: false,
1100
+ };
1101
+ plaintext = normalizeGroupMentionMode(plaintext, msg, msg.envelope_json);
1102
+ attachGatewayProximity(plaintext, msg);
1069
1103
  }
1070
- plaintext = {
1071
- message_id: String(msg.message_id ?? ''), from: String(msg.from_aid ?? ''),
1072
- group_id: groupId, group_aid: groupAid, seq, type: String(msg.type ?? ''),
1073
- timestamp: msg.t_server, payload: payload, encrypted: false,
1074
- };
1075
- plaintext = normalizeGroupMentionMode(plaintext, msg, msg.envelope_json);
1076
- attachGatewayProximity(plaintext, msg);
1077
1104
  }
1078
1105
  else {
1079
1106
  plaintext = source === 'inline_push'
@@ -1129,6 +1156,8 @@ export class V2E2EECoordinator {
1129
1156
  const afterSeq = strictWindowSeq(params.after_seq ?? 0, 'after_seq');
1130
1157
  const limit = strictWindowLimit(params.limit ?? V2_PULL_DEFAULT_PAGE_LIMIT);
1131
1158
  const ns = client._aid ? `p2p:${client._aid}` : '';
1159
+ if (ns)
1160
+ client._delivery.onPullStarted?.(ns);
1132
1161
  const result = await client._callRawV2Rpc('message.v2.pull', { window_mode: 'tail', after_seq: afterSeq, limit, _rpc_foreground: true });
1133
1162
  const floor = Number(result.retention_floor_seq ?? 0);
1134
1163
  const previous = ns ? client._seqTracker.getSyncState(ns) : null;
@@ -1150,6 +1179,9 @@ export class V2E2EECoordinator {
1150
1179
  const ack = ns ? client._seqTracker.getContiguousSeq(ns) : 0;
1151
1180
  if (previous && ack > previous.ack)
1152
1181
  await this.ackV2(ack);
1182
+ if (ns)
1183
+ client._delivery.onPullPage?.(ns, decoded.length, result.has_more === true, page.head, typeof result.remaining === 'number' && Number.isSafeInteger(result.remaining) && result.remaining >= 0 ? result.remaining : null, page.messages.length, ack, page.head);
1184
+ client._delivery.onPullWorkSettled?.();
1153
1185
  return { ...result, messages: decoded, raw_count: page.messages.length };
1154
1186
  }
1155
1187
  async historyV2Internal(params) {
@@ -1165,16 +1197,33 @@ export class V2E2EECoordinator {
1165
1197
  }
1166
1198
  return { ...result, messages: decoded };
1167
1199
  }
1200
+ async runPullWithLifecycle(ns, operation) {
1201
+ if (ns)
1202
+ this.client._delivery.onPullStarted?.(ns);
1203
+ try {
1204
+ return await operation();
1205
+ }
1206
+ catch (error) {
1207
+ this.client._delivery.onPullAborted?.();
1208
+ throw error;
1209
+ }
1210
+ }
1168
1211
  async pullV2(afterSeq = 0, limit = 50, opts) {
1212
+ const ns = this.client._aid ? `p2p:${this.client._aid}` : '';
1213
+ return await this.runPullWithLifecycle(ns, () => this.pullV2Impl(afterSeq, limit, opts));
1214
+ }
1215
+ async pullV2Impl(afterSeq = 0, limit = 50, opts) {
1169
1216
  const client = this.client;
1170
1217
  await client._ensureV2SessionReady('message.pull');
1171
1218
  const ns = client._aid ? `p2p:${client._aid}` : '';
1219
+ if (ns)
1220
+ client._delivery.onPullStarted?.(ns);
1172
1221
  if (opts?.windowMode === 'tail') {
1173
1222
  if (!opts.gateLocked) {
1174
1223
  const key = pullGateKeyForClient(client, 'message.v2.pull', {
1175
1224
  window_mode: 'tail', after_seq: afterSeq, limit,
1176
1225
  }, `${ns}|tail|after=${afterSeq}|limit=${limit}`);
1177
- return await client._runPullSerialized(key, () => this.pullV2(afterSeq, limit, {
1226
+ return await client._runPullSerialized(key, () => this.pullV2Impl(afterSeq, limit, {
1178
1227
  ...(opts ?? {}),
1179
1228
  gateLocked: true,
1180
1229
  }), false);
@@ -1186,7 +1235,7 @@ export class V2E2EECoordinator {
1186
1235
  const key = pullGateKeyForClient(client, 'message.v2.pull', {
1187
1236
  after_seq: afterSeq, force: opts?.force === true, limit,
1188
1237
  }, `${ns}|forward|after=${afterSeq}|force=${String(Boolean(opts?.force))}|limit=${limit}`);
1189
- return await client._runPullSerialized(key, () => this.pullV2(afterSeq, limit, {
1238
+ return await client._runPullSerialized(key, () => this.pullV2Impl(afterSeq, limit, {
1190
1239
  ...(opts ?? {}),
1191
1240
  gateLocked: true,
1192
1241
  }), true);
@@ -1250,6 +1299,7 @@ export class V2E2EECoordinator {
1250
1299
  const pageMaxSeq = seqs.length > 0 ? Math.max(...seqs) : nextAfterSeq;
1251
1300
  const deferredKeyFetches = new Map();
1252
1301
  let blockedSeq = 0;
1302
+ const pageDecryptedBefore = decrypted.length;
1253
1303
  for (const msg of messages) {
1254
1304
  const seq = Number(msg.seq ?? 0);
1255
1305
  if (!Number.isFinite(seq) || seq <= 0)
@@ -1257,12 +1307,12 @@ export class V2E2EECoordinator {
1257
1307
  const version = String(msg.version ?? 'v2');
1258
1308
  if (version === 'v1') {
1259
1309
  const legacy = isJsonObject(msg.legacy_v1) ? msg.legacy_v1 : {};
1260
- const legacyPayload = legacy.payload;
1310
+ const legacyPayload = legacy.payload !== undefined ? legacy.payload : msg.payload;
1261
1311
  const payloadType = isJsonObject(legacyPayload)
1262
1312
  ? String(legacyPayload.type ?? '').trim()
1263
1313
  : '';
1264
1314
  if (legacyPayload !== undefined && legacyPayload !== null
1265
- && payloadType !== 'e2ee.encrypted' && payloadType !== 'e2ee.group_encrypted') {
1315
+ && !['e2ee.encrypted', 'e2ee.group_encrypted', 'e2ee.p2p_encrypted'].includes(payloadType)) {
1266
1316
  const v1Msg = {
1267
1317
  message_id: String(msg.message_id ?? ''),
1268
1318
  from: String(msg.from_aid ?? ''),
@@ -1281,6 +1331,25 @@ export class V2E2EECoordinator {
1281
1331
  await client._publishAppEvent(appEvent.event, appEvent.payload, 'pull');
1282
1332
  decrypted.push(v1Msg);
1283
1333
  }
1334
+ else if (isJsonObject(legacyPayload)
1335
+ && ['e2ee.p2p_encrypted', 'e2ee.encrypted'].includes(payloadType)) {
1336
+ const deferStatus = {};
1337
+ const plaintext = await client._decryptV2Message({ ...msg, envelope_json: JSON.stringify(legacyPayload) }, true, true, true, true, deferStatus, true, 'pull');
1338
+ if (deferStatus.deferred) {
1339
+ blockedSeq = blockedSeq > 0 ? Math.min(blockedSeq, seq) : seq;
1340
+ if (deferStatus.fromAid) {
1341
+ const key = `${deferStatus.fromAid}\u0000${deferStatus.senderDeviceId ?? ''}\u0000${deferStatus.groupId ?? ''}`;
1342
+ deferredKeyFetches.set(key, { fromAid: deferStatus.fromAid, senderDeviceId: deferStatus.senderDeviceId ?? '', groupId: deferStatus.groupId ?? '' });
1343
+ }
1344
+ }
1345
+ if (plaintext) {
1346
+ if (ns)
1347
+ await client._publishPulledMessage('message.received', ns, seq, plaintext, false);
1348
+ else
1349
+ await client._publishAppEvent('message.received', plaintext, 'pull');
1350
+ decrypted.push(plaintext);
1351
+ }
1352
+ }
1284
1353
  else {
1285
1354
  client._clientLog.debug(`message.v2.pull skipping V1 envelope seq=${seq} payload_type=${payloadType || '<none>'} (V1 E2EE removed)`);
1286
1355
  }
@@ -1295,7 +1364,7 @@ export class V2E2EECoordinator {
1295
1364
  client._v2Session.trackOldSPKMaxSeq(msgSpkId, seq);
1296
1365
  }
1297
1366
  const deferStatus = {};
1298
- const plaintext = await client._decryptV2Message(msg, true, true, true, true, deferStatus, true);
1367
+ const plaintext = await client._decryptV2Message(msg, true, true, true, true, deferStatus, true, 'pull');
1299
1368
  if (deferStatus.deferred) {
1300
1369
  blockedSeq = blockedSeq > 0 ? Math.min(blockedSeq, seq) : seq;
1301
1370
  }
@@ -1375,7 +1444,7 @@ export class V2E2EECoordinator {
1375
1444
  if (ackNeeded) {
1376
1445
  this.recordForwardAck(ns, ackSeq);
1377
1446
  const nextAfter = Math.max(pageMaxSeq, nextAfterSeq);
1378
- const canContinuePage = messages.length > 0 && nextAfter > nextAfterSeq;
1447
+ const canContinuePage = blockedSeq <= 0 && messages.length > 0 && nextAfter > nextAfterSeq;
1379
1448
  if (canContinuePage) {
1380
1449
  pendingAckSeq = Math.max(pendingAckSeq, ackSeq);
1381
1450
  lastAutoAckSeq = Math.max(lastAutoAckSeq, ackSeq);
@@ -1386,6 +1455,8 @@ export class V2E2EECoordinator {
1386
1455
  }
1387
1456
  }
1388
1457
  }
1458
+ if (ns)
1459
+ client._delivery.onPullPage?.(ns, decrypted.length - pageDecryptedBefore, hasMore, pageMaxSeq, typeof result.remaining === 'number' && Number.isSafeInteger(result.remaining) && result.remaining >= 0 ? result.remaining : null, messages.length, client._seqTracker.getContiguousSeq(ns), pageMaxSeq);
1389
1460
  const nextAfter = Math.max(pageMaxSeq, nextAfterSeq);
1390
1461
  const rawCount = messages.length;
1391
1462
  if (blockedSeq > 0)
@@ -1421,6 +1492,7 @@ export class V2E2EECoordinator {
1421
1492
  pendingAckSeq = 0;
1422
1493
  }
1423
1494
  }
1495
+ client._delivery.onPullWorkSettled?.();
1424
1496
  return decrypted;
1425
1497
  }
1426
1498
  async confirmForwardP2PAck(upToSeq, inlineGeneration) {
@@ -1574,6 +1646,14 @@ export class V2E2EECoordinator {
1574
1646
  const afterSeq = strictWindowSeq(params.after_seq ?? 0, 'after_seq');
1575
1647
  const limit = strictWindowLimit(params.limit ?? V2_PULL_DEFAULT_PAGE_LIMIT);
1576
1648
  const ns = `group:${groupId}`;
1649
+ const cursorParams = isJsonObject(params._group_cursor_params)
1650
+ ? params._group_cursor_params : params;
1651
+ const requestDeviceId = String(cursorParams.device_id ?? '').trim();
1652
+ const requestSlotId = String(cursorParams.slot_id ?? '').trim();
1653
+ const ownsCursor = (!requestDeviceId || requestDeviceId === String(client._deviceId ?? ''))
1654
+ && (!requestSlotId || requestSlotId === String(client._slotId ?? ''));
1655
+ if (ownsCursor)
1656
+ client._delivery.onPullStarted?.(ns);
1577
1657
  const result = await client._callRawV2Rpc('group.v2.pull', withExplicitGroupAid({
1578
1658
  group_id: groupId, window_mode: 'tail', after_seq: afterSeq, limit, _rpc_foreground: true,
1579
1659
  }, groupAid));
@@ -1599,6 +1679,9 @@ export class V2E2EECoordinator {
1599
1679
  const ack = client._seqTracker.getContiguousSeq(ns);
1600
1680
  if (ack > previous.ack)
1601
1681
  await this.ackGroupV2(groupId, ack, groupAid);
1682
+ if (ownsCursor)
1683
+ client._delivery.onPullPage?.(ns, decoded.length, result.has_more === true, page.head, typeof result.remaining === 'number' && Number.isSafeInteger(result.remaining) && result.remaining >= 0 ? result.remaining : null, page.messages.length, ack, page.head);
1684
+ client._delivery.onPullWorkSettled?.();
1602
1685
  return { ...result, messages: decoded, raw_count: page.messages.length };
1603
1686
  }
1604
1687
  async historyGroupV2Internal(params) {
@@ -1631,6 +1714,13 @@ export class V2E2EECoordinator {
1631
1714
  });
1632
1715
  }
1633
1716
  async pullGroupV2(groupId, afterSeq = 0, limit = 50, opts) {
1717
+ const lifecycleNs = `group:${String(groupId ?? '').trim()}`;
1718
+ if (opts?.ownsCursor === false) {
1719
+ return await this.pullGroupV2Impl(groupId, afterSeq, limit, opts);
1720
+ }
1721
+ return await this.runPullWithLifecycle(lifecycleNs, () => this.pullGroupV2Impl(groupId, afterSeq, limit, opts));
1722
+ }
1723
+ async pullGroupV2Impl(groupId, afterSeq = 0, limit = 50, opts) {
1634
1724
  const client = this.client;
1635
1725
  await client._ensureV2SessionReady('group.pull');
1636
1726
  const gid = String(groupId ?? '').trim();
@@ -1644,12 +1734,13 @@ export class V2E2EECoordinator {
1644
1734
  group_id: gid, window_mode: 'tail', after_seq: afterSeq, limit,
1645
1735
  _group_cursor_params: opts?.cursorParams,
1646
1736
  }, `${ns}|tail|after=${afterSeq}|limit=${limit}`);
1647
- return await client._runPullSerialized(key, () => this.pullGroupV2(gid, afterSeq, limit, {
1737
+ return await client._runPullSerialized(key, () => this.pullGroupV2Impl(gid, afterSeq, limit, {
1648
1738
  ...(opts ?? {}),
1649
1739
  gateLocked: true,
1650
1740
  }), false);
1651
1741
  }
1652
1742
  const result = await this.pullGroupV2TailInternal({
1743
+ ...(opts?.cursorParams ?? {}),
1653
1744
  group_id: String(opts.wireGroupId ?? gid), group_aid: groupAid || undefined,
1654
1745
  window_mode: 'tail', after_seq: afterSeq, limit,
1655
1746
  });
@@ -1660,7 +1751,7 @@ export class V2E2EECoordinator {
1660
1751
  group_id: gid, after_seq: afterSeq, force: opts?.force === true, limit,
1661
1752
  _group_cursor_params: opts?.cursorParams,
1662
1753
  }, `${ns}|forward|after=${afterSeq}|force=${String(Boolean(opts?.force))}|limit=${limit}`);
1663
- return await client._runPullSerialized(key, () => this.pullGroupV2(gid, afterSeq, limit, {
1754
+ return await client._runPullSerialized(key, () => this.pullGroupV2Impl(gid, afterSeq, limit, {
1664
1755
  ...(opts ?? {}),
1665
1756
  gateLocked: true,
1666
1757
  }), true);
@@ -1669,6 +1760,8 @@ export class V2E2EECoordinator {
1669
1760
  const wireGroupId = String(opts?.wireGroupId ?? groupId ?? '').trim() || gid;
1670
1761
  const cursorParams = opts?.cursorParams ?? {};
1671
1762
  const ownsCursor = opts?.ownsCursor !== false;
1763
+ if (ownsCursor)
1764
+ client._delivery.onPullStarted?.(ns);
1672
1765
  let pullGateKey = ownsCursor
1673
1766
  ? pullGateKeyForClient(client, 'group.v2.pull', {
1674
1767
  group_id: gid, after_seq: afterSeq, force: opts?.force === true, limit,
@@ -1737,28 +1830,31 @@ export class V2E2EECoordinator {
1737
1830
  const pageMaxSeq = seqs.length > 0 ? Math.max(...seqs) : nextAfterSeq;
1738
1831
  const deferredKeyFetches = new Map();
1739
1832
  let blockedSeq = 0;
1833
+ const pageDecryptedBefore = decrypted.length;
1740
1834
  for (const msg of messages) {
1741
1835
  const seq = Number(msg.seq ?? 0);
1742
1836
  if (!Number.isFinite(seq) || seq <= 0)
1743
1837
  continue;
1744
1838
  const version = String(msg.version ?? 'v2');
1745
1839
  if (version === 'v1') {
1746
- const payload = msg.payload;
1840
+ const legacy = isJsonObject(msg.legacy_v1) ? msg.legacy_v1 : {};
1841
+ const payload = msg.payload !== undefined ? msg.payload : legacy.payload;
1747
1842
  const payloadObj = isJsonObject(payload) ? payload : null;
1748
1843
  // 群撤回 tombstone(占位 / 通知):归一化为 group.message_recalled 事件,仍占 seq。
1749
- if (client._delivery.recallEventFromGroupMessage(msg)) {
1844
+ if (typeof client._delivery?.recallEventFromGroupMessage === 'function'
1845
+ && client._delivery.recallEventFromGroupMessage(msg)) {
1750
1846
  await client._delivery.publishGroupRecallTombstone(gid, seq, {
1751
1847
  ...msg,
1752
1848
  group_id: gid,
1753
1849
  group_aid: eventGroupAid,
1754
- });
1850
+ }, 'pull');
1755
1851
  client._markPublishedSeq(ns, seq);
1756
1852
  client._clientLog.debug(`group.v2.pull recall tombstone delivered: group=${gid}, seq=${seq}`);
1757
1853
  continue;
1758
1854
  }
1759
1855
  if (payloadObj) {
1760
1856
  const payloadType = String(payloadObj.type ?? '').trim();
1761
- if (payloadType !== 'e2ee.encrypted' && payloadType !== 'e2ee.group_encrypted') {
1857
+ if (payloadType !== 'e2ee.encrypted' && payloadType !== 'e2ee.group_encrypted' && payloadType !== 'e2ee.p2p_encrypted') {
1762
1858
  let v1Msg = {
1763
1859
  message_id: String(msg.message_id ?? ''),
1764
1860
  from: String(msg.from_aid ?? ''),
@@ -1795,6 +1891,23 @@ export class V2E2EECoordinator {
1795
1891
  decrypted.push(v1Msg);
1796
1892
  continue;
1797
1893
  }
1894
+ if (payloadObj && ['e2ee.group_encrypted', 'e2ee.p2p_encrypted', 'e2ee.encrypted'].includes(String(payloadObj.type ?? '').trim())) {
1895
+ const deferStatus = {};
1896
+ const plaintext = await client._decryptV2Message({ ...msg, envelope_json: JSON.stringify(payloadObj) }, true, true, true, true, deferStatus, true, 'pull');
1897
+ if (deferStatus.deferred) {
1898
+ blockedSeq = blockedSeq > 0 ? Math.min(blockedSeq, seq) : seq;
1899
+ if (deferStatus.fromAid) {
1900
+ const key = `${deferStatus.fromAid}\u0000${deferStatus.senderDeviceId ?? ''}\u0000${deferStatus.groupId ?? ''}`;
1901
+ deferredKeyFetches.set(key, { fromAid: deferStatus.fromAid, senderDeviceId: deferStatus.senderDeviceId ?? '', groupId: deferStatus.groupId ?? '' });
1902
+ }
1903
+ }
1904
+ if (plaintext) {
1905
+ plaintext.group_id = gid;
1906
+ plaintext.group_aid = eventGroupAid;
1907
+ await client._publishPulledMessage('group.message_created', ns, seq, plaintext, false);
1908
+ decrypted.push(plaintext);
1909
+ }
1910
+ }
1798
1911
  client._clientLog.debug(`group.v2.pull skipping V1 envelope group=${gid} seq=${seq} payload_type=${payloadObj ? String(payloadObj.type ?? '') : '<none>'} (V1 E2EE removed)`);
1799
1912
  continue;
1800
1913
  }
@@ -1803,7 +1916,7 @@ export class V2E2EECoordinator {
1803
1916
  continue;
1804
1917
  }
1805
1918
  const deferStatus = {};
1806
- let plaintext = await client._decryptV2Message(msg, true, true, true, true, deferStatus, true);
1919
+ let plaintext = await client._decryptV2Message(msg, true, true, true, true, deferStatus, true, 'pull');
1807
1920
  if (deferStatus.deferred) {
1808
1921
  blockedSeq = blockedSeq > 0 ? Math.min(blockedSeq, seq) : seq;
1809
1922
  }
@@ -1867,6 +1980,7 @@ export class V2E2EECoordinator {
1867
1980
  for (const fetch of deferredKeyFetches.values()) {
1868
1981
  this.scheduleSenderIKFetch(fetch.fromAid, fetch.senderDeviceId, fetch.groupId);
1869
1982
  }
1983
+ client._delivery.onPullPage?.(ns, decrypted.length - pageDecryptedBefore, hasMore, pageMaxSeq, typeof result.remaining === 'number' && Number.isSafeInteger(result.remaining) && result.remaining >= 0 ? result.remaining : null, messages.length, client._seqTracker.getContiguousSeq(ns), pageMaxSeq);
1870
1984
  if (ownsCursor && parsedCursorCurrentSeq !== null && cursorCurrentSeq > ackSeq) {
1871
1985
  this.recordForwardCursor(ns, cursorCurrentSeq, ackSeq);
1872
1986
  }
@@ -1884,7 +1998,7 @@ export class V2E2EECoordinator {
1884
1998
  if (ackNeeded) {
1885
1999
  this.recordForwardAck(ns, ackSeq);
1886
2000
  const nextAfter = Math.max(pageMaxSeq, nextAfterSeq);
1887
- const canContinuePage = messages.length > 0 && nextAfter > nextAfterSeq && ownsCursor;
2001
+ const canContinuePage = blockedSeq <= 0 && messages.length > 0 && nextAfter > nextAfterSeq && ownsCursor;
1888
2002
  if (canContinuePage) {
1889
2003
  pendingAckSeq = Math.max(pendingAckSeq, ackSeq);
1890
2004
  lastAutoAckSeq = Math.max(lastAutoAckSeq, ackSeq);
@@ -1932,6 +2046,7 @@ export class V2E2EECoordinator {
1932
2046
  pendingAckSeq = 0;
1933
2047
  }
1934
2048
  }
2049
+ client._delivery.onPullWorkSettled?.();
1935
2050
  return decrypted;
1936
2051
  }
1937
2052
  async confirmForwardGroupAck(groupId, upToSeq, groupAid) {
@@ -2486,7 +2601,7 @@ export class V2E2EECoordinator {
2486
2601
  async decryptV2PushMessage(data) {
2487
2602
  if (!isJsonObject(data))
2488
2603
  return null;
2489
- return await this.client._decryptV2Message(data);
2604
+ return await this.client._decryptV2Message(data, false, false, false, false, undefined, false, 'inline_push');
2490
2605
  }
2491
2606
  async buildV2P2PEnvelope(opts) {
2492
2607
  const client = this.client;