@agentunion/fastaun 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.
@@ -734,7 +734,7 @@ export class V2E2EECoordinator {
734
734
  let plaintext = null;
735
735
  const retryStatus = {};
736
736
  try {
737
- plaintext = await client._decryptV2Message(entry.msg, false, true, true, true, retryStatus);
737
+ plaintext = await client._decryptV2Message(entry.msg, false, true, true, true, retryStatus, false, 'pending_retry');
738
738
  }
739
739
  catch (exc) {
740
740
  client._clientLog.warn(`V2 sender IK pending retry raised: key=${key} err=${formatE2EEError(exc)}`);
@@ -769,10 +769,10 @@ export class V2E2EECoordinator {
769
769
  if (entry.groupId) {
770
770
  plaintext = normalizeGroupMentionMode(plaintext, entry.msg, entry.msg.envelope_json);
771
771
  plaintext.group_id = entry.groupId;
772
- await client._publishPulledMessage('group.message_created', `group:${entry.groupId}`, seq, plaintext);
772
+ await client._publishPulledMessage('group.message_created', `group:${entry.groupId}`, seq, plaintext, false, 'pending_retry');
773
773
  }
774
774
  else {
775
- await client._publishPulledMessage('message.received', `p2p:${client._aid ?? ''}`, seq, plaintext);
775
+ await client._publishPulledMessage('message.received', `p2p:${client._aid ?? ''}`, seq, plaintext, false, 'pending_retry');
776
776
  }
777
777
  client._clientLog.debug(`V2 sender IK pending retry delivered: key=${key}`);
778
778
  }
@@ -1018,10 +1018,10 @@ export class V2E2EECoordinator {
1018
1018
  if (String(msg.version ?? '') === 'v1') {
1019
1019
  const legacy = isJsonObject(msg.legacy_v1)
1020
1020
  ? msg.legacy_v1 : {};
1021
- const payload = legacy.payload;
1021
+ const payload = legacy.payload !== undefined ? legacy.payload : msg.payload;
1022
1022
  const payloadType = isJsonObject(payload)
1023
1023
  ? String(payload.type ?? '').trim() : '';
1024
- if (payload !== undefined && payload !== null && !['e2ee.encrypted', 'e2ee.group_encrypted'].includes(payloadType)) {
1024
+ if (payload !== undefined && payload !== null && !['e2ee.encrypted', 'e2ee.group_encrypted', 'e2ee.p2p_encrypted'].includes(payloadType)) {
1025
1025
  plaintext = {
1026
1026
  message_id: String(msg.message_id ?? ''),
1027
1027
  from: String(msg.from_aid ?? ''),
@@ -1034,9 +1034,14 @@ export class V2E2EECoordinator {
1034
1034
  };
1035
1035
  attachGatewayProximity(plaintext, msg);
1036
1036
  }
1037
+ else if (isJsonObject(payload)
1038
+ && ['e2ee.p2p_encrypted', 'e2ee.encrypted'].includes(payloadType)) {
1039
+ msg = { ...msg, envelope_json: JSON.stringify(payload) };
1040
+ plaintext = await client._decryptV2Message(msg, false, !history, true, true, undefined, false, source);
1041
+ }
1037
1042
  }
1038
1043
  else {
1039
- plaintext = await client._decryptV2Message(msg, false, !history);
1044
+ plaintext = await client._decryptV2Message(msg, false, !history, true, true, undefined, false, source);
1040
1045
  }
1041
1046
  if (!plaintext) {
1042
1047
  if (history)
@@ -1061,35 +1066,57 @@ export class V2E2EECoordinator {
1061
1066
  let plaintext = null;
1062
1067
  if (String(msg.version ?? '') === 'v1') {
1063
1068
  const payload = msg.payload;
1069
+ const recall = typeof client._delivery?.recallEventFromGroupMessage === 'function'
1070
+ ? client._delivery.recallEventFromGroupMessage(msg)
1071
+ : null;
1072
+ if (recall) {
1073
+ const recallPayload = {
1074
+ ...recall,
1075
+ group_id: groupId,
1076
+ group_aid: groupAid,
1077
+ seq,
1078
+ };
1079
+ if (publish) {
1080
+ if (orderedPublish) {
1081
+ await client._delivery.publishOrderedMessage('group.message_recalled', `group:${groupId}`, seq, recallPayload, source);
1082
+ }
1083
+ else {
1084
+ await client._publishAppEvent('group.message_recalled', recallPayload, source);
1085
+ }
1086
+ }
1087
+ return recallPayload;
1088
+ }
1064
1089
  if (payload === undefined || payload === null) {
1065
1090
  if (history)
1066
1091
  return this.historyDecryptFailure(msg, 'legacy payload is missing');
1067
1092
  client._clientLog.warn(`Group Tail 缺少 payload 的 V1 行已跳过,连续性证明仍保留: group=${groupId}, seq=${seq}`);
1068
1093
  return null;
1069
1094
  }
1095
+ const payloadType = isJsonObject(payload)
1096
+ ? String(payload.type ?? '').trim() : '';
1070
1097
  if (isJsonObject(payload)
1071
- && ['e2ee.encrypted', 'e2ee.group_encrypted'].includes(String(payload.type ?? ''))) {
1072
- if (history)
1073
- return this.historyDecryptFailure(msg, 'unsupported legacy encrypted envelope');
1074
- client._clientLog.warn(`Group Tail 不支持的旧加密消息已跳过,连续性证明仍保留: group=${groupId}, seq=${seq}`);
1075
- return null;
1098
+ && ['e2ee.group_encrypted', 'e2ee.p2p_encrypted', 'e2ee.encrypted'].includes(payloadType)) {
1099
+ msg = { ...msg, envelope_json: JSON.stringify(payload) };
1100
+ plaintext = await client._decryptV2Message(msg, false, !history, true, true, undefined, false, source);
1101
+ }
1102
+ else {
1103
+ plaintext = {
1104
+ message_id: String(msg.message_id ?? ''),
1105
+ from: String(msg.from_aid ?? ''),
1106
+ group_id: groupId,
1107
+ group_aid: groupAid,
1108
+ seq,
1109
+ type: String(msg.type ?? ''),
1110
+ timestamp: msg.t_server,
1111
+ payload: payload,
1112
+ encrypted: false,
1113
+ };
1114
+ plaintext = normalizeGroupMentionMode(plaintext, msg, msg.envelope_json);
1115
+ attachGatewayProximity(plaintext, msg);
1076
1116
  }
1077
- plaintext = {
1078
- message_id: String(msg.message_id ?? ''),
1079
- from: String(msg.from_aid ?? ''),
1080
- group_id: groupId,
1081
- group_aid: groupAid,
1082
- seq,
1083
- type: String(msg.type ?? ''),
1084
- timestamp: msg.t_server,
1085
- payload: payload,
1086
- encrypted: false,
1087
- };
1088
- plaintext = normalizeGroupMentionMode(plaintext, msg, msg.envelope_json);
1089
- attachGatewayProximity(plaintext, msg);
1090
1117
  }
1091
1118
  else {
1092
- plaintext = await client._decryptV2Message(msg, false, !history);
1119
+ plaintext = await client._decryptV2Message(msg, false, !history, true, true, undefined, false, source);
1093
1120
  if (plaintext) {
1094
1121
  plaintext.group_id = groupId;
1095
1122
  plaintext.group_aid = groupAid;
@@ -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.syncStarted(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;
@@ -1153,6 +1182,15 @@ export class V2E2EECoordinator {
1153
1182
  const ack = ns ? client._seqTracker.getContiguousSeq(ns) : 0;
1154
1183
  if (previous && ack > previous.ack)
1155
1184
  await this.ackV2(ack);
1185
+ if (ns)
1186
+ client._delivery.syncProgress(ns, {
1187
+ rawCount: page.messages.length,
1188
+ pulledCount: decoded.length,
1189
+ remaining: result.remaining,
1190
+ hasMore: typeof result.has_more === 'boolean' ? result.has_more : undefined,
1191
+ currentSeq: ack,
1192
+ targetSeq: page.head,
1193
+ });
1156
1194
  return { ...result, messages: decoded, raw_count: page.messages.length };
1157
1195
  }
1158
1196
  async historyV2Internal(params) {
@@ -1169,21 +1207,23 @@ export class V2E2EECoordinator {
1169
1207
  }
1170
1208
  return { ...result, messages: decoded };
1171
1209
  }
1172
- async pullV2(afterSeq = 0, limit = 50, opts) {
1210
+ async pullV2Impl(afterSeq = 0, limit = 50, opts) {
1173
1211
  const client = this.client;
1174
1212
  await client._ensureV2SessionReady('message.pull');
1175
1213
  const ns = client._aid ? `p2p:${client._aid}` : '';
1214
+ if (ns)
1215
+ client._delivery.syncStarted(ns);
1176
1216
  if (opts?.windowMode === 'tail') {
1177
1217
  if (!opts.gateLocked) {
1178
1218
  const key = `${ns}|tail|after=${afterSeq}|limit=${limit}`;
1179
- return (await client._runPullSerialized(key, () => this.pullV2(afterSeq, limit, { ...opts, gateLocked: true }), false));
1219
+ return (await client._runPullSerialized(key, () => this.pullV2Impl(afterSeq, limit, { ...opts, gateLocked: true }), false));
1180
1220
  }
1181
1221
  const result = await this.pullV2TailInternal({ window_mode: 'tail', after_seq: afterSeq, limit });
1182
1222
  return (Array.isArray(result.messages) ? result.messages : []);
1183
1223
  }
1184
1224
  if (ns && !opts?.gateLocked) {
1185
1225
  const key = `${ns}|forward|after=${afterSeq}|force=${String(Boolean(opts?.force))}|limit=${limit}`;
1186
- return await client._runPullSerialized(key, async () => this.pullV2(afterSeq, limit, {
1226
+ return await client._runPullSerialized(key, async () => this.pullV2Impl(afterSeq, limit, {
1187
1227
  ...(opts ?? {}),
1188
1228
  gateLocked: true,
1189
1229
  scheduleFollowup: true,
@@ -1253,6 +1293,7 @@ export class V2E2EECoordinator {
1253
1293
  }
1254
1294
  const deferredKeyFetches = new Map();
1255
1295
  let blockedSeq = 0;
1296
+ const pageDecryptedBefore = decrypted.length;
1256
1297
  for (const msg of messages) {
1257
1298
  const seq = Number(msg.seq ?? 0);
1258
1299
  if (!Number.isFinite(seq) || seq <= 0)
@@ -1260,11 +1301,12 @@ export class V2E2EECoordinator {
1260
1301
  const version = String(msg.version ?? 'v2');
1261
1302
  if (version === 'v1') {
1262
1303
  const legacy = isJsonObject(msg.legacy_v1) ? msg.legacy_v1 : {};
1263
- const legacyPayload = legacy.payload;
1304
+ const legacyPayload = legacy.payload !== undefined ? legacy.payload : msg.payload;
1264
1305
  const payloadType = isJsonObject(legacyPayload)
1265
1306
  ? String(legacyPayload.type ?? '').trim()
1266
1307
  : '';
1267
- if (legacyPayload !== undefined && legacyPayload !== null && payloadType !== 'e2ee.encrypted' && payloadType !== 'e2ee.group_encrypted') {
1308
+ if (legacyPayload !== undefined && legacyPayload !== null
1309
+ && !['e2ee.encrypted', 'e2ee.group_encrypted', 'e2ee.p2p_encrypted'].includes(payloadType)) {
1268
1310
  const v1Msg = {
1269
1311
  message_id: String(msg.message_id ?? ''),
1270
1312
  from: String(msg.from_aid ?? ''),
@@ -1286,6 +1328,25 @@ export class V2E2EECoordinator {
1286
1328
  decrypted.push(v1Msg);
1287
1329
  client._clientLog.debug(`message.v2.pull plaintext V1 delivered: seq=${seq}, ns=${ns || '<none>'}`);
1288
1330
  }
1331
+ else if (isJsonObject(legacyPayload)
1332
+ && ['e2ee.p2p_encrypted', 'e2ee.encrypted'].includes(payloadType)) {
1333
+ const deferStatus = {};
1334
+ const plaintext = await client._decryptV2Message({ ...msg, envelope_json: JSON.stringify(legacyPayload) }, true, true, true, true, deferStatus, true, 'pull');
1335
+ if (deferStatus.deferred) {
1336
+ blockedSeq = blockedSeq > 0 ? Math.min(blockedSeq, seq) : seq;
1337
+ if (deferStatus.fromAid) {
1338
+ const key = `${deferStatus.fromAid}\u0000${deferStatus.senderDeviceId ?? ''}\u0000${deferStatus.groupId ?? ''}`;
1339
+ deferredKeyFetches.set(key, { fromAid: deferStatus.fromAid, senderDeviceId: deferStatus.senderDeviceId ?? '', groupId: deferStatus.groupId ?? '' });
1340
+ }
1341
+ }
1342
+ if (plaintext) {
1343
+ if (ns)
1344
+ await client._publishPulledMessage('message.received', ns, seq, plaintext, false);
1345
+ else
1346
+ await client._publishAppEvent('message.received', plaintext, 'pull');
1347
+ decrypted.push(plaintext);
1348
+ }
1349
+ }
1289
1350
  else {
1290
1351
  client._clientLog.debug(`message.v2.pull skipping V1 envelope seq=${seq} payload_type=${payloadType || '<none>'} (V1 E2EE removed)`);
1291
1352
  }
@@ -1300,7 +1361,7 @@ export class V2E2EECoordinator {
1300
1361
  client._v2Session.trackOldSPKMaxSeq(spkId, seq);
1301
1362
  }
1302
1363
  const deferStatus = {};
1303
- const plaintext = await client._decryptV2Message(msg, true, true, true, true, deferStatus, true);
1364
+ const plaintext = await client._decryptV2Message(msg, true, true, true, true, deferStatus, true, 'pull');
1304
1365
  if (deferStatus.deferred) {
1305
1366
  blockedSeq = blockedSeq > 0 ? Math.min(blockedSeq, seq) : seq;
1306
1367
  }
@@ -1399,8 +1460,19 @@ export class V2E2EECoordinator {
1399
1460
  this.scheduleSenderIKFetch(fetch.fromAid, fetch.senderDeviceId, fetch.groupId);
1400
1461
  }
1401
1462
  }
1463
+ if (ns)
1464
+ client._delivery.syncProgress(ns, {
1465
+ rawCount: messages.length,
1466
+ pulledCount: decrypted.length - pageDecryptedBefore,
1467
+ remaining: result.remaining,
1468
+ hasMore: hasMoreField ? hasMore : undefined,
1469
+ currentSeq: client._seqTracker.getContiguousSeq(ns),
1470
+ targetSeq: serverHead,
1471
+ });
1402
1472
  const nextAfter = Math.max(pageMaxSeq, nextAfterSeq);
1403
1473
  const rawCount = messages.length;
1474
+ if (blockedSeq > 0)
1475
+ break;
1404
1476
  const shouldContinue = shouldContinueForwardPage({
1405
1477
  rawCount,
1406
1478
  nextAfterSeq,
@@ -1434,6 +1506,19 @@ export class V2E2EECoordinator {
1434
1506
  client._clientLog.debug(`message.v2.pull done: requested_after_seq=${afterSeq}, pages=${pageCount}, decrypted=${decrypted.length}, ns=${ns || '<none>'}`);
1435
1507
  return decrypted;
1436
1508
  }
1509
+ async pullV2(afterSeq = 0, limit = 50, opts) {
1510
+ const ns = this.client._aid ? `p2p:${this.client._aid}` : '';
1511
+ if (!ns)
1512
+ return await this.pullV2Impl(afterSeq, limit, opts);
1513
+ this.client._delivery.syncStarted(ns);
1514
+ try {
1515
+ return await this.pullV2Impl(afterSeq, limit, opts);
1516
+ }
1517
+ catch (error) {
1518
+ this.client._delivery.onPullAborted?.();
1519
+ throw error;
1520
+ }
1521
+ }
1437
1522
  async confirmForwardP2PAck(upToSeq) {
1438
1523
  const ns = this.client._aid ? `p2p:${this.client._aid}` : '';
1439
1524
  try {
@@ -1739,6 +1824,14 @@ export class V2E2EECoordinator {
1739
1824
  const afterSeq = strictWindowSeq(params.after_seq ?? 0, 'after_seq');
1740
1825
  const limit = strictWindowLimit(params.limit ?? V2_PULL_DEFAULT_PAGE_LIMIT);
1741
1826
  const ns = `group:${groupId}`;
1827
+ const cursorParams = isJsonObject(params._group_cursor_params)
1828
+ ? params._group_cursor_params : params;
1829
+ const requestDeviceId = String(cursorParams.device_id ?? '').trim();
1830
+ const requestSlotId = String(cursorParams.slot_id ?? '').trim();
1831
+ const ownsCursor = (!requestDeviceId || requestDeviceId === String(client._deviceId ?? ''))
1832
+ && (!requestSlotId || requestSlotId === String(client._slotId ?? ''));
1833
+ if (ownsCursor)
1834
+ client._delivery.syncStarted(ns);
1742
1835
  const result = await client._callRawV2Rpc('group.v2.pull', withExplicitGroupAid({
1743
1836
  group_id: groupId,
1744
1837
  window_mode: 'tail',
@@ -1771,6 +1864,15 @@ export class V2E2EECoordinator {
1771
1864
  const ack = client._seqTracker.getContiguousSeq(ns);
1772
1865
  if (ack > previous.ack)
1773
1866
  await this.ackGroupV2(groupId, ack, groupAid);
1867
+ if (ownsCursor)
1868
+ client._delivery.syncProgress(ns, {
1869
+ rawCount: page.messages.length,
1870
+ pulledCount: decoded.length,
1871
+ remaining: result.remaining,
1872
+ hasMore: typeof result.has_more === 'boolean' ? result.has_more : undefined,
1873
+ currentSeq: ack,
1874
+ targetSeq: page.head,
1875
+ });
1774
1876
  return { ...result, messages: decoded, raw_count: page.messages.length };
1775
1877
  }
1776
1878
  async historyGroupV2Internal(params) {
@@ -1806,7 +1908,7 @@ export class V2E2EECoordinator {
1806
1908
  force: params.force === true,
1807
1909
  });
1808
1910
  }
1809
- async pullGroupV2(groupId, afterSeq = 0, limit = 50, opts) {
1911
+ async pullGroupV2Impl(groupId, afterSeq = 0, limit = 50, opts) {
1810
1912
  const client = this.client;
1811
1913
  await client._ensureV2SessionReady('group.pull');
1812
1914
  const gid = String(groupId ?? '').trim();
@@ -1814,15 +1916,18 @@ export class V2E2EECoordinator {
1814
1916
  throw new ValidationError('group.pull requires group_id');
1815
1917
  let groupAid = String(opts?.groupAid ?? '').trim();
1816
1918
  const ns = `group:${gid}`;
1919
+ if (opts?.ownsCursor !== false)
1920
+ client._delivery.syncStarted(ns);
1817
1921
  if (opts?.windowMode === 'tail') {
1818
1922
  if (!opts.gateLocked) {
1819
1923
  const key = `${ns}|tail|after=${afterSeq}|limit=${limit}`;
1820
- return (await client._runPullSerialized(key, () => this.pullGroupV2(gid, afterSeq, limit, {
1924
+ return (await client._runPullSerialized(key, () => this.pullGroupV2Impl(gid, afterSeq, limit, {
1821
1925
  ...opts,
1822
1926
  gateLocked: true,
1823
1927
  }), false));
1824
1928
  }
1825
1929
  const result = await this.pullGroupV2TailInternal({
1930
+ ...(opts?.cursorParams ?? {}),
1826
1931
  group_id: String(opts.wireGroupId ?? gid),
1827
1932
  group_aid: groupAid || undefined,
1828
1933
  window_mode: 'tail',
@@ -1833,7 +1938,7 @@ export class V2E2EECoordinator {
1833
1938
  }
1834
1939
  if (!opts?.gateLocked) {
1835
1940
  const key = `${ns}|forward|after=${afterSeq}|force=${opts?.force === true}|limit=${limit}`;
1836
- return await client._runPullSerialized(key, async () => this.pullGroupV2(gid, afterSeq, limit, {
1941
+ return await client._runPullSerialized(key, async () => this.pullGroupV2Impl(gid, afterSeq, limit, {
1837
1942
  ...(opts ?? {}),
1838
1943
  gateLocked: true,
1839
1944
  scheduleFollowup: true,
@@ -1916,28 +2021,31 @@ export class V2E2EECoordinator {
1916
2021
  }
1917
2022
  const deferredKeyFetches = new Map();
1918
2023
  let blockedSeq = 0;
2024
+ const pageDecryptedBefore = decrypted.length;
1919
2025
  for (const msg of messages) {
1920
2026
  const seq = Number(msg.seq ?? 0);
1921
2027
  if (!Number.isFinite(seq) || seq <= 0)
1922
2028
  continue;
1923
2029
  const version = String(msg.version ?? 'v2');
1924
2030
  if (version === 'v1') {
1925
- const payload = msg.payload;
2031
+ const legacy = isJsonObject(msg.legacy_v1) ? msg.legacy_v1 : {};
2032
+ const payload = msg.payload !== undefined ? msg.payload : legacy.payload;
1926
2033
  const payloadObj = isJsonObject(payload) ? payload : null;
2034
+ const payloadType = payloadObj ? String(payloadObj.type ?? '').trim() : '';
1927
2035
  // 群撤回 tombstone(占位 / 通知):归一化为 group.message_recalled 事件,仍占 seq。
1928
- if (client._delivery.recallEventFromGroupMessage(msg)) {
2036
+ if (typeof client._delivery?.recallEventFromGroupMessage === 'function'
2037
+ && client._delivery.recallEventFromGroupMessage(msg)) {
1929
2038
  await client._delivery.publishGroupRecallTombstone(gid, seq, {
1930
2039
  ...msg,
1931
2040
  group_id: gid,
1932
2041
  group_aid: eventGroupAid,
1933
- });
2042
+ }, 'pull');
1934
2043
  client._markPublishedSeq(ns, seq);
1935
2044
  client._clientLog.debug(`group.v2.pull recall tombstone delivered: group=${gid}, seq=${seq}`);
1936
2045
  continue;
1937
2046
  }
1938
2047
  if (payloadObj) {
1939
- const payloadType = String(payloadObj.type ?? '').trim();
1940
- if (payloadType !== 'e2ee.encrypted' && payloadType !== 'e2ee.group_encrypted') {
2048
+ if (!['e2ee.encrypted', 'e2ee.group_encrypted', 'e2ee.p2p_encrypted'].includes(payloadType)) {
1941
2049
  let v1Msg = {
1942
2050
  message_id: String(msg.message_id ?? ''),
1943
2051
  from: String(msg.from_aid ?? ''),
@@ -1976,7 +2084,26 @@ export class V2E2EECoordinator {
1976
2084
  client._clientLog.debug(`group.v2.pull plaintext V1 delivered: group=${gid}, seq=${seq}`);
1977
2085
  continue;
1978
2086
  }
1979
- client._clientLog.debug(`group.v2.pull skipping V1 envelope group=${gid} seq=${seq} payload_type=${payloadObj ? String(payloadObj.type ?? '') : '<none>'} (V1 E2EE removed)`);
2087
+ if (payloadObj && ['e2ee.group_encrypted', 'e2ee.p2p_encrypted', 'e2ee.encrypted'].includes(payloadType)) {
2088
+ const deferStatus = {};
2089
+ const plaintext = await client._decryptV2Message({ ...msg, envelope_json: JSON.stringify(payloadObj) }, true, true, true, true, deferStatus, true, 'pull');
2090
+ if (deferStatus.deferred) {
2091
+ blockedSeq = blockedSeq > 0 ? Math.min(blockedSeq, seq) : seq;
2092
+ if (deferStatus.fromAid) {
2093
+ const key = `${deferStatus.fromAid}\u0000${deferStatus.senderDeviceId ?? ''}\u0000${deferStatus.groupId ?? ''}`;
2094
+ deferredKeyFetches.set(key, { fromAid: deferStatus.fromAid, senderDeviceId: deferStatus.senderDeviceId ?? '', groupId: deferStatus.groupId ?? '' });
2095
+ }
2096
+ }
2097
+ if (plaintext) {
2098
+ plaintext.group_id = gid;
2099
+ plaintext.group_aid = eventGroupAid;
2100
+ await client._publishPulledMessage('group.message_created', ns, seq, plaintext, false);
2101
+ decrypted.push(plaintext);
2102
+ }
2103
+ }
2104
+ else {
2105
+ client._clientLog.debug(`group.v2.pull skipping V1 envelope group=${gid} seq=${seq} payload_type=${payloadObj ? String(payloadObj.type ?? '') : '<none>'} (V1 E2EE removed)`);
2106
+ }
1980
2107
  continue;
1981
2108
  }
1982
2109
  if (version !== 'v2') {
@@ -1984,7 +2111,7 @@ export class V2E2EECoordinator {
1984
2111
  continue;
1985
2112
  }
1986
2113
  const deferStatus = {};
1987
- let plaintext = await client._decryptV2Message(msg, true, true, true, true, deferStatus, true);
2114
+ let plaintext = await client._decryptV2Message(msg, true, true, true, true, deferStatus, true, 'pull');
1988
2115
  if (deferStatus.deferred) {
1989
2116
  blockedSeq = blockedSeq > 0 ? Math.min(blockedSeq, seq) : seq;
1990
2117
  }
@@ -2079,10 +2206,20 @@ export class V2E2EECoordinator {
2079
2206
  lastAutoAckSeq = Math.max(lastAutoAckSeq, ackSeq);
2080
2207
  }
2081
2208
  }
2209
+ client._delivery.syncProgress(ns, {
2210
+ rawCount: messages.length,
2211
+ pulledCount: decrypted.length - pageDecryptedBefore,
2212
+ remaining: result.remaining,
2213
+ hasMore: hasMoreField ? hasMore : undefined,
2214
+ currentSeq: client._seqTracker.getContiguousSeq(ns),
2215
+ targetSeq: serverHead,
2216
+ });
2082
2217
  const nextAfter = Math.max(pageMaxSeq, nextAfterSeq);
2083
2218
  if (!ownsCursor)
2084
2219
  break;
2085
2220
  const rawCount = messages.length;
2221
+ if (blockedSeq > 0)
2222
+ break;
2086
2223
  const shouldContinue = shouldContinueForwardPage({
2087
2224
  rawCount,
2088
2225
  nextAfterSeq,
@@ -2116,6 +2253,20 @@ export class V2E2EECoordinator {
2116
2253
  client._clientLog.debug(`group.v2.pull done: group=${gid}, requested_after_seq=${afterSeq}, pages=${pageCount}, decrypted=${decrypted.length}, ns=${ns}`);
2117
2254
  return decrypted;
2118
2255
  }
2256
+ async pullGroupV2(groupId, afterSeq = 0, limit = 50, opts) {
2257
+ if (opts?.ownsCursor === false) {
2258
+ return await this.pullGroupV2Impl(groupId, afterSeq, limit, opts);
2259
+ }
2260
+ const ns = `group:${String(groupId ?? '').trim()}`;
2261
+ this.client._delivery.syncStarted(ns);
2262
+ try {
2263
+ return await this.pullGroupV2Impl(groupId, afterSeq, limit, opts);
2264
+ }
2265
+ catch (error) {
2266
+ this.client._delivery.onPullAborted?.();
2267
+ throw error;
2268
+ }
2269
+ }
2119
2270
  async confirmForwardGroupAck(groupId, upToSeq, groupAid) {
2120
2271
  const ns = `group:${String(groupId ?? '').trim()}`;
2121
2272
  try {
@@ -2675,7 +2826,7 @@ export class V2E2EECoordinator {
2675
2826
  async decryptV2PushMessage(data) {
2676
2827
  if (!isJsonObject(data))
2677
2828
  return null;
2678
- return await this.client._decryptV2Message(data, false, false, false, false);
2829
+ return await this.client._decryptV2Message(data, false, false, false, false, undefined, false, 'inline_push');
2679
2830
  }
2680
2831
  handleGroupChangedSpk(data, groupId, action) {
2681
2832
  if (!this.client._v2Session || !groupId || !MEMBERSHIP_ACTIONS.has(action))