@agentunion/fastaun-browser 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 (149) 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 +17 -3
  48. package/dist/agent-md-schema.d.ts.map +1 -1
  49. package/dist/agent-md-schema.js +94 -26
  50. package/dist/agent-md-schema.js.map +1 -1
  51. package/dist/agent-md.d.ts +54 -3
  52. package/dist/agent-md.d.ts.map +1 -1
  53. package/dist/agent-md.js +711 -65
  54. package/dist/agent-md.js.map +1 -1
  55. package/dist/aid-store.d.ts +58 -46
  56. package/dist/aid-store.d.ts.map +1 -1
  57. package/dist/aid-store.js +254 -248
  58. package/dist/aid-store.js.map +1 -1
  59. package/dist/aid.d.ts +4 -1
  60. package/dist/aid.d.ts.map +1 -1
  61. package/dist/aid.js +27 -33
  62. package/dist/aid.js.map +1 -1
  63. package/dist/auth.d.ts +4 -1
  64. package/dist/auth.d.ts.map +1 -1
  65. package/dist/auth.js +426 -39
  66. package/dist/auth.js.map +1 -1
  67. package/dist/bundle.js +38179 -32054
  68. package/dist/cert-utils.d.ts +50 -2
  69. package/dist/cert-utils.d.ts.map +1 -1
  70. package/dist/cert-utils.js +105 -47
  71. package/dist/cert-utils.js.map +1 -1
  72. package/dist/client/delivery.d.ts +22 -1
  73. package/dist/client/delivery.d.ts.map +1 -1
  74. package/dist/client/delivery.js +389 -78
  75. package/dist/client/delivery.js.map +1 -1
  76. package/dist/client/rpc-pipeline.d.ts.map +1 -1
  77. package/dist/client/rpc-pipeline.js +103 -77
  78. package/dist/client/rpc-pipeline.js.map +1 -1
  79. package/dist/client/v2-e2ee.d.ts +1 -0
  80. package/dist/client/v2-e2ee.d.ts.map +1 -1
  81. package/dist/client/v2-e2ee.js +182 -89
  82. package/dist/client/v2-e2ee.js.map +1 -1
  83. package/dist/client.d.ts +100 -1
  84. package/dist/client.d.ts.map +1 -1
  85. package/dist/client.js +1220 -25
  86. package/dist/client.js.map +1 -1
  87. package/dist/crypto.d.ts.map +1 -1
  88. package/dist/crypto.js +8 -6
  89. package/dist/crypto.js.map +1 -1
  90. package/dist/errors.d.ts.map +1 -1
  91. package/dist/errors.js +3 -2
  92. package/dist/errors.js.map +1 -1
  93. package/dist/events.d.ts +9 -2
  94. package/dist/events.d.ts.map +1 -1
  95. package/dist/events.js +72 -13
  96. package/dist/events.js.map +1 -1
  97. package/dist/facades.d.ts +82 -2
  98. package/dist/facades.d.ts.map +1 -1
  99. package/dist/facades.js +100 -6
  100. package/dist/facades.js.map +1 -1
  101. package/dist/index.d.ts +7 -3
  102. package/dist/index.d.ts.map +1 -1
  103. package/dist/index.js +3 -2
  104. package/dist/index.js.map +1 -1
  105. package/dist/keystore/index.d.ts +17 -0
  106. package/dist/keystore/index.d.ts.map +1 -1
  107. package/dist/keystore/indexeddb-shared.d.ts +10 -1
  108. package/dist/keystore/indexeddb-shared.d.ts.map +1 -1
  109. package/dist/keystore/indexeddb-shared.js +111 -0
  110. package/dist/keystore/indexeddb-shared.js.map +1 -1
  111. package/dist/keystore/indexeddb-token-store.d.ts +7 -1
  112. package/dist/keystore/indexeddb-token-store.d.ts.map +1 -1
  113. package/dist/keystore/indexeddb-token-store.js +30 -1
  114. package/dist/keystore/indexeddb-token-store.js.map +1 -1
  115. package/dist/org-protocol.d.ts +98 -0
  116. package/dist/org-protocol.d.ts.map +1 -0
  117. package/dist/org-protocol.js +1802 -0
  118. package/dist/org-protocol.js.map +1 -0
  119. package/dist/ownership-proof.d.ts +93 -5
  120. package/dist/ownership-proof.d.ts.map +1 -1
  121. package/dist/ownership-proof.js +239 -53
  122. package/dist/ownership-proof.js.map +1 -1
  123. package/dist/ownership-v21.d.ts +194 -0
  124. package/dist/ownership-v21.d.ts.map +1 -0
  125. package/dist/ownership-v21.js +678 -0
  126. package/dist/ownership-v21.js.map +1 -0
  127. package/dist/ownership-workflow.d.ts +151 -0
  128. package/dist/ownership-workflow.d.ts.map +1 -0
  129. package/dist/ownership-workflow.js +1229 -0
  130. package/dist/ownership-workflow.js.map +1 -0
  131. package/dist/seq-tracker.d.ts +1 -0
  132. package/dist/seq-tracker.d.ts.map +1 -1
  133. package/dist/seq-tracker.js +10 -2
  134. package/dist/seq-tracker.js.map +1 -1
  135. package/dist/tools/cross-sdk-agent.d.ts +83 -0
  136. package/dist/tools/cross-sdk-agent.d.ts.map +1 -1
  137. package/dist/tools/cross-sdk-agent.js +415 -7
  138. package/dist/tools/cross-sdk-agent.js.map +1 -1
  139. package/dist/transport.d.ts +7 -0
  140. package/dist/transport.d.ts.map +1 -1
  141. package/dist/transport.js +90 -2
  142. package/dist/transport.js.map +1 -1
  143. package/dist/v2/e2ee/decrypt.d.ts +1 -0
  144. package/dist/v2/e2ee/decrypt.d.ts.map +1 -1
  145. package/dist/v2/e2ee/decrypt.js +9 -1
  146. package/dist/v2/e2ee/decrypt.js.map +1 -1
  147. package/dist/version.d.ts +1 -1
  148. package/dist/version.js +1 -1
  149. package/package.json +1 -1
@@ -113,12 +113,10 @@ function deduplicateForwardPageMessages(value, afterSeq) {
113
113
  }
114
114
  return [...bySeq.entries()].sort(([a], [b]) => a - b).map(([, row]) => row);
115
115
  }
116
- function contiguousPullSeq(afterSeq, seqs, proof) {
116
+ function contiguousPullSeq(afterSeq, seqs, _proof) {
117
117
  // Forward 原始页中的缺号视为服务端永久空号;页完成后按页内最大
118
118
  // seq 推进 A,避免单页空洞阻塞后续 pull/ACK。
119
- let contiguous = seqs.length > 0 ? Math.max(afterSeq, ...seqs) : afterSeq;
120
- const serverProof = safeNonNegativeSeq(proof);
121
- return serverProof !== null ? Math.max(contiguous, serverProof) : contiguous;
119
+ return seqs.length > 0 ? Math.max(afterSeq, ...seqs) : afterSeq;
122
120
  }
123
121
  function deduplicatePulledResults(messages) {
124
122
  const seen = new Set();
@@ -138,7 +136,7 @@ function shouldContinueForwardPage({ rawCount, nextAfterSeq, nextAfter, limit, h
138
136
  // 无新 seq 或游标未前进时必须停止,避免错误元数据造成循环。
139
137
  if (rawCount <= 0 || nextAfter <= nextAfterSeq)
140
138
  return false;
141
- if (hasMore || serverHead > nextAfter || pendingAckSeq > 0 || observedUpper > nextAfter)
139
+ if (hasMore || serverHead > nextAfter || observedUpper > nextAfter)
142
140
  return true;
143
141
  // 仅旧服务缺失 has_more 时,以满页保留兼容续拉。
144
142
  return !hasMoreField && limit > 0 && rawCount >= limit;
@@ -1000,11 +998,12 @@ export class V2E2EECoordinator {
1000
998
  client._clientLog.debug(`V2 sender IK pending retry delivered: key=${key}`);
1001
999
  continue;
1002
1000
  }
1003
- client._v2SenderIKPending.delete(key);
1004
1001
  const seq = Number(entry.msg.seq ?? 0);
1005
1002
  if (entry.groupId) {
1006
- if (client._delivery.isSelfSentGroupMessage?.(entry.msg))
1003
+ if (client._delivery.isSelfSentGroupMessage?.(entry.msg)) {
1004
+ client._v2SenderIKPending.delete(key);
1007
1005
  continue;
1006
+ }
1008
1007
  plaintext = normalizeGroupMentionMode(plaintext, entry.msg, entry.msg.envelope_json);
1009
1008
  plaintext.group_id = entry.groupId;
1010
1009
  await client._publishPulledMessage('group.message_created', `group:${entry.groupId}`, seq, plaintext, false, 'pending_retry');
@@ -1012,6 +1011,7 @@ export class V2E2EECoordinator {
1012
1011
  else {
1013
1012
  await client._publishPulledMessage('message.received', `p2p:${client._aid ?? ''}`, seq, plaintext, false, 'pending_retry');
1014
1013
  }
1014
+ client._v2SenderIKPending.delete(key);
1015
1015
  client._clientLog.debug(`V2 sender IK pending retry delivered: key=${key}`);
1016
1016
  }
1017
1017
  }
@@ -1110,6 +1110,12 @@ export class V2E2EECoordinator {
1110
1110
  history_error: { code: 'decrypt_failed', message: reason },
1111
1111
  };
1112
1112
  }
1113
+ ensureDeliveryCurrent(ns, generation, inlineGeneration) {
1114
+ if (Number(this.client._v2RuntimeGeneration ?? 0) !== generation
1115
+ || (inlineGeneration !== undefined && !this.client._delivery.isInlineGenerationCurrent(inlineGeneration))
1116
+ || this.client._rpcPipeline?.isPullNamespaceCurrent?.(ns) === false)
1117
+ throw new StateError('pull invalidated');
1118
+ }
1113
1119
  async decodeP2PWindowMessage(msg, publish, history, source, orderedPublish = false) {
1114
1120
  const client = this.client;
1115
1121
  const seq = strictWindowSeq(msg.seq, 'messages[].seq', true);
@@ -1257,6 +1263,8 @@ export class V2E2EECoordinator {
1257
1263
  }
1258
1264
  async pullV2TailInternal(params, deferPublish = false) {
1259
1265
  const client = this.client;
1266
+ const generation = Number(client._v2RuntimeGeneration ?? 0);
1267
+ const inlineGeneration = client._delivery.captureInlineGeneration?.();
1260
1268
  if (params.window_mode !== 'tail')
1261
1269
  throw new ValidationError('window_mode must equal tail');
1262
1270
  if ('ack_up_to_seq' in params || params.force === true)
@@ -1271,19 +1279,27 @@ export class V2E2EECoordinator {
1271
1279
  }, `${ns}|tail|after=${afterSeq}|limit=${limit}`);
1272
1280
  const result = await client._callRawV2Rpc('message.v2.pull', { window_mode: 'tail', after_seq: afterSeq, limit, _rpc_foreground: true }, pullGateKey);
1273
1281
  const page = validateTailPage(result, afterSeq, limit);
1274
- if (ns) {
1275
- client._seqTracker.commitTailWindow(ns, {
1276
- windowStartSeq: page.windowStart, headSeq: page.head, coveredThroughSeq: page.head, pageLimit: limit,
1277
- });
1278
- client._persistSeq(ns);
1279
- await client._saveSeqTrackerState?.();
1280
- }
1281
1282
  const decoded = [];
1282
1283
  for (const msg of page.messages) {
1284
+ this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
1283
1285
  const plaintext = await this.decodeP2PWindowMessage(msg, true, false, 'tail', deferPublish);
1284
1286
  if (plaintext)
1285
1287
  decoded.push(plaintext);
1286
1288
  }
1289
+ this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
1290
+ if (ns) {
1291
+ const proof = {
1292
+ windowStartSeq: page.windowStart, headSeq: page.head, coveredThroughSeq: page.head, pageLimit: limit,
1293
+ };
1294
+ if (deferPublish)
1295
+ client._delivery.deferTailWindow(ns, proof);
1296
+ else {
1297
+ client._seqTracker.commitTailWindow(ns, proof);
1298
+ client._persistSeq(ns);
1299
+ await client._saveSeqTrackerState?.();
1300
+ this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
1301
+ }
1302
+ }
1287
1303
  const ack = ns ? client._seqTracker.getContiguousSeq(ns) : 0;
1288
1304
  if (ns)
1289
1305
  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);
@@ -1320,6 +1336,8 @@ export class V2E2EECoordinator {
1320
1336
  }
1321
1337
  async pullV2Impl(afterSeq = 0, limit = 50, opts) {
1322
1338
  const client = this.client;
1339
+ const generation = Number(client._v2RuntimeGeneration ?? 0);
1340
+ const inlineGeneration = client._delivery.captureInlineGeneration?.();
1323
1341
  await client._ensureV2SessionReady('message.pull');
1324
1342
  const ns = client._aid ? `p2p:${client._aid}` : '';
1325
1343
  let volatileCursor = String(opts?.afterVolatileCursor ?? client._delivery.volatilePullCursor() ?? '').trim();
@@ -1359,10 +1377,11 @@ export class V2E2EECoordinator {
1359
1377
  let pageCount = 0;
1360
1378
  let lastAutoAckSeq = 0;
1361
1379
  let pendingAckSeq = deferredForwardAck;
1380
+ let committedAckSeq = ns ? client._seqTracker.getContiguousSeq(ns) : 0;
1362
1381
  const maxPages = Math.max(1, Math.min(100, Math.trunc(Number(opts?.maxPages ?? 100) || 100)));
1363
1382
  let pipelineRunning = false;
1364
1383
  let pipelineError = null;
1365
- const pipelineQueue = new V2PullPageQueue(V2_PULL_PIPELINE_QUEUE_CAPACITY);
1384
+ let pipelineQueue = new V2PullPageQueue(V2_PULL_PIPELINE_QUEUE_CAPACITY);
1366
1385
  let pipelineProducer = null;
1367
1386
  let pipelineStopped = false;
1368
1387
  const startPipeline = (startAfterSeq, startVolatileCursor) => {
@@ -1375,21 +1394,27 @@ export class V2E2EECoordinator {
1375
1394
  for (let produced = pageCount; produced < maxPages; produced += 1) {
1376
1395
  if (pipelineStopped)
1377
1396
  break;
1397
+ this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
1378
1398
  const requestedVolatileCursor = producerVolatileCursor;
1399
+ const ackUpToSeq = Math.min(pendingAckSeq, committedAckSeq);
1400
+ const inlineAckGeneration = ackUpToSeq > 0
1401
+ ? client._delivery?.pendingP2PInlineAckGeneration?.(ns) ?? null : null;
1379
1402
  const result = await client._callRawV2Rpc('message.v2.pull', {
1380
1403
  after_seq: producerAfterSeq,
1381
1404
  limit,
1382
1405
  ...(producerVolatileCursor ? { after_volatile_cursor: producerVolatileCursor } : {}),
1383
1406
  ...(opts?.force ? { force: true } : {}),
1407
+ ...(ackUpToSeq > 0 ? { ack_up_to_seq: ackUpToSeq } : {}),
1384
1408
  }, pullGateKey);
1385
1409
  if (pipelineStopped)
1386
1410
  break;
1411
+ this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
1387
1412
  const rawPageMessages = (Array.isArray(result?.messages) ? result.messages : []);
1388
1413
  const enqueued = await pipelineQueue.put({
1389
1414
  result,
1390
1415
  afterSeq: producerAfterSeq,
1391
- ackUpToSeq: 0,
1392
- inlineAckGeneration: null,
1416
+ ackUpToSeq,
1417
+ inlineAckGeneration,
1393
1418
  requestedVolatileCursor,
1394
1419
  weight: rawPageMessages.length,
1395
1420
  }, rawPageMessages.length);
@@ -1480,21 +1505,24 @@ export class V2E2EECoordinator {
1480
1505
  continue;
1481
1506
  }
1482
1507
  }
1508
+ this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
1509
+ if (!opts?.force)
1510
+ client._delivery.observeBackgroundPullResponse?.(ns, nextAfterSeq, result, limit);
1483
1511
  if (ackUpToSeq > 0) {
1484
1512
  const actualAckSeq = client._delivery?.resolveP2PPullAckSeq?.(result, ackUpToSeq) ?? 0;
1485
1513
  if (actualAckSeq >= ackUpToSeq) {
1486
1514
  if (pendingInlineGeneration !== null) {
1487
1515
  client._delivery?.completePendingP2PInlineAckUpTo?.(ns, actualAckSeq, pendingInlineGeneration);
1488
1516
  }
1489
- pendingAckSeq = 0;
1517
+ if (pendingAckSeq <= ackUpToSeq)
1518
+ pendingAckSeq = 0;
1490
1519
  lastAutoAckSeq = Math.max(lastAutoAckSeq, ackUpToSeq);
1491
1520
  this.clearForwardAck(ns, ackUpToSeq);
1492
1521
  }
1493
1522
  else {
1494
1523
  client._clientLog.warn(`message.v2.pull piggyback ACK below target: requested=${ackUpToSeq}, actual=${actualAckSeq}; fallback terminal ACK`);
1495
- await this.confirmForwardP2PAck(ackUpToSeq, pendingInlineGeneration ?? undefined);
1496
- pendingAckSeq = 0;
1497
- lastAutoAckSeq = Math.max(lastAutoAckSeq, ackUpToSeq);
1524
+ pendingAckSeq = Math.max(pendingAckSeq, ackUpToSeq);
1525
+ this.recordForwardAck(ns, ackUpToSeq);
1498
1526
  }
1499
1527
  }
1500
1528
  const pageMessages = (Array.isArray(result?.messages) ? result.messages : []);
@@ -1514,11 +1542,16 @@ export class V2E2EECoordinator {
1514
1542
  .map((msg) => Number(msg.seq ?? 0))
1515
1543
  .filter((seq) => Number.isFinite(seq) && seq > 0);
1516
1544
  const pageContigBefore = ns ? client._seqTracker.getContiguousSeq(ns) : 0;
1517
- const pageTrackerSnapshot = ns && typeof client._seqTracker.snapshotNamespace === 'function'
1518
- ? client._seqTracker.snapshotNamespace(ns)
1519
- : null;
1545
+ let pageTrackerSnapshot;
1520
1546
  const pageMaxSeq = seqs.length > 0 ? Math.max(...seqs) : nextAfterSeq;
1521
1547
  const pageContiguousSeq = contiguousPullSeq(pageContigBefore, seqs, result.contiguous_seq);
1548
+ if (!pipelineRunning && volatileMessages.length === 0 && shouldContinueForwardPage({
1549
+ rawCount: messages.length, nextAfterSeq, nextAfter: pageMaxSeq, limit,
1550
+ hasMoreField, hasMore, serverHead, pendingAckSeq: 0,
1551
+ observedUpper: typeof client._seqTracker.getMaxSeenSeq === 'function'
1552
+ ? client._seqTracker.getMaxSeenSeq(ns) : pageMaxSeq,
1553
+ }))
1554
+ startPipeline(pageMaxSeq, volatileCursor);
1522
1555
  const deferredKeyFetches = new Map();
1523
1556
  let blockedSeq = 0;
1524
1557
  const pageDecryptedBefore = decrypted.length;
@@ -1622,10 +1655,18 @@ export class V2E2EECoordinator {
1622
1655
  const serverAckSeq = parsedServerAckSeq ?? 0;
1623
1656
  const retentionFloor = Math.max(0, Number(result.retention_floor_seq ?? 0));
1624
1657
  if (ns) {
1625
- const commitTarget = Math.max(pageContigBefore, Number.isFinite(retentionFloor) ? retentionFloor : 0, pageCount === 1 ? deferredServerCursor : 0, serverAckSeq, blockedSeq > 0 ? blockedSeq - 1 : pageContiguousSeq);
1658
+ let commitTarget = Math.max(pageContigBefore, Number.isFinite(retentionFloor) ? retentionFloor : 0, pageCount === 1 ? deferredServerCursor : 0, serverAckSeq, blockedSeq > 0 ? blockedSeq - 1 : pageContiguousSeq);
1659
+ commitTarget = client._delivery.pendingTailDeliveryThrough?.(ns, commitTarget) ?? commitTarget;
1626
1660
  let ackSeq = pageContigBefore;
1627
1661
  let contigAdvanced = false;
1628
1662
  try {
1663
+ this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
1664
+ const state = client._seqTracker.getSyncState?.(ns);
1665
+ const deliveryThrough = client._delivery.pendingTailDeliveryThrough?.(ns, state && commitTarget >= state.tail - 1 ? Math.max(commitTarget, state.head) : commitTarget) ?? (state && commitTarget >= state.tail - 1 ? Math.max(commitTarget, state.head) : commitTarget);
1666
+ await client._drainOrderedMessages(ns, undefined, false, false, deliveryThrough);
1667
+ this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
1668
+ pageTrackerSnapshot = client._seqTracker.snapshotNamespace?.(ns);
1669
+ client._delivery.commitPendingTailWindow?.(ns, commitTarget);
1629
1670
  if (commitTarget > pageContigBefore) {
1630
1671
  client._seqTracker.forceContiguousSeq(ns, commitTarget);
1631
1672
  if (retentionFloor > pageContigBefore) {
@@ -1642,19 +1683,24 @@ export class V2E2EECoordinator {
1642
1683
  contigAdvanced = ackSeq !== pageContigBefore;
1643
1684
  if (contigAdvanced) {
1644
1685
  await client._commitSeqTrackerState(ns);
1686
+ this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
1645
1687
  if (pageCount === 1 && deferredServerCursor > 0)
1646
1688
  this.clearForwardCursor(ns, ackSeq);
1647
1689
  }
1690
+ client._delivery.completePendingTailWindow?.(ns);
1691
+ committedAckSeq = ackSeq;
1648
1692
  }
1649
1693
  catch (exc) {
1650
- if (typeof client._seqTracker.restoreNamespaceSnapshot === 'function') {
1651
- client._seqTracker.restoreNamespaceSnapshot(ns, pageTrackerSnapshot);
1694
+ if (Number(client._v2RuntimeGeneration ?? 0) === generation
1695
+ && (inlineGeneration === undefined || client._delivery.isInlineGenerationCurrent(inlineGeneration))
1696
+ && client._rpcPipeline?.isPullNamespaceCurrent?.(ns) !== false
1697
+ && pageTrackerSnapshot !== undefined
1698
+ && typeof client._seqTracker.restoreNamespaceSnapshot === 'function') {
1699
+ client._seqTracker._restoreNamespaceSnapshotPreservingPush(ns, pageTrackerSnapshot);
1652
1700
  }
1653
1701
  client._clientLog.error?.(`message.v2.pull Forward commit rolled back: ns=${ns} target=${commitTarget} error=${formatE2EEError(exc)}`);
1654
1702
  throw exc;
1655
1703
  }
1656
- if (contigAdvanced)
1657
- await client._drainOrderedMessages(ns, undefined, false, false);
1658
1704
  for (const fetch of deferredKeyFetches.values()) {
1659
1705
  this.scheduleSenderIKFetch(fetch.fromAid, fetch.senderDeviceId, fetch.groupId);
1660
1706
  }
@@ -1672,16 +1718,7 @@ export class V2E2EECoordinator {
1672
1718
  || (contigAdvanced && ackSeq > knownServerAckSeq));
1673
1719
  if (ackNeeded) {
1674
1720
  this.recordForwardAck(ns, ackSeq);
1675
- const nextAfter = Math.max(pageContiguousSeq, nextAfterSeq);
1676
- const canContinuePage = blockedSeq <= 0 && messages.length > 0 && nextAfter > nextAfterSeq;
1677
- if (canContinuePage && !pipelineRunning) {
1678
- pendingAckSeq = Math.max(pendingAckSeq, ackSeq);
1679
- lastAutoAckSeq = Math.max(lastAutoAckSeq, ackSeq);
1680
- }
1681
- else {
1682
- await this.confirmForwardP2PAck(ackSeq);
1683
- lastAutoAckSeq = Math.max(lastAutoAckSeq, ackSeq);
1684
- }
1721
+ pendingAckSeq = Math.max(pendingAckSeq, ackSeq);
1685
1722
  }
1686
1723
  }
1687
1724
  if (ns)
@@ -1721,11 +1758,14 @@ export class V2E2EECoordinator {
1721
1758
  });
1722
1759
  if (!shouldContinue)
1723
1760
  break;
1761
+ if (pipelinePage && volatileMessages.length > 0) {
1762
+ await stopPipeline();
1763
+ this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
1764
+ pipelineQueue = new V2PullPageQueue(V2_PULL_PIPELINE_QUEUE_CAPACITY);
1765
+ pipelineStopped = false;
1766
+ pipelineError = null;
1767
+ }
1724
1768
  if (shouldContinueForPipeline && !volatileAdvanced && !pipelineRunning && limit >= V2_PULL_MAX_PAGE_LIMIT) {
1725
- if (pendingAckSeq > 0) {
1726
- await this.confirmForwardP2PAck(pendingAckSeq);
1727
- pendingAckSeq = 0;
1728
- }
1729
1769
  startPipeline(nextAfter, volatileCursor);
1730
1770
  }
1731
1771
  if (!pipelineRunning && pullGateKey && opts?.gateLocked && client._rpcPipeline?.yieldPullGate) {
@@ -1736,7 +1776,7 @@ export class V2E2EECoordinator {
1736
1776
  pullGateKey = nextKey;
1737
1777
  }
1738
1778
  const tailDelayMs = v2PullTailDelayMs(rawCount, limit);
1739
- if (tailDelayMs > 0)
1779
+ if (!pipelineRunning && tailDelayMs > 0)
1740
1780
  await v2Sleep(tailDelayMs);
1741
1781
  nextAfterSeq = nextAfter;
1742
1782
  }
@@ -1746,10 +1786,11 @@ export class V2E2EECoordinator {
1746
1786
  }
1747
1787
  if (pageCount >= maxPages) {
1748
1788
  client._clientLog.warn(`message.v2.pull reached max_pages=${maxPages} after_seq=${nextAfterSeq}`);
1749
- if (pendingAckSeq > 0) {
1750
- await this.confirmForwardP2PAck(pendingAckSeq);
1751
- pendingAckSeq = 0;
1752
- }
1789
+ }
1790
+ if (pendingAckSeq > 0) {
1791
+ this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
1792
+ await this.confirmForwardP2PAck(pendingAckSeq);
1793
+ pendingAckSeq = 0;
1753
1794
  }
1754
1795
  client._delivery.onPullWorkSettled?.();
1755
1796
  return deduplicatePulledResults(decrypted);
@@ -1823,8 +1864,11 @@ export class V2E2EECoordinator {
1823
1864
  }
1824
1865
  async confirmForwardP2PAck(upToSeq, inlineGeneration) {
1825
1866
  const ns = this.client._aid ? `p2p:${this.client._aid}` : '';
1867
+ const generation = Number(this.client._v2RuntimeGeneration ?? 0);
1868
+ const deliveryGeneration = this.client._delivery.captureInlineGeneration?.();
1826
1869
  try {
1827
1870
  const result = await this.ackV2ForForward(upToSeq, inlineGeneration);
1871
+ this.ensureDeliveryCurrent(ns, generation, deliveryGeneration);
1828
1872
  const actualAckSeq = this.client._delivery?.resolveForwardAckSeq?.(result, upToSeq) ?? 0;
1829
1873
  if (actualAckSeq < upToSeq) {
1830
1874
  throw new E2EEError(`message.v2.ack server ACK watermark ${actualAckSeq} is below requested ${upToSeq}`);
@@ -1832,7 +1876,10 @@ export class V2E2EECoordinator {
1832
1876
  this.clearForwardAck(ns, upToSeq);
1833
1877
  }
1834
1878
  catch (exc) {
1835
- this.recordForwardAck(ns, upToSeq);
1879
+ if (Number(this.client._v2RuntimeGeneration ?? 0) === generation
1880
+ && (deliveryGeneration === undefined || this.client._delivery.isInlineGenerationCurrent(deliveryGeneration))
1881
+ && this.client._rpcPipeline?.isPullNamespaceCurrent?.(ns) !== false)
1882
+ this.recordForwardAck(ns, upToSeq);
1836
1883
  throw exc;
1837
1884
  }
1838
1885
  }
@@ -1965,6 +2012,8 @@ export class V2E2EECoordinator {
1965
2012
  }
1966
2013
  async pullGroupV2TailInternal(params, deferPublish = false) {
1967
2014
  const client = this.client;
2015
+ const generation = Number(client._v2RuntimeGeneration ?? 0);
2016
+ const inlineGeneration = client._delivery.captureInlineGeneration?.();
1968
2017
  if (params.window_mode !== 'tail')
1969
2018
  throw new ValidationError('window_mode must equal tail');
1970
2019
  if ('ack_up_to_seq' in params || params.force === true)
@@ -2000,18 +2049,26 @@ export class V2E2EECoordinator {
2000
2049
  if (resultAid)
2001
2050
  groupAid = resultAid;
2002
2051
  const page = validateTailPage(result, afterSeq, limit);
2003
- client._seqTracker.commitTailWindow(ns, {
2004
- windowStartSeq: page.windowStart, headSeq: page.head, coveredThroughSeq: page.head, pageLimit: limit,
2005
- });
2006
- client._persistSeq(ns);
2007
- await client._saveSeqTrackerState?.();
2008
2052
  const decoded = [];
2009
2053
  for (const msg of page.messages) {
2054
+ this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
2010
2055
  client._logMessageDebug?.('tail-raw', 'group.v2.pull', 'group.message_created', msg);
2011
2056
  const plaintext = await this.decodeGroupWindowMessage(msg, groupId, groupAid, true, false, 'tail', deferPublish);
2012
2057
  if (plaintext)
2013
2058
  decoded.push(plaintext);
2014
2059
  }
2060
+ this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
2061
+ const proof = {
2062
+ windowStartSeq: page.windowStart, headSeq: page.head, coveredThroughSeq: page.head, pageLimit: limit,
2063
+ };
2064
+ if (deferPublish)
2065
+ client._delivery.deferTailWindow(ns, proof);
2066
+ else {
2067
+ client._seqTracker.commitTailWindow(ns, proof);
2068
+ client._persistSeq(ns);
2069
+ await client._saveSeqTrackerState?.();
2070
+ this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
2071
+ }
2015
2072
  const ack = client._seqTracker.getContiguousSeq(ns);
2016
2073
  if (ownsCursor)
2017
2074
  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);
@@ -2056,6 +2113,8 @@ export class V2E2EECoordinator {
2056
2113
  }
2057
2114
  async pullGroupV2Impl(groupId, afterSeq = 0, limit = 50, opts) {
2058
2115
  const client = this.client;
2116
+ const generation = Number(client._v2RuntimeGeneration ?? 0);
2117
+ const inlineGeneration = client._delivery.captureInlineGeneration?.();
2059
2118
  await client._ensureV2SessionReady('group.pull');
2060
2119
  const gid = String(groupId ?? '').trim();
2061
2120
  if (!gid)
@@ -2111,6 +2170,7 @@ export class V2E2EECoordinator {
2111
2170
  let pageCount = 0;
2112
2171
  let lastAutoAckSeq = 0;
2113
2172
  let pendingAckSeq = deferredForwardAck;
2173
+ let committedAckSeq = ownsCursor ? client._seqTracker.getContiguousSeq(ns) : 0;
2114
2174
  const maxPages = Math.max(1, Math.min(100, Math.trunc(Number(opts?.maxPages ?? 100) || 100)));
2115
2175
  let pipelineRunning = false;
2116
2176
  let pipelineError = null;
@@ -2126,21 +2186,27 @@ export class V2E2EECoordinator {
2126
2186
  for (let produced = pageCount; produced < maxPages; produced += 1) {
2127
2187
  if (pipelineStopped)
2128
2188
  break;
2189
+ this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
2190
+ const ackUpToSeq = Math.min(pendingAckSeq, committedAckSeq);
2191
+ const inlineAckGeneration = ackUpToSeq > 0
2192
+ ? client._delivery?.pendingGroupInlineAckGeneration?.(ns) ?? null : null;
2129
2193
  const result = await client._callRawV2Rpc('group.v2.pull', withExplicitGroupAid({
2130
2194
  group_id: wireGroupId,
2131
2195
  after_seq: producerAfterSeq,
2132
2196
  limit,
2133
2197
  ...cursorParams,
2134
2198
  ...(opts?.force ? { force: true } : {}),
2199
+ ...(ackUpToSeq > 0 ? { ack_up_to_seq: ackUpToSeq } : {}),
2135
2200
  }, groupAid), pullGateKey);
2136
2201
  if (pipelineStopped)
2137
2202
  break;
2203
+ this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
2138
2204
  const pageMessages = (Array.isArray(result?.messages) ? result.messages : []);
2139
2205
  const enqueued = await pipelineQueue.put({
2140
2206
  result,
2141
2207
  afterSeq: producerAfterSeq,
2142
- ackUpToSeq: 0,
2143
- inlineAckGeneration: null,
2208
+ ackUpToSeq,
2209
+ inlineAckGeneration,
2144
2210
  requestedVolatileCursor: '',
2145
2211
  weight: pageMessages.length,
2146
2212
  }, pageMessages.length);
@@ -2214,21 +2280,24 @@ export class V2E2EECoordinator {
2214
2280
  ...(ackUpToSeq > 0 ? { ack_up_to_seq: ackUpToSeq } : {}),
2215
2281
  }, groupAid), pullGateKey);
2216
2282
  }
2283
+ this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
2284
+ if (ownsCursor && !opts?.force)
2285
+ client._delivery.observeBackgroundPullResponse?.(ns, nextAfterSeq, result, limit);
2217
2286
  if (ackUpToSeq > 0) {
2218
2287
  const actualAckSeq = client._delivery?.resolveGroupPullAckSeq?.(result, ackUpToSeq) ?? 0;
2219
2288
  if (actualAckSeq >= ackUpToSeq) {
2220
2289
  if (pendingInlineGeneration !== null) {
2221
2290
  client._delivery?.completePendingGroupInlineAckUpTo?.(ns, actualAckSeq, pendingInlineGeneration);
2222
2291
  }
2223
- pendingAckSeq = 0;
2292
+ if (pendingAckSeq <= ackUpToSeq)
2293
+ pendingAckSeq = 0;
2224
2294
  lastAutoAckSeq = Math.max(lastAutoAckSeq, ackUpToSeq);
2225
2295
  this.clearForwardAck(ns, ackUpToSeq);
2226
2296
  }
2227
2297
  else {
2228
2298
  client._clientLog.warn(`group.v2.pull piggyback ACK below target: group=${gid}, requested=${ackUpToSeq}, actual=${actualAckSeq}; fallback terminal ACK`);
2229
- await this.confirmForwardGroupAck(gid, ackUpToSeq, groupAid);
2230
- pendingAckSeq = 0;
2231
- lastAutoAckSeq = Math.max(lastAutoAckSeq, ackUpToSeq);
2299
+ pendingAckSeq = Math.max(pendingAckSeq, ackUpToSeq);
2300
+ this.recordForwardAck(ns, ackUpToSeq);
2232
2301
  }
2233
2302
  }
2234
2303
  const resultGroupAid = String(result.group_aid ?? result.groupAid ?? '').trim();
@@ -2245,11 +2314,16 @@ export class V2E2EECoordinator {
2245
2314
  .map((msg) => Number(msg.seq ?? 0))
2246
2315
  .filter((seq) => Number.isFinite(seq) && seq > 0);
2247
2316
  const pageContigBefore = client._seqTracker.getContiguousSeq(ns);
2248
- const pageTrackerSnapshot = typeof client._seqTracker.snapshotNamespace === 'function'
2249
- ? client._seqTracker.snapshotNamespace(ns)
2250
- : null;
2317
+ let pageTrackerSnapshot;
2251
2318
  const pageMaxSeq = seqs.length > 0 ? Math.max(...seqs) : nextAfterSeq;
2252
2319
  const pageContiguousSeq = contiguousPullSeq(pageContigBefore, seqs, result.contiguous_seq);
2320
+ if (!pipelineRunning && shouldContinueForwardPage({
2321
+ rawCount: messages.length, nextAfterSeq, nextAfter: pageMaxSeq, limit,
2322
+ hasMoreField, hasMore, serverHead, pendingAckSeq: 0,
2323
+ observedUpper: typeof client._seqTracker.getMaxSeenSeq === 'function'
2324
+ ? client._seqTracker.getMaxSeenSeq(ns) : pageMaxSeq,
2325
+ }))
2326
+ startPipeline(pageMaxSeq);
2253
2327
  const deferredKeyFetches = new Map();
2254
2328
  let blockedSeq = 0;
2255
2329
  const pageDecryptedBefore = decrypted.length;
@@ -2383,11 +2457,19 @@ export class V2E2EECoordinator {
2383
2457
  const retentionFloor = Math.max(Number(result.retention_floor_message_seq ?? result.retention_floor_seq ?? 0), Number(cursor?.retention_floor_seq ?? 0));
2384
2458
  const visibilityFloor = Math.max(0, Math.max(0, Number(result.earliest_available_message_seq ?? 1) - 1), Number(cursor?.join_seq ?? 0));
2385
2459
  const effectiveFloor = Math.max(retentionFloor, visibilityFloor);
2386
- const commitTarget = Math.max(pageContigBefore, Number.isFinite(effectiveFloor) ? effectiveFloor : 0, ownsCursor && pageCount === 1 ? deferredServerCursor : 0, cursorCurrentSeq, blockedSeq > 0 ? blockedSeq - 1 : pageContiguousSeq);
2460
+ let commitTarget = Math.max(pageContigBefore, Number.isFinite(effectiveFloor) ? effectiveFloor : 0, ownsCursor && pageCount === 1 ? deferredServerCursor : 0, cursorCurrentSeq, blockedSeq > 0 ? blockedSeq - 1 : pageContiguousSeq);
2461
+ commitTarget = client._delivery.pendingTailDeliveryThrough?.(ns, commitTarget) ?? commitTarget;
2387
2462
  let ackSeq = pageContigBefore;
2388
2463
  let contigAdvanced = false;
2389
2464
  if (ownsCursor) {
2390
2465
  try {
2466
+ this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
2467
+ const state = client._seqTracker.getSyncState?.(ns);
2468
+ const deliveryThrough = client._delivery.pendingTailDeliveryThrough?.(ns, state && commitTarget >= state.tail - 1 ? Math.max(commitTarget, state.head) : commitTarget) ?? (state && commitTarget >= state.tail - 1 ? Math.max(commitTarget, state.head) : commitTarget);
2469
+ await client._drainOrderedMessages(ns, undefined, false, false, deliveryThrough);
2470
+ this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
2471
+ pageTrackerSnapshot = client._seqTracker.snapshotNamespace?.(ns);
2472
+ client._delivery.commitPendingTailWindow?.(ns, commitTarget);
2391
2473
  if (commitTarget > pageContigBefore) {
2392
2474
  client._seqTracker.forceContiguousSeq(ns, commitTarget);
2393
2475
  if (effectiveFloor > pageContigBefore) {
@@ -2404,19 +2486,24 @@ export class V2E2EECoordinator {
2404
2486
  contigAdvanced = ackSeq !== pageContigBefore;
2405
2487
  if (contigAdvanced) {
2406
2488
  await client._commitSeqTrackerState(ns);
2489
+ this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
2407
2490
  if (pageCount === 1 && deferredServerCursor > 0)
2408
2491
  this.clearForwardCursor(ns, ackSeq);
2409
2492
  }
2493
+ client._delivery.completePendingTailWindow?.(ns);
2494
+ committedAckSeq = ackSeq;
2410
2495
  }
2411
2496
  catch (exc) {
2412
- if (typeof client._seqTracker.restoreNamespaceSnapshot === 'function') {
2413
- client._seqTracker.restoreNamespaceSnapshot(ns, pageTrackerSnapshot);
2497
+ if (Number(client._v2RuntimeGeneration ?? 0) === generation
2498
+ && (inlineGeneration === undefined || client._delivery.isInlineGenerationCurrent(inlineGeneration))
2499
+ && client._rpcPipeline?.isPullNamespaceCurrent?.(ns) !== false
2500
+ && pageTrackerSnapshot !== undefined
2501
+ && typeof client._seqTracker.restoreNamespaceSnapshot === 'function') {
2502
+ client._seqTracker._restoreNamespaceSnapshotPreservingPush(ns, pageTrackerSnapshot);
2414
2503
  }
2415
2504
  client._clientLog.error?.(`group.v2.pull Forward commit rolled back: group=${gid} ns=${ns} target=${commitTarget} error=${formatE2EEError(exc)}`);
2416
2505
  throw exc;
2417
2506
  }
2418
- if (contigAdvanced)
2419
- await client._drainOrderedMessages(ns, undefined, false, false);
2420
2507
  }
2421
2508
  for (const fetch of deferredKeyFetches.values()) {
2422
2509
  this.scheduleSenderIKFetch(fetch.fromAid, fetch.senderDeviceId, fetch.groupId);
@@ -2437,16 +2524,7 @@ export class V2E2EECoordinator {
2437
2524
  || (contigAdvanced && ackSeq > knownServerCursorSeq));
2438
2525
  if (ackNeeded) {
2439
2526
  this.recordForwardAck(ns, ackSeq);
2440
- const nextAfter = Math.max(pageContiguousSeq, nextAfterSeq);
2441
- const canContinuePage = blockedSeq <= 0 && messages.length > 0 && nextAfter > nextAfterSeq && ownsCursor;
2442
- if (canContinuePage && !pipelineRunning) {
2443
- pendingAckSeq = Math.max(pendingAckSeq, ackSeq);
2444
- lastAutoAckSeq = Math.max(lastAutoAckSeq, ackSeq);
2445
- }
2446
- else {
2447
- await this.confirmForwardGroupAck(gid, ackSeq, groupAid);
2448
- lastAutoAckSeq = Math.max(lastAutoAckSeq, ackSeq);
2449
- }
2527
+ pendingAckSeq = Math.max(pendingAckSeq, ackSeq);
2450
2528
  }
2451
2529
  const nextAfter = Math.max(pageContiguousSeq, nextAfterSeq);
2452
2530
  if (!ownsCursor)
@@ -2481,10 +2559,6 @@ export class V2E2EECoordinator {
2481
2559
  if (!shouldContinue)
2482
2560
  break;
2483
2561
  if (shouldContinueForPipeline && !pipelineRunning && limit >= V2_PULL_MAX_PAGE_LIMIT) {
2484
- if (pendingAckSeq > 0) {
2485
- await this.confirmForwardGroupAck(gid, pendingAckSeq, groupAid);
2486
- pendingAckSeq = 0;
2487
- }
2488
2562
  startPipeline(nextAfter);
2489
2563
  }
2490
2564
  if (!pipelineRunning && pullGateKey && opts?.gateLocked && client._rpcPipeline?.yieldPullGate) {
@@ -2496,7 +2570,7 @@ export class V2E2EECoordinator {
2496
2570
  pullGateKey = nextKey;
2497
2571
  }
2498
2572
  const tailDelayMs = v2PullTailDelayMs(rawCount, limit);
2499
- if (tailDelayMs > 0)
2573
+ if (!pipelineRunning && tailDelayMs > 0)
2500
2574
  await v2Sleep(tailDelayMs);
2501
2575
  nextAfterSeq = nextAfter;
2502
2576
  }
@@ -2506,18 +2580,22 @@ export class V2E2EECoordinator {
2506
2580
  }
2507
2581
  if (pageCount >= maxPages) {
2508
2582
  client._clientLog.warn(`group.v2.pull reached max_pages=${maxPages} group=${gid} after_seq=${nextAfterSeq}`);
2509
- if (pendingAckSeq > 0) {
2510
- await this.confirmForwardGroupAck(gid, pendingAckSeq, groupAid);
2511
- pendingAckSeq = 0;
2512
- }
2583
+ }
2584
+ if (pendingAckSeq > 0) {
2585
+ this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
2586
+ await this.confirmForwardGroupAck(gid, pendingAckSeq, groupAid);
2587
+ pendingAckSeq = 0;
2513
2588
  }
2514
2589
  client._delivery.onPullWorkSettled?.();
2515
2590
  return deduplicatePulledResults(decrypted);
2516
2591
  }
2517
2592
  async confirmForwardGroupAck(groupId, upToSeq, groupAid) {
2518
2593
  const ns = `group:${String(groupId ?? '').trim()}`;
2594
+ const generation = Number(this.client._v2RuntimeGeneration ?? 0);
2595
+ const deliveryGeneration = this.client._delivery.captureInlineGeneration?.();
2519
2596
  try {
2520
2597
  const result = await this.ackGroupV2ForForward(groupId, upToSeq, groupAid);
2598
+ this.ensureDeliveryCurrent(ns, generation, deliveryGeneration);
2521
2599
  const actualAckSeq = this.client._delivery?.resolveForwardAckSeq?.(result, upToSeq) ?? 0;
2522
2600
  if (actualAckSeq < upToSeq) {
2523
2601
  throw new E2EEError(`group.v2.ack server ACK watermark ${actualAckSeq} is below requested ${upToSeq}`);
@@ -2525,7 +2603,10 @@ export class V2E2EECoordinator {
2525
2603
  this.clearForwardAck(ns, upToSeq);
2526
2604
  }
2527
2605
  catch (exc) {
2528
- this.recordForwardAck(ns, upToSeq);
2606
+ if (Number(this.client._v2RuntimeGeneration ?? 0) === generation
2607
+ && (deliveryGeneration === undefined || this.client._delivery.isInlineGenerationCurrent(deliveryGeneration))
2608
+ && this.client._rpcPipeline?.isPullNamespaceCurrent?.(ns) !== false)
2609
+ this.recordForwardAck(ns, upToSeq);
2529
2610
  throw exc;
2530
2611
  }
2531
2612
  }
@@ -3108,11 +3189,19 @@ export class V2E2EECoordinator {
3108
3189
  });
3109
3190
  }
3110
3191
  }
3192
+ const targetDeviceId = String(opts.deviceId ?? '').trim();
3111
3193
  if (peerDevices.length === 0) {
3194
+ if (targetDeviceId && useCache) {
3195
+ this.deleteBootstrapCacheEntry(to);
3196
+ return this.buildV2P2PEnvelope({ ...opts, useCache: false });
3197
+ }
3112
3198
  throw new E2EEError(`V2 bootstrap: no devices found for ${to}`);
3113
3199
  }
3114
- const targetDeviceId = String(opts.deviceId ?? '').trim();
3115
3200
  if (targetDeviceId && !peerDevices.some((dev) => String(dev.device_id ?? '').trim() === targetDeviceId)) {
3201
+ if (useCache) {
3202
+ this.deleteBootstrapCacheEntry(to);
3203
+ return this.buildV2P2PEnvelope({ ...opts, useCache: false });
3204
+ }
3116
3205
  throw new E2EEError(`V2 bootstrap: target device ${targetDeviceId} not found for ${to}`);
3117
3206
  }
3118
3207
  let targetSet = useCache ? this.getTargetSetCache(to) : undefined;
@@ -3203,6 +3292,10 @@ export class V2E2EECoordinator {
3203
3292
  return isPeerTarget || target.role === 'self_sync';
3204
3293
  });
3205
3294
  if (!peerTargetFound) {
3295
+ if (useCache) {
3296
+ this.deleteBootstrapCacheEntry(to);
3297
+ return this.buildV2P2PEnvelope({ ...opts, useCache: false });
3298
+ }
3206
3299
  throw new E2EEError(`V2 bootstrap: target device ${targetDeviceId} not found for ${to}`);
3207
3300
  }
3208
3301
  }