@agentunion/fastaun 0.5.18 → 0.5.20

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 (122) hide show
  1. package/CHANGELOG.md +172 -79
  2. package/_packed_docs/CHANGELOG.md +172 -79
  3. package/_packed_docs/INDEX.md +244 -116
  4. package/_packed_docs/KITE_DOCS_GUIDE.md +98 -69
  5. package/_packed_docs/agent.md/SCHEMA.md +245 -205
  6. package/_packed_docs/agent.md//350/277/234/347/250/213agent.md/347/274/223/345/255/230/344/270/216etag/351/200/217/344/274/240/346/226/271/346/241/210.md +11 -11
  7. package/_packed_docs/aun/345/210/206/345/270/203/345/274/217/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +42 -15
  8. package/_packed_docs/aun/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +4 -4
  9. package/_packed_docs/protocol/01-/350/272/253/344/273/275/344/270/216/345/207/255/350/257/201/345/215/217/350/256/256-auth.md +45 -134
  10. package/_packed_docs/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +1072 -1072
  11. package/_packed_docs/protocol/07-/351/224/231/350/257/257/347/240/201/344/270/216/347/212/266/346/200/201/346/234/272.md +5 -20
  12. package/_packed_docs/protocol/10-Group-/345/255/220/345/215/217/350/256/256.md +687 -668
  13. package/_packed_docs/protocol/11-Storage-/345/255/220/345/215/217/350/256/256.md +19 -28
  14. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/211.md +443 -0
  15. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2112.0.md +161 -0
  16. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2112.1.md +658 -0
  17. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2113.0.md +233 -0
  18. package/_packed_docs/protocol/agent.md/SCHEMA.md +4 -0
  19. package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +1 -1
  20. package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +68 -61
  21. package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +1 -1
  22. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +279 -285
  23. package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +3 -3
  24. package/_packed_docs/sdk/09-group-rpc-manual.md +271 -235
  25. package/_packed_docs/sdk/09-message-rpc-manual.md +47 -6
  26. package/_packed_docs/sdk/09-storage-rpc-manual.md +70 -68
  27. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +63 -34
  28. package/_packed_docs/sdk/INDEX.md +105 -34
  29. package/_packed_docs/sdk/Ownership-SDK/345/256/236/347/216/260/346/214/207/345/215/227.md +169 -0
  30. package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/227.md +349 -0
  31. package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/2272.1.md +922 -0
  32. package/_packed_docs/sdk/Ownership-SDK/351/233/206/346/210/220/346/214/207/345/215/227.md +270 -0
  33. package/_packed_docs/sdk/Ownership2.1-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +300 -0
  34. package/_packed_docs/sdk/Ownership2.1-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +382 -0
  35. package/_packed_docs/sdk/Ownership2.1-TDD/345/210/206/351/230/266/346/256/265/345/256/236/346/226/275/350/256/241/345/210/222.md +708 -0
  36. package/_packed_docs/sdk/Ownership2.1/350/257/246/347/273/206/350/256/276/350/256/241.md +476 -0
  37. package/_packed_docs/sdk/Ownership/347/256/241/347/220/206/344/270/223/351/242/230.md +559 -0
  38. package/_packed_docs/sdk/ProtectedHeaders/347/224/250/346/263/225/344/270/223/351/242/230.md +299 -0
  39. package/_packed_docs/sdk//345/207/255/350/257/201/344/270/216/347/273/204/347/273/207/346/234/215/345/212/241SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +173 -0
  40. package/_packed_docs/sdk//345/207/255/350/257/201/344/270/216/347/273/204/347/273/207/346/234/215/345/212/241SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +211 -0
  41. package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273SDK/345/256/236/347/216/260/347/247/273/346/244/215/346/214/207/345/215/227.md +331 -0
  42. package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273SDK/351/233/206/346/210/220/346/214/207/345/215/227.md +207 -0
  43. package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273/344/273/243/347/240/201/345/256/241/346/237/245/346/212/245/345/221/212.md +252 -0
  44. package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273/345/256/236/346/226/275/350/256/241/345/210/222.md +156 -0
  45. package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273/346/226/271/346/241/210.md +273 -0
  46. package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273/350/257/246/347/273/206/350/256/276/350/256/241.md +211 -0
  47. package/dist/agent-md-schema.d.ts +15 -1
  48. package/dist/agent-md-schema.js +87 -24
  49. package/dist/agent-md-schema.js.map +1 -1
  50. package/dist/agent-md.d.ts +54 -2
  51. package/dist/agent-md.js +637 -53
  52. package/dist/agent-md.js.map +1 -1
  53. package/dist/aid-store.d.ts +38 -59
  54. package/dist/aid-store.js +300 -265
  55. package/dist/aid-store.js.map +1 -1
  56. package/dist/aid.js +30 -11
  57. package/dist/aid.js.map +1 -1
  58. package/dist/auth.d.ts +6 -1
  59. package/dist/auth.js +500 -15
  60. package/dist/auth.js.map +1 -1
  61. package/dist/cert-utils.d.ts +44 -2
  62. package/dist/cert-utils.js +149 -49
  63. package/dist/cert-utils.js.map +1 -1
  64. package/dist/client/delivery.d.ts +11 -1
  65. package/dist/client/delivery.js +321 -62
  66. package/dist/client/delivery.js.map +1 -1
  67. package/dist/client/rpc-pipeline.d.ts +1 -0
  68. package/dist/client/rpc-pipeline.js +142 -82
  69. package/dist/client/rpc-pipeline.js.map +1 -1
  70. package/dist/client/v2-e2ee.d.ts +1 -0
  71. package/dist/client/v2-e2ee.js +166 -96
  72. package/dist/client/v2-e2ee.js.map +1 -1
  73. package/dist/client.d.ts +84 -1
  74. package/dist/client.js +780 -30
  75. package/dist/client.js.map +1 -1
  76. package/dist/errors.js +4 -3
  77. package/dist/errors.js.map +1 -1
  78. package/dist/events.d.ts +9 -3
  79. package/dist/events.js +69 -11
  80. package/dist/events.js.map +1 -1
  81. package/dist/facades.d.ts +83 -3
  82. package/dist/facades.js +100 -6
  83. package/dist/facades.js.map +1 -1
  84. package/dist/index.d.ts +9 -4
  85. package/dist/index.js +3 -2
  86. package/dist/index.js.map +1 -1
  87. package/dist/keystore/aid-db.d.ts +5 -0
  88. package/dist/keystore/aid-db.js +57 -0
  89. package/dist/keystore/aid-db.js.map +1 -1
  90. package/dist/keystore/index.d.ts +8 -0
  91. package/dist/keystore/local-identity-store.js +10 -3
  92. package/dist/keystore/local-identity-store.js.map +1 -1
  93. package/dist/keystore/local-token-store.d.ts +6 -1
  94. package/dist/keystore/local-token-store.js +24 -1
  95. package/dist/keystore/local-token-store.js.map +1 -1
  96. package/dist/org-protocol.d.ts +98 -0
  97. package/dist/org-protocol.js +1753 -0
  98. package/dist/org-protocol.js.map +1 -0
  99. package/dist/ownership-proof.d.ts +82 -5
  100. package/dist/ownership-proof.js +327 -31
  101. package/dist/ownership-proof.js.map +1 -1
  102. package/dist/ownership-v21.d.ts +196 -0
  103. package/dist/ownership-v21.js +712 -0
  104. package/dist/ownership-v21.js.map +1 -0
  105. package/dist/ownership-workflow.d.ts +87 -0
  106. package/dist/ownership-workflow.js +1182 -0
  107. package/dist/ownership-workflow.js.map +1 -0
  108. package/dist/seq-tracker.d.ts +1 -0
  109. package/dist/seq-tracker.js +10 -1
  110. package/dist/seq-tracker.js.map +1 -1
  111. package/dist/tools/cross-sdk-agent.d.ts +90 -1
  112. package/dist/tools/cross-sdk-agent.js +441 -7
  113. package/dist/tools/cross-sdk-agent.js.map +1 -1
  114. package/dist/transport.d.ts +8 -0
  115. package/dist/transport.js +118 -4
  116. package/dist/transport.js.map +1 -1
  117. package/dist/v2/e2ee/decrypt.d.ts +1 -0
  118. package/dist/v2/e2ee/decrypt.js +9 -1
  119. package/dist/v2/e2ee/decrypt.js.map +1 -1
  120. package/dist/version.d.ts +1 -1
  121. package/dist/version.js +1 -1
  122. package/package.json +1 -1
@@ -113,13 +113,13 @@ function deduplicateForwardPageMessages(value, afterSeq) {
113
113
  function contiguousPullSeq(afterSeq, seqs, proof) {
114
114
  const pulledMax = seqs.length > 0 ? Math.max(...seqs) : 0;
115
115
  const serverProof = safeNonNegativeSeq(proof);
116
- return Math.max(afterSeq, pulledMax, serverProof ?? 0);
116
+ return Math.max(afterSeq, pulledMax, pulledMax > 0 ? 0 : serverProof ?? 0);
117
117
  }
118
118
  function shouldContinueForwardPage({ rawCount, nextAfterSeq, nextAfter, limit, hasMoreField, hasMore, serverHead, observedUpper, pendingAckSeq, }) {
119
119
  // 无新 seq 或游标未前进时必须停止,避免错误元数据造成循环。
120
120
  if (rawCount <= 0 || nextAfter <= nextAfterSeq)
121
121
  return false;
122
- if (hasMore || serverHead > nextAfter || observedUpper > nextAfter || pendingAckSeq > 0)
122
+ if (hasMore || serverHead > nextAfter || observedUpper > nextAfter)
123
123
  return true;
124
124
  // 仅旧服务缺失 has_more 时,以满页保留兼容续拉。
125
125
  return !hasMoreField && limit > 0 && rawCount >= limit;
@@ -825,11 +825,12 @@ export class V2E2EECoordinator {
825
825
  client._clientLog.debug(`V2 sender IK pending retry delivered: key=${key}`);
826
826
  continue;
827
827
  }
828
- pending.delete(key);
829
828
  const seq = Number(entry.msg.seq ?? 0);
830
829
  if (entry.groupId) {
831
- if (client._delivery.isSelfSentGroupMessage?.(entry.msg))
830
+ if (client._delivery.isSelfSentGroupMessage?.(entry.msg)) {
831
+ pending.delete(key);
832
832
  continue;
833
+ }
833
834
  plaintext = normalizeGroupMentionMode(plaintext, entry.msg, entry.msg.envelope_json);
834
835
  plaintext.group_id = entry.groupId;
835
836
  await client._publishPulledMessage('group.message_created', `group:${entry.groupId}`, seq, plaintext, false, 'pending_retry');
@@ -837,6 +838,8 @@ export class V2E2EECoordinator {
837
838
  else {
838
839
  await client._publishPulledMessage('message.received', `p2p:${client._aid ?? ''}`, seq, plaintext, false, 'pending_retry');
839
840
  }
841
+ if (generationIsCurrent())
842
+ pending.delete(key);
840
843
  client._clientLog.debug(`V2 sender IK pending retry delivered: key=${key}`);
841
844
  }
842
845
  }
@@ -914,10 +917,14 @@ export class V2E2EECoordinator {
914
917
  });
915
918
  }
916
919
  }
920
+ const targetDeviceId = String(opts.deviceId ?? '').trim();
921
+ if (useCache && targetDeviceId && !peerDevices.some((dev) => String(dev.device_id ?? '').trim() === targetDeviceId)) {
922
+ this.deleteBootstrapCacheEntry(to);
923
+ return this.buildV2P2PEnvelope({ ...opts, useCache: false });
924
+ }
917
925
  if (peerDevices.length === 0) {
918
926
  throw new E2EEError(`V2 bootstrap: no devices found for ${to}`);
919
927
  }
920
- const targetDeviceId = String(opts.deviceId ?? '').trim();
921
928
  if (targetDeviceId && !peerDevices.some((dev) => String(dev.device_id ?? '').trim() === targetDeviceId)) {
922
929
  throw new E2EEError(`V2 bootstrap: target device ${targetDeviceId} not found for ${to}`);
923
930
  }
@@ -1004,6 +1011,10 @@ export class V2E2EECoordinator {
1004
1011
  targetSet = this.cloneP2PTargetSet(targetSet);
1005
1012
  }
1006
1013
  if (targetSet.targets.length === 0) {
1014
+ if (useCache && targetDeviceId) {
1015
+ this.deleteBootstrapCacheEntry(to);
1016
+ return this.buildV2P2PEnvelope({ ...opts, useCache: false });
1017
+ }
1007
1018
  throw new E2EEError(`V2 bootstrap: no usable devices found for ${to}`);
1008
1019
  }
1009
1020
  if (targetDeviceId) {
@@ -1017,6 +1028,10 @@ export class V2E2EECoordinator {
1017
1028
  return isPeerTarget || target.role === 'self_sync';
1018
1029
  });
1019
1030
  if (!peerTargetFound) {
1031
+ if (useCache) {
1032
+ this.deleteBootstrapCacheEntry(to);
1033
+ return this.buildV2P2PEnvelope({ ...opts, useCache: false });
1034
+ }
1020
1035
  throw new E2EEError(`V2 bootstrap: target device ${targetDeviceId} not found for ${to}`);
1021
1036
  }
1022
1037
  }
@@ -1122,6 +1137,11 @@ export class V2E2EECoordinator {
1122
1137
  history_error: { code: 'decrypt_failed', message: reason },
1123
1138
  };
1124
1139
  }
1140
+ ensureDeliveryCurrent(ns, generation) {
1141
+ if (Number(this.client._v2RuntimeGeneration ?? 0) !== generation
1142
+ || this.client._rpcPipeline?.isPullNamespaceCurrent?.(ns) === false)
1143
+ throw new StateError('pull invalidated');
1144
+ }
1125
1145
  async decodeP2PWindowMessage(msg, publish, history, source, orderedPublish = false) {
1126
1146
  const client = this.client;
1127
1147
  const seq = strictWindowSeq(msg.seq, 'messages[].seq', true);
@@ -1250,6 +1270,7 @@ export class V2E2EECoordinator {
1250
1270
  }
1251
1271
  async pullV2TailInternal(params, deferPublish = false) {
1252
1272
  const client = this.client;
1273
+ const generation = Number(client._v2RuntimeGeneration ?? 0);
1253
1274
  if (params.window_mode !== 'tail')
1254
1275
  throw new ValidationError('window_mode must equal tail');
1255
1276
  if ('ack_up_to_seq' in params || params.force === true)
@@ -1264,6 +1285,14 @@ export class V2E2EECoordinator {
1264
1285
  }, `${ns}|tail|after=${afterSeq}|limit=${limit}`);
1265
1286
  const result = await client._callRawV2Rpc('message.v2.pull', { window_mode: 'tail', after_seq: afterSeq, limit, _rpc_foreground: true }, pullGateKey);
1266
1287
  const page = validateTailPage(result, afterSeq, limit);
1288
+ const decoded = [];
1289
+ for (const msg of page.messages) {
1290
+ this.ensureDeliveryCurrent(ns, generation);
1291
+ const plaintext = await this.decodeP2PWindowMessage(msg, true, false, 'tail', deferPublish);
1292
+ if (plaintext)
1293
+ decoded.push(plaintext);
1294
+ }
1295
+ this.ensureDeliveryCurrent(ns, generation);
1267
1296
  if (ns) {
1268
1297
  client._seqTracker.commitTailWindow(ns, {
1269
1298
  windowStartSeq: page.windowStart,
@@ -1273,12 +1302,7 @@ export class V2E2EECoordinator {
1273
1302
  });
1274
1303
  client._persistSeq(ns);
1275
1304
  await client._saveSeqTrackerState?.();
1276
- }
1277
- const decoded = [];
1278
- for (const msg of page.messages) {
1279
- const plaintext = await this.decodeP2PWindowMessage(msg, true, false, 'tail', deferPublish);
1280
- if (plaintext)
1281
- decoded.push(plaintext);
1305
+ this.ensureDeliveryCurrent(ns, generation);
1282
1306
  }
1283
1307
  const ack = ns ? client._seqTracker.getContiguousSeq(ns) : 0;
1284
1308
  if (ns)
@@ -1308,6 +1332,7 @@ export class V2E2EECoordinator {
1308
1332
  }
1309
1333
  async pullV2Impl(afterSeq = 0, limit = 50, opts) {
1310
1334
  const client = this.client;
1335
+ const generation = Number(client._v2RuntimeGeneration ?? 0);
1311
1336
  await client._ensureV2SessionReady('message.pull');
1312
1337
  const ns = client._aid ? `p2p:${client._aid}` : '';
1313
1338
  if (ns)
@@ -1348,7 +1373,7 @@ export class V2E2EECoordinator {
1348
1373
  const maxPages = Math.max(1, Math.min(100, Math.trunc(Number(opts?.maxPages ?? 100) || 100)));
1349
1374
  let pipelineRunning = false;
1350
1375
  let pipelineError = null;
1351
- const pipelineQueue = new V2PullPageQueue(V2_PULL_PIPELINE_QUEUE_CAPACITY);
1376
+ let pipelineQueue = new V2PullPageQueue(V2_PULL_PIPELINE_QUEUE_CAPACITY);
1352
1377
  let pipelineProducer = null;
1353
1378
  let pipelineStopped = false;
1354
1379
  const startPipeline = (startAfterSeq, startVolatileCursor) => {
@@ -1361,21 +1386,26 @@ export class V2E2EECoordinator {
1361
1386
  for (let produced = pageCount; produced < maxPages; produced += 1) {
1362
1387
  if (pipelineStopped)
1363
1388
  break;
1389
+ this.ensureDeliveryCurrent(ns, generation);
1364
1390
  const requestedVolatileCursor = producerVolatileCursor;
1391
+ const ackUpToSeq = pendingAckSeq;
1392
+ const inlineAckGeneration = ackUpToSeq > 0 ? client._delivery.captureP2PInlineAckGeneration() : null;
1365
1393
  const result = await client._callRawV2Rpc('message.v2.pull', {
1366
1394
  after_seq: producerAfterSeq,
1367
1395
  limit,
1396
+ ...(ackUpToSeq > 0 ? { ack_up_to_seq: ackUpToSeq } : {}),
1368
1397
  ...(producerVolatileCursor ? { after_volatile_cursor: producerVolatileCursor } : {}),
1369
1398
  ...(opts?.force ? { force: true } : {}),
1370
1399
  }, pullGateKey);
1371
1400
  if (pipelineStopped)
1372
1401
  break;
1402
+ this.ensureDeliveryCurrent(ns, generation);
1373
1403
  const rawPageMessages = (Array.isArray(result?.messages) ? result.messages : []);
1374
1404
  const enqueued = await pipelineQueue.put({
1375
1405
  result,
1376
1406
  afterSeq: producerAfterSeq,
1377
- ackUpToSeq: 0,
1378
- inlineAckGeneration: null,
1407
+ ackUpToSeq,
1408
+ inlineAckGeneration,
1379
1409
  requestedVolatileCursor,
1380
1410
  weight: rawPageMessages.length,
1381
1411
  }, rawPageMessages.length);
@@ -1388,7 +1418,7 @@ export class V2E2EECoordinator {
1388
1418
  const seqs = messages
1389
1419
  .map((msg) => Number(msg.seq ?? 0))
1390
1420
  .filter((seq) => Number.isFinite(seq) && seq > 0);
1391
- const nextAfter = Math.max(contiguousPullSeq(producerAfterSeq, seqs, result.contiguous_seq), producerAfterSeq);
1421
+ const nextAfter = Math.max(producerAfterSeq, ...seqs);
1392
1422
  const cursor = isJsonObject(result.cursor)
1393
1423
  ? result.cursor : null;
1394
1424
  const shouldContinue = shouldContinueForwardPage({
@@ -1438,7 +1468,7 @@ export class V2E2EECoordinator {
1438
1468
  pageCount += 1;
1439
1469
  const requestedVolatileCursor = pipelinePage?.requestedVolatileCursor ?? volatileCursor;
1440
1470
  const ackUpToSeq = pipelinePage?.ackUpToSeq ?? pendingAckSeq;
1441
- const inlineAckGeneration = pipelinePage?.inlineAckGeneration ?? (ackUpToSeq > 0
1471
+ const inlineAckGeneration = pipelinePage ? pipelinePage.inlineAckGeneration : (ackUpToSeq > 0
1442
1472
  ? client._delivery.captureP2PInlineAckGeneration()
1443
1473
  : null);
1444
1474
  client._clientLog.debug(`message.v2.pull page request: page=${pageCount}, after_seq=${nextAfterSeq}, limit=${limit}, ack_up_to_seq=${ackUpToSeq}, ns=${ns || '<none>'}`);
@@ -1467,25 +1497,31 @@ export class V2E2EECoordinator {
1467
1497
  continue;
1468
1498
  }
1469
1499
  }
1500
+ this.ensureDeliveryCurrent(ns, generation);
1470
1501
  if (ackUpToSeq > 0) {
1471
1502
  const actualAckSeq = client._delivery.resolveP2PPullAckSeq(result, ackUpToSeq);
1472
1503
  if (actualAckSeq >= ackUpToSeq) {
1473
1504
  if (inlineAckGeneration !== null) {
1474
1505
  client._delivery.onP2PV2PullAckResult(result, ackUpToSeq, inlineAckGeneration);
1475
1506
  }
1476
- pendingAckSeq = 0;
1507
+ if (pendingAckSeq <= ackUpToSeq)
1508
+ pendingAckSeq = 0;
1477
1509
  lastAutoAckSeq = Math.max(lastAutoAckSeq, ackUpToSeq);
1478
1510
  this.clearForwardAck(ns, ackUpToSeq);
1479
1511
  }
1480
1512
  else {
1481
1513
  client._clientLog.warn(`message.v2.pull piggyback ACK below target: requested=${ackUpToSeq}, actual=${actualAckSeq}; fallback terminal ACK`);
1482
- await this.confirmForwardP2PAck(ackUpToSeq);
1483
- pendingAckSeq = 0;
1484
- lastAutoAckSeq = Math.max(lastAutoAckSeq, ackUpToSeq);
1485
1514
  }
1486
1515
  }
1487
1516
  const pageMessages = (Array.isArray(result?.messages) ? result.messages : []);
1488
1517
  const volatileMessages = client._delivery.volatileMessages?.(pageMessages) ?? [];
1518
+ if (pipelinePage && volatileMessages.length > 0) {
1519
+ if (pipelineProducer)
1520
+ await pipelineProducer;
1521
+ pipelineProducer = null;
1522
+ pipelineRunning = false;
1523
+ pipelineQueue = new V2PullPageQueue(V2_PULL_PIPELINE_QUEUE_CAPACITY);
1524
+ }
1489
1525
  if (volatileMessages.length > 0) {
1490
1526
  decrypted.push(...await this.publishV2VolatileMessages(volatileMessages, 'pull', true));
1491
1527
  volatileCursor = String(client._delivery.volatilePullCursor?.() ?? '').trim();
@@ -1504,14 +1540,20 @@ export class V2E2EECoordinator {
1504
1540
  .map((msg) => Number(msg.seq ?? 0))
1505
1541
  .filter((seq) => Number.isFinite(seq) && seq > 0);
1506
1542
  const pageContigBefore = ns ? client._seqTracker.getContiguousSeq(ns) : 0;
1507
- const pageTrackerSnapshot = ns && typeof client._seqTracker.snapshotNamespace === 'function'
1508
- ? client._seqTracker.snapshotNamespace(ns)
1509
- : null;
1543
+ let pageTrackerSnapshot = null;
1544
+ let trackerMutated = false;
1510
1545
  let pageMaxSeq = nextAfterSeq;
1511
1546
  if (seqs.length > 0) {
1512
1547
  pageMaxSeq = Math.max(...seqs);
1513
1548
  }
1514
1549
  const pageContiguousSeq = contiguousPullSeq(pageContigBefore, seqs, result.contiguous_seq);
1550
+ if (!pipelineRunning && volatileMessages.length === 0 && shouldContinueForwardPage({
1551
+ rawCount: messages.length, nextAfterSeq, nextAfter: pageMaxSeq, limit,
1552
+ hasMoreField, hasMore, serverHead,
1553
+ observedUpper: Math.max(client._seqTracker.getMaxSeenSeq(ns), Number(client._pendingP2pPullUpper?.get?.(ns) ?? 0)),
1554
+ pendingAckSeq: 0,
1555
+ }))
1556
+ startPipeline(pageMaxSeq, volatileCursor);
1515
1557
  const deferredKeyFetches = new Map();
1516
1558
  let blockedSeq = 0;
1517
1559
  const pageDecryptedBefore = decrypted.length;
@@ -1624,7 +1666,17 @@ export class V2E2EECoordinator {
1624
1666
  let ackSeq = pageContigBefore;
1625
1667
  let contigAdvanced = false;
1626
1668
  try {
1669
+ this.ensureDeliveryCurrent(ns, generation);
1670
+ const window = client._seqTracker.getSyncState?.(ns);
1671
+ const deliveryTarget = window && commitTarget >= window.tail - 1
1672
+ ? Math.max(commitTarget, window.head) : commitTarget;
1673
+ if (deliveryTarget > pageContigBefore) {
1674
+ await client._drainOrderedMessages(ns, undefined, false, false, 'pull', deliveryTarget);
1675
+ }
1676
+ this.ensureDeliveryCurrent(ns, generation);
1677
+ pageTrackerSnapshot = client._seqTracker.snapshotNamespace?.(ns) ?? null;
1627
1678
  if (commitTarget > pageContigBefore) {
1679
+ trackerMutated = true;
1628
1680
  client._seqTracker.forceContiguousSeq(ns, commitTarget);
1629
1681
  if (retentionFloor > pageContigBefore) {
1630
1682
  client._clientLog.info(`message.v2.pull retention-floor advance: ns=${ns} contiguous=${pageContigBefore} -> retention_floor_seq=${retentionFloor}`);
@@ -1640,19 +1692,21 @@ export class V2E2EECoordinator {
1640
1692
  contigAdvanced = ackSeq !== pageContigBefore;
1641
1693
  if (contigAdvanced) {
1642
1694
  await client._commitSeqTrackerState(ns);
1695
+ this.ensureDeliveryCurrent(ns, generation);
1643
1696
  if (pageCount === 1 && deferredServerCursor > 0)
1644
1697
  this.clearForwardCursor(ns, ackSeq);
1645
1698
  }
1646
1699
  }
1647
1700
  catch (exc) {
1648
- if (typeof client._seqTracker.restoreNamespaceSnapshot === 'function') {
1649
- client._seqTracker.restoreNamespaceSnapshot(ns, pageTrackerSnapshot);
1701
+ if (trackerMutated && Number(client._v2RuntimeGeneration ?? 0) === generation
1702
+ && client._rpcPipeline?.isPullNamespaceCurrent?.(ns) !== false
1703
+ && typeof client._seqTracker.restoreNamespaceSnapshot === 'function') {
1704
+ (client._seqTracker._restoreNamespaceSnapshotPreservingPush ?? client._seqTracker.restoreNamespaceSnapshot)
1705
+ .call(client._seqTracker, ns, pageTrackerSnapshot);
1650
1706
  }
1651
1707
  client._clientLog.error?.(`message.v2.pull Forward commit rolled back: ns=${ns} target=${commitTarget} error=${formatE2EEError(exc)}`);
1652
1708
  throw exc;
1653
1709
  }
1654
- if (contigAdvanced)
1655
- await client._drainOrderedMessages(ns, undefined, false, false, 'pull');
1656
1710
  for (const fetch of deferredKeyFetches.values()) {
1657
1711
  this.scheduleSenderIKFetch(fetch.fromAid, fetch.senderDeviceId, fetch.groupId);
1658
1712
  }
@@ -1670,18 +1724,8 @@ export class V2E2EECoordinator {
1670
1724
  || (contigAdvanced && ackSeq > knownServerAckSeq));
1671
1725
  if (ackNeeded) {
1672
1726
  this.recordForwardAck(ns, ackSeq);
1673
- const nextAfter = Math.max(pageContiguousSeq, nextAfterSeq);
1674
- const canContinuePage = blockedSeq <= 0 && messages.length > 0 && nextAfter > nextAfterSeq;
1675
- if (canContinuePage && !pipelineRunning) {
1676
- pendingAckSeq = Math.max(pendingAckSeq, ackSeq);
1677
- lastAutoAckSeq = Math.max(lastAutoAckSeq, ackSeq);
1678
- client._clientLog.debug(`message.v2.pull queued piggyback auto-ack: ns=${ns}, ack_seq=${pendingAckSeq}, raw_count=${messages.length}`);
1679
- }
1680
- else {
1681
- client._clientLog.debug(`message.v2.pull sending terminal auto-ack: ns=${ns}, ack_seq=${ackSeq}, raw_count=${messages.length}`);
1682
- await this.confirmForwardP2PAck(ackSeq);
1683
- lastAutoAckSeq = Math.max(lastAutoAckSeq, ackSeq);
1684
- }
1727
+ pendingAckSeq = Math.max(pendingAckSeq, ackSeq);
1728
+ client._clientLog.debug(`message.v2.pull queued piggyback auto-ack: ns=${ns}, ack_seq=${pendingAckSeq}, raw_count=${messages.length}`);
1685
1729
  }
1686
1730
  }
1687
1731
  if (!ns) {
@@ -1698,7 +1742,7 @@ export class V2E2EECoordinator {
1698
1742
  currentSeq: client._seqTracker.getContiguousSeq(ns),
1699
1743
  targetSeq: serverHead,
1700
1744
  });
1701
- const nextAfter = Math.max(pageContiguousSeq, nextAfterSeq);
1745
+ const nextAfter = Math.max(pageMaxSeq, nextAfterSeq);
1702
1746
  const rawCount = messages.length;
1703
1747
  if (blockedSeq > 0)
1704
1748
  break;
@@ -1730,11 +1774,7 @@ export class V2E2EECoordinator {
1730
1774
  && volatileCursor !== requestedVolatileCursor;
1731
1775
  if (!shouldContinuePersistent && !shouldContinueVolatile)
1732
1776
  break;
1733
- if (shouldContinueForPipeline && !pipelineRunning && limit >= V2_PULL_MAX_PAGE_LIMIT) {
1734
- if (pendingAckSeq > 0) {
1735
- await this.confirmForwardP2PAck(pendingAckSeq);
1736
- pendingAckSeq = 0;
1737
- }
1777
+ if (shouldContinueForPipeline && volatileMessages.length === 0 && !pipelineRunning && limit >= V2_PULL_MAX_PAGE_LIMIT) {
1738
1778
  startPipeline(nextAfter, volatileCursor);
1739
1779
  }
1740
1780
  if (!pipelineRunning && pullGateKey && opts?.gateLocked && client._rpcPipeline?.yieldPullGate) {
@@ -1744,7 +1784,7 @@ export class V2E2EECoordinator {
1744
1784
  await client._rpcPipeline.yieldPullGate(pullGateKey, nextKey, true);
1745
1785
  pullGateKey = nextKey;
1746
1786
  }
1747
- const tailDelayMs = v2PullTailDelayMs(rawCount, limit);
1787
+ const tailDelayMs = pipelineRunning ? 0 : v2PullTailDelayMs(rawCount, limit);
1748
1788
  if (tailDelayMs > 0)
1749
1789
  await v2Sleep(tailDelayMs);
1750
1790
  nextAfterSeq = nextAfter;
@@ -1755,11 +1795,14 @@ export class V2E2EECoordinator {
1755
1795
  }
1756
1796
  if (pageCount >= maxPages) {
1757
1797
  client._clientLog.warn(`message.v2.pull reached max_pages=${maxPages} after_seq=${nextAfterSeq}`);
1758
- if (pendingAckSeq > 0) {
1759
- client._clientLog.debug(`message.v2.pull flushing pending auto-ack after max_pages: ns=${ns}, ack_seq=${pendingAckSeq}`);
1760
- await this.confirmForwardP2PAck(pendingAckSeq);
1798
+ }
1799
+ if (pendingAckSeq > 0) {
1800
+ this.ensureDeliveryCurrent(ns, generation);
1801
+ client._clientLog.debug(`message.v2.pull flushing pending auto-ack: ns=${ns}, ack_seq=${pendingAckSeq}`);
1802
+ const ackToFlush = pendingAckSeq;
1803
+ await this.confirmForwardP2PAck(ackToFlush);
1804
+ if (pendingAckSeq <= ackToFlush)
1761
1805
  pendingAckSeq = 0;
1762
- }
1763
1806
  }
1764
1807
  client._clientLog.debug(`message.v2.pull done: requested_after_seq=${afterSeq}, pages=${pageCount}, decrypted=${decrypted.length}, ns=${ns || '<none>'}`);
1765
1808
  return decrypted;
@@ -1846,8 +1889,10 @@ export class V2E2EECoordinator {
1846
1889
  }
1847
1890
  async confirmForwardP2PAck(upToSeq) {
1848
1891
  const ns = this.client._aid ? `p2p:${this.client._aid}` : '';
1892
+ const generation = Number(this.client._v2RuntimeGeneration ?? 0);
1849
1893
  try {
1850
1894
  const result = await this.ackV2ForForward(upToSeq);
1895
+ this.ensureDeliveryCurrent(ns, generation);
1851
1896
  const actualAckSeq = this.client._delivery.resolveForwardAckSeq(result, upToSeq);
1852
1897
  if (actualAckSeq < upToSeq) {
1853
1898
  throw new E2EEError(`message.v2.ack server ACK watermark ${actualAckSeq} is below requested ${upToSeq}`);
@@ -1855,7 +1900,9 @@ export class V2E2EECoordinator {
1855
1900
  this.clearForwardAck(ns, upToSeq);
1856
1901
  }
1857
1902
  catch (exc) {
1858
- this.recordForwardAck(ns, upToSeq);
1903
+ if (Number(this.client._v2RuntimeGeneration ?? 0) === generation
1904
+ && this.client._rpcPipeline?.isPullNamespaceCurrent?.(ns) !== false)
1905
+ this.recordForwardAck(ns, upToSeq);
1859
1906
  throw exc;
1860
1907
  }
1861
1908
  }
@@ -2128,6 +2175,7 @@ export class V2E2EECoordinator {
2128
2175
  }
2129
2176
  async pullGroupV2TailInternal(params, deferPublish = false) {
2130
2177
  const client = this.client;
2178
+ const generation = Number(client._v2RuntimeGeneration ?? 0);
2131
2179
  if (params.window_mode !== 'tail')
2132
2180
  throw new ValidationError('window_mode must equal tail');
2133
2181
  if ('ack_up_to_seq' in params || params.force === true)
@@ -2167,6 +2215,15 @@ export class V2E2EECoordinator {
2167
2215
  if (resultAid)
2168
2216
  groupAid = resultAid;
2169
2217
  const page = validateTailPage(result, afterSeq, limit);
2218
+ const decoded = [];
2219
+ for (const msg of page.messages) {
2220
+ this.ensureDeliveryCurrent(ns, generation);
2221
+ client._logMessageDebug('tail-raw', 'group.v2.pull', 'group.message_created', msg);
2222
+ const plaintext = await this.decodeGroupWindowMessage(msg, groupId, groupAid, true, false, 'tail', deferPublish);
2223
+ if (plaintext)
2224
+ decoded.push(plaintext);
2225
+ }
2226
+ this.ensureDeliveryCurrent(ns, generation);
2170
2227
  client._seqTracker.commitTailWindow(ns, {
2171
2228
  windowStartSeq: page.windowStart,
2172
2229
  headSeq: page.head,
@@ -2175,13 +2232,7 @@ export class V2E2EECoordinator {
2175
2232
  });
2176
2233
  client._persistSeq(ns);
2177
2234
  await client._saveSeqTrackerState?.();
2178
- const decoded = [];
2179
- for (const msg of page.messages) {
2180
- client._logMessageDebug('tail-raw', 'group.v2.pull', 'group.message_created', msg);
2181
- const plaintext = await this.decodeGroupWindowMessage(msg, groupId, groupAid, true, false, 'tail', deferPublish);
2182
- if (plaintext)
2183
- decoded.push(plaintext);
2184
- }
2235
+ this.ensureDeliveryCurrent(ns, generation);
2185
2236
  const ack = client._seqTracker.getContiguousSeq(ns);
2186
2237
  if (ownsCursor)
2187
2238
  client._delivery.syncProgress(ns, {
@@ -2229,6 +2280,7 @@ export class V2E2EECoordinator {
2229
2280
  }
2230
2281
  async pullGroupV2Impl(groupId, afterSeq = 0, limit = 50, opts) {
2231
2282
  const client = this.client;
2283
+ const generation = Number(client._v2RuntimeGeneration ?? 0);
2232
2284
  await client._ensureV2SessionReady('group.pull');
2233
2285
  const gid = String(groupId ?? '').trim();
2234
2286
  if (!gid)
@@ -2303,21 +2355,26 @@ export class V2E2EECoordinator {
2303
2355
  for (let produced = pageCount; produced < maxPages; produced += 1) {
2304
2356
  if (pipelineStopped)
2305
2357
  break;
2358
+ this.ensureDeliveryCurrent(ns, generation);
2359
+ const ackUpToSeq = pendingAckSeq;
2360
+ const inlineAckGeneration = ackUpToSeq > 0 ? client._delivery.captureGroupInlineAckGeneration() : null;
2306
2361
  const result = await client._callRawV2Rpc('group.v2.pull', withExplicitGroupAid({
2307
2362
  group_id: wireGroupId,
2308
2363
  after_seq: producerAfterSeq,
2309
2364
  limit,
2310
2365
  ...cursorParams,
2311
2366
  ...(opts?.force ? { force: true } : {}),
2367
+ ...(ackUpToSeq > 0 ? { ack_up_to_seq: ackUpToSeq } : {}),
2312
2368
  }, groupAid), pullGateKey);
2313
2369
  if (pipelineStopped)
2314
2370
  break;
2371
+ this.ensureDeliveryCurrent(ns, generation);
2315
2372
  const pageMessages = (Array.isArray(result.messages) ? result.messages : []);
2316
2373
  const enqueued = await pipelineQueue.put({
2317
2374
  result,
2318
2375
  afterSeq: producerAfterSeq,
2319
- ackUpToSeq: 0,
2320
- inlineAckGeneration: null,
2376
+ ackUpToSeq,
2377
+ inlineAckGeneration,
2321
2378
  requestedVolatileCursor: '',
2322
2379
  weight: pageMessages.length,
2323
2380
  }, pageMessages.length);
@@ -2327,7 +2384,7 @@ export class V2E2EECoordinator {
2327
2384
  const seqs = messages
2328
2385
  .map((msg) => Number(msg.seq ?? 0))
2329
2386
  .filter((seq) => Number.isFinite(seq) && seq > 0);
2330
- const nextAfter = Math.max(contiguousPullSeq(producerAfterSeq, seqs, result.contiguous_seq), producerAfterSeq);
2387
+ const nextAfter = Math.max(producerAfterSeq, ...seqs);
2331
2388
  const cursor = isJsonObject(result.cursor)
2332
2389
  ? result.cursor : null;
2333
2390
  const shouldContinue = shouldContinueForwardPage({
@@ -2375,7 +2432,7 @@ export class V2E2EECoordinator {
2375
2432
  }
2376
2433
  pageCount += 1;
2377
2434
  const ackUpToSeq = ownsCursor ? (pipelinePage?.ackUpToSeq ?? pendingAckSeq) : 0;
2378
- const inlineAckGeneration = pipelinePage?.inlineAckGeneration ?? (ackUpToSeq > 0
2435
+ const inlineAckGeneration = pipelinePage ? pipelinePage.inlineAckGeneration : (ackUpToSeq > 0
2379
2436
  ? client._delivery.captureGroupInlineAckGeneration()
2380
2437
  : null);
2381
2438
  client._clientLog.debug(`group.v2.pull page request: group=${gid}, page=${pageCount}, after_seq=${nextAfterSeq}, limit=${limit}, ack_up_to_seq=${ackUpToSeq}, ns=${ns}`);
@@ -2394,21 +2451,21 @@ export class V2E2EECoordinator {
2394
2451
  ...(ackUpToSeq > 0 ? { ack_up_to_seq: ackUpToSeq } : {}),
2395
2452
  }, groupAid), pullGateKey);
2396
2453
  }
2454
+ if (ownsCursor)
2455
+ this.ensureDeliveryCurrent(ns, generation);
2397
2456
  if (ackUpToSeq > 0) {
2398
2457
  const actualAckSeq = client._delivery.resolveGroupPullAckSeq(result, ackUpToSeq);
2399
2458
  if (actualAckSeq >= ackUpToSeq) {
2400
2459
  if (inlineAckGeneration !== null) {
2401
2460
  client._delivery.onGroupV2PullAckResult(gid, result, ackUpToSeq, inlineAckGeneration);
2402
2461
  }
2403
- pendingAckSeq = 0;
2462
+ if (pendingAckSeq <= ackUpToSeq)
2463
+ pendingAckSeq = 0;
2404
2464
  lastAutoAckSeq = Math.max(lastAutoAckSeq, ackUpToSeq);
2405
2465
  this.clearForwardAck(ns, ackUpToSeq);
2406
2466
  }
2407
2467
  else {
2408
2468
  client._clientLog.warn(`group.v2.pull piggyback ACK below target: group=${gid}, requested=${ackUpToSeq}, actual=${actualAckSeq}; fallback terminal ACK`);
2409
- await this.confirmForwardGroupAck(gid, ackUpToSeq, groupAid);
2410
- pendingAckSeq = 0;
2411
- lastAutoAckSeq = Math.max(lastAutoAckSeq, ackUpToSeq);
2412
2469
  }
2413
2470
  }
2414
2471
  const resultGroupAid = String(result.group_aid ?? result.groupAid ?? '').trim();
@@ -2429,14 +2486,20 @@ export class V2E2EECoordinator {
2429
2486
  .map((msg) => Number(msg.seq ?? 0))
2430
2487
  .filter((seq) => Number.isFinite(seq) && seq > 0);
2431
2488
  const pageContigBefore = client._seqTracker.getContiguousSeq(ns);
2432
- const pageTrackerSnapshot = typeof client._seqTracker.snapshotNamespace === 'function'
2433
- ? client._seqTracker.snapshotNamespace(ns)
2434
- : null;
2489
+ let pageTrackerSnapshot = null;
2490
+ let trackerMutated = false;
2435
2491
  let pageMaxSeq = nextAfterSeq;
2436
2492
  if (seqs.length > 0) {
2437
2493
  pageMaxSeq = Math.max(...seqs);
2438
2494
  }
2439
2495
  const pageContiguousSeq = contiguousPullSeq(pageContigBefore, seqs, result.contiguous_seq);
2496
+ if (!pipelineRunning && ownsCursor && shouldContinueForwardPage({
2497
+ rawCount: messages.length, nextAfterSeq, nextAfter: pageMaxSeq, limit,
2498
+ hasMoreField, hasMore, serverHead,
2499
+ observedUpper: Math.max(client._seqTracker.getMaxSeenSeq(ns), Number(client._delivery.pendingPullUpper?.(ns) ?? 0)),
2500
+ pendingAckSeq: 0,
2501
+ }))
2502
+ startPipeline(pageMaxSeq);
2440
2503
  const deferredKeyFetches = new Map();
2441
2504
  let blockedSeq = 0;
2442
2505
  const pageDecryptedBefore = decrypted.length;
@@ -2582,7 +2645,17 @@ export class V2E2EECoordinator {
2582
2645
  let contigAdvanced = false;
2583
2646
  if (ownsCursor) {
2584
2647
  try {
2648
+ this.ensureDeliveryCurrent(ns, generation);
2649
+ const window = client._seqTracker.getSyncState?.(ns);
2650
+ const deliveryTarget = window && commitTarget >= window.tail - 1
2651
+ ? Math.max(commitTarget, window.head) : commitTarget;
2652
+ if (deliveryTarget > pageContigBefore) {
2653
+ await client._drainOrderedMessages(ns, undefined, false, false, 'pull', deliveryTarget);
2654
+ }
2655
+ this.ensureDeliveryCurrent(ns, generation);
2656
+ pageTrackerSnapshot = client._seqTracker.snapshotNamespace?.(ns) ?? null;
2585
2657
  if (commitTarget > pageContigBefore) {
2658
+ trackerMutated = true;
2586
2659
  client._seqTracker.forceContiguousSeq(ns, commitTarget);
2587
2660
  if (effectiveFloor > pageContigBefore) {
2588
2661
  client._clientLog.info(`group.v2.pull floor advance: ns=${ns} contiguous=${pageContigBefore} -> effective_floor=${effectiveFloor} retention_floor=${retentionFloor} visibility_floor=${visibilityFloor}`);
@@ -2598,19 +2671,21 @@ export class V2E2EECoordinator {
2598
2671
  contigAdvanced = ackSeq !== pageContigBefore;
2599
2672
  if (contigAdvanced) {
2600
2673
  await client._commitSeqTrackerState(ns);
2674
+ this.ensureDeliveryCurrent(ns, generation);
2601
2675
  if (pageCount === 1 && deferredServerCursor > 0)
2602
2676
  this.clearForwardCursor(ns, ackSeq);
2603
2677
  }
2604
2678
  }
2605
2679
  catch (exc) {
2606
- if (typeof client._seqTracker.restoreNamespaceSnapshot === 'function') {
2607
- client._seqTracker.restoreNamespaceSnapshot(ns, pageTrackerSnapshot);
2680
+ if (trackerMutated && Number(client._v2RuntimeGeneration ?? 0) === generation
2681
+ && client._rpcPipeline?.isPullNamespaceCurrent?.(ns) !== false
2682
+ && typeof client._seqTracker.restoreNamespaceSnapshot === 'function') {
2683
+ (client._seqTracker._restoreNamespaceSnapshotPreservingPush ?? client._seqTracker.restoreNamespaceSnapshot)
2684
+ .call(client._seqTracker, ns, pageTrackerSnapshot);
2608
2685
  }
2609
2686
  client._clientLog.error?.(`group.v2.pull Forward commit rolled back: group=${gid} ns=${ns} target=${commitTarget} error=${formatE2EEError(exc)}`);
2610
2687
  throw exc;
2611
2688
  }
2612
- if (contigAdvanced)
2613
- await client._drainOrderedMessages(ns, undefined, false, false, 'pull');
2614
2689
  }
2615
2690
  for (const fetch of deferredKeyFetches.values()) {
2616
2691
  this.scheduleSenderIKFetch(fetch.fromAid, fetch.senderDeviceId, fetch.groupId);
@@ -2630,18 +2705,8 @@ export class V2E2EECoordinator {
2630
2705
  || (contigAdvanced && ackSeq > knownServerCursorSeq));
2631
2706
  if (ackNeeded) {
2632
2707
  this.recordForwardAck(ns, ackSeq);
2633
- const nextAfter = Math.max(pageContiguousSeq, nextAfterSeq);
2634
- const canContinuePage = blockedSeq <= 0 && messages.length > 0 && nextAfter > nextAfterSeq && ownsCursor;
2635
- if (canContinuePage && !pipelineRunning) {
2636
- pendingAckSeq = Math.max(pendingAckSeq, ackSeq);
2637
- lastAutoAckSeq = Math.max(lastAutoAckSeq, ackSeq);
2638
- client._clientLog.debug(`group.v2.pull queued piggyback auto-ack: group=${gid}, ns=${ns}, ack_seq=${pendingAckSeq}, raw_count=${messages.length}`);
2639
- }
2640
- else {
2641
- client._clientLog.debug(`group.v2.pull sending terminal auto-ack: group=${gid}, ns=${ns}, ack_seq=${ackSeq}, raw_count=${messages.length}`);
2642
- await this.confirmForwardGroupAck(gid, ackSeq, groupAid);
2643
- lastAutoAckSeq = Math.max(lastAutoAckSeq, ackSeq);
2644
- }
2708
+ pendingAckSeq = Math.max(pendingAckSeq, ackSeq);
2709
+ client._clientLog.debug(`group.v2.pull queued piggyback auto-ack: group=${gid}, ns=${ns}, ack_seq=${pendingAckSeq}, raw_count=${messages.length}`);
2645
2710
  }
2646
2711
  client._delivery.syncProgress(ns, {
2647
2712
  rawCount: messages.length,
@@ -2651,7 +2716,7 @@ export class V2E2EECoordinator {
2651
2716
  currentSeq: client._seqTracker.getContiguousSeq(ns),
2652
2717
  targetSeq: serverHead,
2653
2718
  });
2654
- const nextAfter = Math.max(pageContiguousSeq, nextAfterSeq);
2719
+ const nextAfter = Math.max(pageMaxSeq, nextAfterSeq);
2655
2720
  if (!ownsCursor)
2656
2721
  break;
2657
2722
  const rawCount = messages.length;
@@ -2682,10 +2747,6 @@ export class V2E2EECoordinator {
2682
2747
  if (!shouldContinue)
2683
2748
  break;
2684
2749
  if (shouldContinueForPipeline && !pipelineRunning && limit >= V2_PULL_MAX_PAGE_LIMIT) {
2685
- if (pendingAckSeq > 0) {
2686
- await this.confirmForwardGroupAck(gid, pendingAckSeq, groupAid);
2687
- pendingAckSeq = 0;
2688
- }
2689
2750
  startPipeline(nextAfter);
2690
2751
  }
2691
2752
  if (!pipelineRunning && pullGateKey && opts?.gateLocked && client._rpcPipeline?.yieldPullGate) {
@@ -2696,7 +2757,7 @@ export class V2E2EECoordinator {
2696
2757
  await client._rpcPipeline.yieldPullGate(pullGateKey, nextKey, true);
2697
2758
  pullGateKey = nextKey;
2698
2759
  }
2699
- const tailDelayMs = v2PullTailDelayMs(rawCount, limit);
2760
+ const tailDelayMs = pipelineRunning ? 0 : v2PullTailDelayMs(rawCount, limit);
2700
2761
  if (tailDelayMs > 0)
2701
2762
  await v2Sleep(tailDelayMs);
2702
2763
  nextAfterSeq = nextAfter;
@@ -2707,11 +2768,14 @@ export class V2E2EECoordinator {
2707
2768
  }
2708
2769
  if (pageCount >= maxPages) {
2709
2770
  client._clientLog.warn(`group.v2.pull reached max_pages=${maxPages} group=${gid} after_seq=${nextAfterSeq}`);
2710
- if (pendingAckSeq > 0) {
2711
- client._clientLog.debug(`group.v2.pull flushing pending auto-ack after max_pages: group=${gid}, ns=${ns}, ack_seq=${pendingAckSeq}`);
2712
- await this.confirmForwardGroupAck(gid, pendingAckSeq, groupAid);
2771
+ }
2772
+ if (pendingAckSeq > 0) {
2773
+ this.ensureDeliveryCurrent(ns, generation);
2774
+ client._clientLog.debug(`group.v2.pull flushing pending auto-ack: group=${gid}, ns=${ns}, ack_seq=${pendingAckSeq}`);
2775
+ const ackToFlush = pendingAckSeq;
2776
+ await this.confirmForwardGroupAck(gid, ackToFlush, groupAid);
2777
+ if (pendingAckSeq <= ackToFlush)
2713
2778
  pendingAckSeq = 0;
2714
- }
2715
2779
  }
2716
2780
  client._clientLog.debug(`group.v2.pull done: group=${gid}, requested_after_seq=${afterSeq}, pages=${pageCount}, decrypted=${decrypted.length}, ns=${ns}`);
2717
2781
  return decrypted;
@@ -2732,8 +2796,10 @@ export class V2E2EECoordinator {
2732
2796
  }
2733
2797
  async confirmForwardGroupAck(groupId, upToSeq, groupAid) {
2734
2798
  const ns = `group:${String(groupId ?? '').trim()}`;
2799
+ const generation = Number(this.client._v2RuntimeGeneration ?? 0);
2735
2800
  try {
2736
2801
  const result = await this.ackGroupV2ForForward(groupId, upToSeq, groupAid);
2802
+ this.ensureDeliveryCurrent(ns, generation);
2737
2803
  const actualAckSeq = this.client._delivery.resolveForwardAckSeq(result, upToSeq);
2738
2804
  if (actualAckSeq < upToSeq) {
2739
2805
  throw new E2EEError(`group.v2.ack server ACK watermark ${actualAckSeq} is below requested ${upToSeq}`);
@@ -2741,7 +2807,9 @@ export class V2E2EECoordinator {
2741
2807
  this.clearForwardAck(ns, upToSeq);
2742
2808
  }
2743
2809
  catch (exc) {
2744
- this.recordForwardAck(ns, upToSeq);
2810
+ if (Number(this.client._v2RuntimeGeneration ?? 0) === generation
2811
+ && this.client._rpcPipeline?.isPullNamespaceCurrent?.(ns) !== false)
2812
+ this.recordForwardAck(ns, upToSeq);
2745
2813
  throw exc;
2746
2814
  }
2747
2815
  }
@@ -3241,6 +3309,8 @@ export class V2E2EECoordinator {
3241
3309
  encrypted: true,
3242
3310
  e2ee,
3243
3311
  };
3312
+ if (typeof plaintext.type === 'string' && plaintext.type.startsWith('aun.ownership.'))
3313
+ result.from_aid = fromAid;
3244
3314
  const senderDeviceId = String(aad.from_device ?? '').trim();
3245
3315
  if (senderDeviceId)
3246
3316
  result.sender_device_id = senderDeviceId;