@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
@@ -9,6 +9,7 @@ const MESSAGE_RECALL_SEEN_LIMIT = 10_000;
9
9
  const GROUP_RECALL_SEEN_LIMIT = 10_000;
10
10
  const VOLATILE_SEEN_LIMIT = 10_000;
11
11
  const SEQ_TRACKER_PERSIST_FLUSH_DELAY_MS = 200;
12
+ const BACKGROUND_PULL_RETRY_DELAY_MS = 250;
12
13
  const DELIVERY_CHANGED_MESSAGE_EVENTS = new Set([
13
14
  'message.received',
14
15
  'group.message_created',
@@ -80,6 +81,11 @@ export class MessageDeliveryEngine {
80
81
  pendingGroupPullUpper = null;
81
82
  onlineUnreadHintTargets = null;
82
83
  onlineUnreadHintTasks = null;
84
+ backgroundPullNeeds = new Map();
85
+ backgroundPullTasks = new Set();
86
+ backgroundPullCheckQueued = false;
87
+ backgroundPullTimer = null;
88
+ backgroundPullTimerAt = 0;
83
89
  pendingP2PInlineAcks = null;
84
90
  pendingGroupInlineAcks = null;
85
91
  pendingGroupInlineAckNamespaces = null;
@@ -175,9 +181,8 @@ export class MessageDeliveryEngine {
175
181
  const pageInlineGeneration = this.captureInlineAckGeneration();
176
182
  const deferredServerCursor = Number(coordinator.pendingForwardCursor(ns, pageContigBefore) ?? 0);
177
183
  const deferredForwardAck = Number(coordinator.pendingForwardAck(ns, pageContigBefore) ?? 0);
178
- const pageTrackerSnapshot = typeof client._seqTracker.snapshotNamespace === 'function'
179
- ? client._seqTracker.snapshotNamespace(ns)
180
- : null;
184
+ let pageTrackerSnapshot = null;
185
+ let trackerMutated = false;
181
186
  response._contig_before = pageContigBefore;
182
187
  const cursor = isJsonObject(response.cursor)
183
188
  ? response.cursor
@@ -225,6 +230,18 @@ export class MessageDeliveryEngine {
225
230
  }
226
231
  }
227
232
  this.ensurePullOperationCurrent(ns);
233
+ if (pageInlineGeneration !== this.captureInlineAckGeneration())
234
+ throw new Error('pull invalidated');
235
+ const previousWindow = client._seqTracker.getSyncState(ns);
236
+ const scanTarget = Math.max(pageContigBefore, ...messages.map((message) => positiveSafeSequenceHint(message.seq)), positiveSafeSequenceHint(response.contiguous_seq), retentionFloor, visibilityFloor, deferredServerCursor);
237
+ const deliveryTarget = scanTarget >= previousWindow.tail - 1
238
+ ? Math.max(scanTarget, previousWindow.head) : scanTarget;
239
+ await this.drainOrderedMessages(ns, undefined, false, false, 'pull', undefined, deliveryTarget);
240
+ this.ensurePullOperationCurrent(ns);
241
+ if (pageInlineGeneration !== this.captureInlineAckGeneration())
242
+ throw new Error('pull invalidated');
243
+ pageTrackerSnapshot = client._seqTracker.snapshotNamespace?.(ns) ?? null;
244
+ trackerMutated = true;
228
245
  client._seqTracker.onPullResult(ns, messages, afterSeq, Number.isSafeInteger(result.contiguous_seq)
229
246
  ? result.contiguous_seq
230
247
  : undefined);
@@ -233,16 +250,19 @@ export class MessageDeliveryEngine {
233
250
  client._seqTracker.forceContiguousSeq(ns, commitTarget);
234
251
  }
235
252
  if (client._seqTracker.getContiguousSeq(ns) !== pageContigBefore) {
236
- this.ensurePullOperationCurrent(ns);
237
- await this.drainOrderedMessages(ns, undefined, false, false, 'pull');
238
253
  this.ensurePullOperationCurrent(ns);
239
254
  await client._commitSeqTrackerState(ns);
255
+ this.ensurePullOperationCurrent(ns);
256
+ if (pageInlineGeneration !== this.captureInlineAckGeneration())
257
+ throw new Error('pull invalidated');
240
258
  }
241
259
  committed = true;
242
260
  }
243
261
  catch (exc) {
244
- if (this.isPullOperationCurrent(ns) && !committed && typeof client._seqTracker.restoreNamespaceSnapshot === 'function') {
245
- client._seqTracker.restoreNamespaceSnapshot(ns, pageTrackerSnapshot);
262
+ if (trackerMutated && pageInlineGeneration === this.captureInlineAckGeneration() && this.isPullOperationCurrent(ns)
263
+ && !committed && typeof client._seqTracker.restoreNamespaceSnapshot === 'function') {
264
+ (client._seqTracker._restoreNamespaceSnapshotPreservingPush ?? client._seqTracker.restoreNamespaceSnapshot)
265
+ .call(client._seqTracker, ns, pageTrackerSnapshot);
246
266
  this.dropSeqTrackerPending(ns);
247
267
  }
248
268
  throw exc;
@@ -408,6 +428,13 @@ export class MessageDeliveryEngine {
408
428
  this.deliveryChangedGeneration += 1;
409
429
  this.pendingDeliveryChanges = null;
410
430
  this.inlineAckGeneration += 1;
431
+ if (this.backgroundPullTimer)
432
+ clearTimeout(this.backgroundPullTimer);
433
+ this.backgroundPullTimer = null;
434
+ this.backgroundPullTimerAt = 0;
435
+ this.backgroundPullNeeds.clear();
436
+ this.backgroundPullTasks.clear();
437
+ this.backgroundPullCheckQueued = false;
411
438
  void this.runtime.client._rpcPipeline?.invalidatePulls?.();
412
439
  this.pendingP2PInlineAcks = null;
413
440
  this.pendingGroupInlineAcks = null;
@@ -679,6 +706,7 @@ export class MessageDeliveryEngine {
679
706
  }
680
707
  endRealtimeSync(ns) {
681
708
  this.realtimeSyncing?.delete(ns);
709
+ this.requestBackgroundPullRecovery();
682
710
  this.runtime.client._rpcPipeline?.requestPullDrainedCheck?.();
683
711
  }
684
712
  hasPendingPull(ns) {
@@ -689,6 +717,7 @@ export class MessageDeliveryEngine {
689
717
  }
690
718
  hasAnyPendingPull() {
691
719
  return [...(this.pendingGroupPullUpper?.values() ?? [])].some((seq) => seq > 0)
720
+ || this.backgroundPullNeeds.size > 0
692
721
  || Boolean(this.onlineUnreadHintTasks?.size)
693
722
  || Boolean(this.realtimeSyncing?.size)
694
723
  || Boolean(this.runtime.client._v2E2EE?.hasPendingSenderIKWork?.());
@@ -696,12 +725,136 @@ export class MessageDeliveryEngine {
696
725
  onPullGateIdle(ns) {
697
726
  if (!ns)
698
727
  return;
728
+ this.requestBackgroundPullRecovery();
699
729
  if (ns.startsWith('p2p:')) {
700
730
  this.runtime.client._schedulePendingP2pPullIfNeeded?.(ns, 'pull-gate-idle');
701
731
  return;
702
732
  }
703
733
  this.schedulePendingGroupPullIfNeeded(ns, 'pull-gate-idle');
704
734
  }
735
+ recordBackgroundPullNeed(ns, throughSeq = 0, probe = false) {
736
+ const client = this.runtime.client;
737
+ if (client._sessionOptions?.background_sync === false || !client._aid
738
+ || !(ns === `p2p:${client._aid}` || ns.startsWith('group:') || ns.startsWith('group_event:')))
739
+ return;
740
+ const need = this.backgroundPullNeeds.get(ns) ?? {
741
+ throughSeq: 0, hasMore: false, probe, lastAfter: -1, retryAt: 0,
742
+ };
743
+ need.throughSeq = Math.max(need.throughSeq, throughSeq);
744
+ need.probe ||= probe;
745
+ this.backgroundPullNeeds.set(ns, need);
746
+ }
747
+ observeBackgroundPullResponse(ns, afterSeq, result, limit = 50) {
748
+ if (!isJsonObject(result))
749
+ return;
750
+ const response = result;
751
+ const rows = ns.startsWith('group_event:') ? response.events : response.messages;
752
+ if (!Array.isArray(rows))
753
+ return;
754
+ const cursor = isJsonObject(response.cursor)
755
+ ? response.cursor : null;
756
+ const throughSeq = Math.max(positiveSafeSequenceHint(cursor?.latest_seq), positiveSafeSequenceHint(response.latest_seq), positiveSafeSequenceHint(response.latest_message_seq), positiveSafeSequenceHint(response.latest_event_seq), ...rows.map((row) => row && typeof row === 'object' && row.storage !== 'volatile'
757
+ ? positiveSafeSequenceHint(row.seq ?? row.event_seq) : 0));
758
+ this.recordBackgroundPullNeed(ns, throughSeq);
759
+ const need = this.backgroundPullNeeds.get(ns);
760
+ if (!need || afterSeq < need.lastAfter)
761
+ return;
762
+ need.lastAfter = afterSeq;
763
+ need.hasMore = response.has_more === true || positiveSafeSequenceHint(response.remaining) > 0
764
+ || (!('has_more' in response) && limit > 0 && rows.length >= limit);
765
+ need.probe = false;
766
+ }
767
+ finishBackgroundPull(ns, generation, afterSeq, failed) {
768
+ if (generation !== this.captureInlineAckGeneration())
769
+ return;
770
+ const need = this.backgroundPullNeeds.get(ns);
771
+ if (need) {
772
+ if (failed)
773
+ need.probe = true;
774
+ else
775
+ need.probe = false;
776
+ need.retryAt = failed || this.syncState(ns).ack <= afterSeq
777
+ ? Date.now() + BACKGROUND_PULL_RETRY_DELAY_MS : 0;
778
+ }
779
+ this.requestBackgroundPullRecovery();
780
+ }
781
+ requestBackgroundPullRecovery() {
782
+ if (this.backgroundPullCheckQueued || this.backgroundPullNeeds.size === 0)
783
+ return;
784
+ this.backgroundPullCheckQueued = true;
785
+ const generation = this.captureInlineAckGeneration();
786
+ queueMicrotask(() => {
787
+ this.backgroundPullCheckQueued = false;
788
+ if (generation !== this.captureInlineAckGeneration())
789
+ return;
790
+ const client = this.runtime.client;
791
+ if (client._sessionOptions?.background_sync === false || client.state !== ConnectionState.READY || client._closing)
792
+ return;
793
+ let retryAt = Infinity;
794
+ for (const [ns, need] of this.backgroundPullNeeds) {
795
+ if (this.backgroundPullTasks.has(ns) || this.realtimeSyncing?.has(ns) || this.onlineUnreadHintTasks?.has(ns))
796
+ continue;
797
+ const eventScope = ns.startsWith('group_event:');
798
+ const filling = [...(client._gapFillDone?.keys?.() ?? [])].some((key) => eventScope
799
+ ? String(key) === `group_event_pull:${ns}`
800
+ : ns.startsWith('p2p:') ? String(key).startsWith('p2p:') : String(key).startsWith(`group_msg:${ns.slice(6)}:`));
801
+ if (filling || (!eventScope && client._rpcPipeline?.hasPullActivity?.(ns, false))
802
+ || (eventScope && ['pulling', 'processing', 'pulled'].includes(client._rpcPipeline?.getPullLifecycle?.(ns))))
803
+ continue;
804
+ if (this.syncState(ns).ack >= need.throughSeq && !need.hasMore && !need.probe) {
805
+ this.backgroundPullNeeds.delete(ns);
806
+ continue;
807
+ }
808
+ if (need.retryAt > Date.now()) {
809
+ retryAt = Math.min(retryAt, need.retryAt);
810
+ continue;
811
+ }
812
+ this.backgroundPullTasks.add(ns);
813
+ const task = Promise.resolve().then(() => {
814
+ if (generation !== this.captureInlineAckGeneration())
815
+ return;
816
+ return eventScope ? this.fillGroupEventGap(ns.slice('group_event:'.length))
817
+ : ns.startsWith('group:') ? this.fillGroupGap(ns.slice(6)) : this.fillP2pGap();
818
+ }).finally(() => {
819
+ if (generation !== this.captureInlineAckGeneration())
820
+ return;
821
+ this.backgroundPullTasks.delete(ns);
822
+ this.requestBackgroundPullRecovery();
823
+ });
824
+ client._safeAsync(task);
825
+ }
826
+ if (this.backgroundPullNeeds.size === 0) {
827
+ if (this.backgroundPullTimer)
828
+ clearTimeout(this.backgroundPullTimer);
829
+ this.backgroundPullTimer = null;
830
+ this.backgroundPullTimerAt = 0;
831
+ client._rpcPipeline?.requestPullDrainedCheck?.();
832
+ }
833
+ if (Number.isFinite(retryAt) && (!this.backgroundPullTimer || retryAt < this.backgroundPullTimerAt)) {
834
+ if (this.backgroundPullTimer)
835
+ clearTimeout(this.backgroundPullTimer);
836
+ this.backgroundPullTimerAt = retryAt;
837
+ this.backgroundPullTimer = setTimeout(() => {
838
+ this.backgroundPullTimer = null;
839
+ this.backgroundPullTimerAt = 0;
840
+ this.requestBackgroundPullRecovery();
841
+ }, Math.max(0, retryAt - Date.now()));
842
+ this.backgroundPullTimer.unref?.();
843
+ }
844
+ });
845
+ }
846
+ onForegroundPullsDrained() {
847
+ const client = this.runtime.client;
848
+ if (!client._aid || client._sessionOptions?.background_sync === false
849
+ || client.state !== ConnectionState.READY || client._closing)
850
+ return;
851
+ const ns = `p2p:${client._aid}`;
852
+ const state = this.syncState(ns);
853
+ const maxSeen = client._seqTracker.getMaxSeenSeq(ns);
854
+ if (maxSeen <= state.ack && state.ack >= state.tail - 1)
855
+ return;
856
+ client._safeAsync(this.fillP2pGap());
857
+ }
705
858
  recordPendingGroupPull(ns, seq) {
706
859
  if (!ns || !Number.isSafeInteger(seq) || seq <= 0)
707
860
  return;
@@ -792,12 +945,12 @@ export class MessageDeliveryEngine {
792
945
  return;
793
946
  }
794
947
  if (pullResponse) {
795
- const published = client._withPullResponseProcessing(ns, () => this.publishAppEvent(event, payload, source, deliveryContext));
948
+ const published = client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload, source, deliveryContext));
796
949
  if (isPromiseLike(published))
797
950
  await published;
798
951
  }
799
952
  else {
800
- const published = this.publishAppEvent(event, payload, source, deliveryContext);
953
+ const published = client._publishAppEvent(event, payload, source, deliveryContext);
801
954
  if (isPromiseLike(published))
802
955
  await published;
803
956
  }
@@ -806,6 +959,12 @@ export class MessageDeliveryEngine {
806
959
  }
807
960
  async publishOrderedGroupChanged(payload, source = 'ordered') {
808
961
  const client = this.runtime.client;
962
+ const generation = this.captureInlineAckGeneration();
963
+ const published = client._publishAppEvent('group.changed', payload, source);
964
+ if (isPromiseLike(published))
965
+ await published;
966
+ if (generation !== this.captureInlineAckGeneration())
967
+ throw new Error('pull invalidated');
809
968
  if (isJsonObject(payload)) {
810
969
  const eventPayload = payload;
811
970
  client._groupState?.handleGroupChangedV2Membership?.(eventPayload);
@@ -816,9 +975,6 @@ export class MessageDeliveryEngine {
816
975
  client._cleanupDissolvedGroup?.(groupId);
817
976
  }
818
977
  }
819
- const published = client._publishAppEvent('group.changed', payload, source);
820
- if (isPromiseLike(published))
821
- await published;
822
978
  }
823
979
  isInstanceScopedMessageEvent(event) {
824
980
  return event === 'message.received'
@@ -1131,6 +1287,7 @@ export class MessageDeliveryEngine {
1131
1287
  }
1132
1288
  async publishMessageRecallTombstone(seq, message, source = 'push') {
1133
1289
  const client = this.runtime.client;
1290
+ const generation = this.captureInlineAckGeneration();
1134
1291
  const eventPayload = this.recallEventFromMessage(message);
1135
1292
  if (!eventPayload)
1136
1293
  return false;
@@ -1144,13 +1301,17 @@ export class MessageDeliveryEngine {
1144
1301
  client._clientLog.debug(`message.recalled dedup suppressed: seq=${String(seq)} key=${dedupKey}`);
1145
1302
  return false;
1146
1303
  }
1304
+ await client._publishAppEvent('message.recalled', eventPayload, source);
1305
+ if (generation !== this.captureInlineAckGeneration())
1306
+ throw new Error('pull invalidated');
1307
+ if (this.isPullScopedSource(source))
1308
+ this.ensurePullOperationCurrent(`p2p:${client._aid ?? ''}`);
1147
1309
  seen.set(dedupKey, Date.now());
1148
1310
  if (seen.size > MESSAGE_RECALL_SEEN_LIMIT) {
1149
1311
  const drop = [...seen.entries()].sort((a, b) => a[1] - b[1]).slice(0, seen.size - MESSAGE_RECALL_SEEN_LIMIT);
1150
1312
  for (const [oldKey] of drop)
1151
1313
  seen.delete(oldKey);
1152
1314
  }
1153
- await client._publishAppEvent('message.recalled', eventPayload, source);
1154
1315
  client._clientLog.debug(`message.recalled published: seq=${String(seq)} ids=${JSON.stringify(eventPayload.message_ids)}`);
1155
1316
  return true;
1156
1317
  }
@@ -1235,6 +1396,7 @@ export class MessageDeliveryEngine {
1235
1396
  }
1236
1397
  async publishGroupRecallTombstone(groupId, seq, message, source = 'push') {
1237
1398
  const client = this.runtime.client;
1399
+ const generation = this.captureInlineAckGeneration();
1238
1400
  const eventPayload = this.recallEventFromGroupMessage(message);
1239
1401
  if (!eventPayload)
1240
1402
  return false;
@@ -1250,13 +1412,17 @@ export class MessageDeliveryEngine {
1250
1412
  client._clientLog.debug(`group.message_recalled dedup suppressed: group=${groupId} seq=${String(seq)} key=${dedupKey}`);
1251
1413
  return false;
1252
1414
  }
1415
+ await client._publishAppEvent('group.message_recalled', eventPayload, source);
1416
+ if (generation !== this.captureInlineAckGeneration())
1417
+ throw new Error('pull invalidated');
1418
+ if (this.isPullScopedSource(source))
1419
+ this.ensurePullOperationCurrent(`group:${groupId}`);
1253
1420
  seen.set(dedupKey, Date.now());
1254
1421
  if (seen.size > GROUP_RECALL_SEEN_LIMIT) {
1255
1422
  const drop = [...seen.entries()].sort((a, b) => a[1] - b[1]).slice(0, seen.size - GROUP_RECALL_SEEN_LIMIT);
1256
1423
  for (const [oldKey] of drop)
1257
1424
  seen.delete(oldKey);
1258
1425
  }
1259
- await client._publishAppEvent('group.message_recalled', eventPayload, source);
1260
1426
  client._clientLog.debug(`group.message_recalled published: group=${groupId} seq=${String(seq)} ids=${JSON.stringify(eventPayload.message_ids)}`);
1261
1427
  return true;
1262
1428
  }
@@ -1301,6 +1467,7 @@ export class MessageDeliveryEngine {
1301
1467
  if (seqNum > client._seqTracker.getContiguousSeq(ns) && client._sessionOptions?.background_sync !== false) {
1302
1468
  this.fillGroupGap(groupId).catch((exc) => client._clientLog.warn(`background recall gap fill failed: ${formatDeliveryError(exc)}`));
1303
1469
  }
1470
+ await this.publishGroupRecallTombstone(groupId, seq, wrapped);
1304
1471
  }
1305
1472
  publishAppEvent(event, payload, source = 'direct', deliveryContext) {
1306
1473
  const client = this.runtime.client;
@@ -1325,11 +1492,14 @@ export class MessageDeliveryEngine {
1325
1492
  const generation = this.deliveryChangedGeneration;
1326
1493
  const normalized = this.normalizePublishedMessagePayload(event, payload, source);
1327
1494
  const dispatcher = client._dispatcher;
1328
- const result = typeof dispatcher.enqueue === 'function'
1329
- ? (dispatcher.enqueue(event, normalized), undefined)
1330
- : typeof dispatcher.publishSyncAware === 'function'
1331
- ? dispatcher.publishSyncAware(event, normalized)
1332
- : dispatcher.publish?.(event, normalized);
1495
+ const result = ['message.received', 'group.message_created', 'message.recalled', 'group.message_recalled', 'group.changed'].includes(event)
1496
+ && typeof dispatcher._publishAndWait === 'function'
1497
+ ? dispatcher._publishAndWait(event, normalized)
1498
+ : typeof dispatcher.enqueue === 'function'
1499
+ ? (dispatcher.enqueue(event, normalized), undefined)
1500
+ : typeof dispatcher.publishSyncAware === 'function'
1501
+ ? dispatcher.publishSyncAware(event, normalized)
1502
+ : dispatcher.publish?.(event, normalized);
1333
1503
  const note = () => {
1334
1504
  if (generation === this.deliveryChangedGeneration) {
1335
1505
  this.noteDeliveryChanged(event, payload, source, deliveryContext);
@@ -1583,7 +1753,6 @@ export class MessageDeliveryEngine {
1583
1753
  if (Number(seq) > client._seqTracker.getContiguousSeq(ns) && client._sessionOptions?.background_sync !== false) {
1584
1754
  this.fillP2pGap().catch((exc) => client._clientLog.warn(`background recall gap fill failed: ${formatDeliveryError(exc)}`));
1585
1755
  }
1586
- return;
1587
1756
  }
1588
1757
  await this.publishMessageRecallTombstone(seq, msg);
1589
1758
  }
@@ -1784,12 +1953,15 @@ export class MessageDeliveryEngine {
1784
1953
  return;
1785
1954
  const ns = `p2p:${client._aid}`;
1786
1955
  const afterSeq = client._seqTracker.getContiguousSeq(ns);
1956
+ const generation = this.captureInlineAckGeneration();
1787
1957
  const dedupKey = `p2p:${afterSeq}`;
1788
1958
  if (client._gapFillDone.has(dedupKey))
1789
1959
  return;
1790
1960
  client._gapFillDone.set(dedupKey, Date.now());
1961
+ this.recordBackgroundPullNeed(ns, 0, true);
1791
1962
  client._clientLog.debug(`P2P message gap fill start: after_seq=${afterSeq}`);
1792
1963
  let filled = 0;
1964
+ let failed = false;
1793
1965
  try {
1794
1966
  // Gap Fill 本身就是 Forward Pull,必须沿用协调器的完整事务:
1795
1967
  // 原始页提交成功后由协调器负责 Piggyback/终端 ACK。
@@ -1802,11 +1974,15 @@ export class MessageDeliveryEngine {
1802
1974
  client._clientLog.debug(`P2P message gap fill done: after_seq=${afterSeq}, filled=${filled}`);
1803
1975
  }
1804
1976
  catch (exc) {
1977
+ failed = true;
1805
1978
  client._clientLog.warn(`P2P message gap fill failed: ${formatDeliveryError(exc)}`);
1806
1979
  }
1807
1980
  finally {
1981
+ if (generation !== this.captureInlineAckGeneration())
1982
+ return;
1808
1983
  client._gapFillDone.delete(dedupKey);
1809
1984
  client._schedulePendingP2pPullIfNeeded?.(ns, 'gap-fill-complete');
1985
+ this.finishBackgroundPull(ns, generation, afterSeq, failed);
1810
1986
  }
1811
1987
  }
1812
1988
  async fillGroupGap(groupId) {
@@ -1818,12 +1994,15 @@ export class MessageDeliveryEngine {
1818
1994
  return;
1819
1995
  const ns = `group:${groupId}`;
1820
1996
  const afterSeq = client._seqTracker.getContiguousSeq(ns);
1997
+ const generation = this.captureInlineAckGeneration();
1821
1998
  const dedupKey = `group_msg:${groupId}:${afterSeq}`;
1822
1999
  if (client._gapFillDone.has(dedupKey))
1823
2000
  return;
1824
2001
  client._gapFillDone.set(dedupKey, Date.now());
2002
+ this.recordBackgroundPullNeed(ns, 0, true);
1825
2003
  client._clientLog.debug(`group message gap fill start: group=${groupId}, after_seq=${afterSeq}`);
1826
2004
  let filled = 0;
2005
+ let failed = false;
1827
2006
  try {
1828
2007
  const operation = () => client._pullGroupV2(groupId, afterSeq, 50, { gateLocked: true });
1829
2008
  const messages = client._rpcPipeline
@@ -1834,14 +2013,15 @@ export class MessageDeliveryEngine {
1834
2013
  client._clientLog.debug(`group message gap fill done: group=${groupId}, after_seq=${afterSeq}, filled=${filled}`);
1835
2014
  }
1836
2015
  catch (exc) {
2016
+ failed = true;
1837
2017
  client._clientLog.warn(`group message gap fill failed: ${formatDeliveryError(exc)}`);
1838
2018
  }
1839
2019
  finally {
2020
+ if (generation !== this.captureInlineAckGeneration())
2021
+ return;
1840
2022
  client._gapFillDone.delete(dedupKey);
1841
- if (filled > 0 && client._seqTracker.getContiguousSeq(ns) > afterSeq) {
1842
- void this.fillGroupGap(groupId);
1843
- }
1844
2023
  this.schedulePendingGroupPullIfNeeded(ns, 'gap-fill-complete');
2024
+ this.finishBackgroundPull(ns, generation, afterSeq, failed);
1845
2025
  }
1846
2026
  }
1847
2027
  scheduleRealtimeWindowForward(ns, groupId = '') {
@@ -1883,6 +2063,7 @@ export class MessageDeliveryEngine {
1883
2063
  if (!groupId)
1884
2064
  return;
1885
2065
  const ns = `group_event:${groupId}`;
2066
+ const generation = this.captureInlineAckGeneration();
1886
2067
  const requestedAfterSeq = Number(options.afterSeq ?? 0);
1887
2068
  const afterSeq = Number.isSafeInteger(requestedAfterSeq) && requestedAfterSeq > 0
1888
2069
  ? requestedAfterSeq
@@ -1901,10 +2082,12 @@ export class MessageDeliveryEngine {
1901
2082
  }
1902
2083
  }
1903
2084
  client._gapFillDone.set(dedupKey, Date.now());
2085
+ this.recordBackgroundPullNeed(ns, client._seqTracker.getMaxSeenSeq(ns), true);
1904
2086
  this.syncStarted(ns);
1905
2087
  let filled = 0;
1906
2088
  let continuationAfterSeq = 0;
1907
2089
  let failed = false;
2090
+ let dissolved = false;
1908
2091
  try {
1909
2092
  let nextAfterSeq = afterSeq;
1910
2093
  const maxPages = singlePage ? 1 : 100;
@@ -1930,14 +2113,16 @@ export class MessageDeliveryEngine {
1930
2113
  }
1931
2114
  if (!isJsonObject(result))
1932
2115
  return;
2116
+ if (generation !== this.captureInlineAckGeneration())
2117
+ return;
2118
+ this.observeBackgroundPullResponse(ns, nextAfterSeq, result);
1933
2119
  const events = result.events;
1934
2120
  if (!Array.isArray(events))
1935
2121
  return;
1936
2122
  const pageContigBefore = client._seqTracker.getContiguousSeq(ns);
2123
+ let pageTrackerSnapshot = null;
2124
+ let trackerMutated = false;
1937
2125
  const eventObjects = events.filter((evt) => isJsonObject(evt));
1938
- client._seqTracker.onPullResult(ns, eventObjects, nextAfterSeq, Number.isSafeInteger(result.contiguous_seq)
1939
- ? result.contiguous_seq
1940
- : undefined);
1941
2126
  const cursor = isJsonObject(result.cursor)
1942
2127
  ? result.cursor
1943
2128
  : null;
@@ -1947,13 +2132,6 @@ export class MessageDeliveryEngine {
1947
2132
  const earliestAvailable = Number(result.earliest_available_event_seq ?? 0);
1948
2133
  const visibilityFloor = Math.max(0, Number.isFinite(Number(cursor?.join_seq)) ? Number(cursor?.join_seq) : 0, Number.isFinite(earliestAvailable) && earliestAvailable > 0 ? earliestAvailable - 1 : 0);
1949
2134
  const effectiveFloor = Math.max(retentionFloor, serverAckFloor, visibilityFloor);
1950
- if (effectiveFloor > 0) {
1951
- const contigBeforeFloor = client._seqTracker.getContiguousSeq(ns);
1952
- if (contigBeforeFloor < effectiveFloor) {
1953
- client._clientLog.info(`group.pull_events floor advance: ns=${ns} contiguous=${contigBeforeFloor} -> effective_floor=${effectiveFloor} retention_floor=${retentionFloor} server_ack=${serverAckFloor} visibility_floor=${visibilityFloor}`);
1954
- client._seqTracker.forceContiguousSeq(ns, effectiveFloor);
1955
- }
1956
- }
1957
2135
  const eventSeqs = [];
1958
2136
  let hasDissolvedEvent = false;
1959
2137
  let publishedEventCount = 0;
@@ -1983,11 +2161,40 @@ export class MessageDeliveryEngine {
1983
2161
  if (et !== 'group.message_created')
1984
2162
  filled += 1;
1985
2163
  }
1986
- const ackContig = client._seqTracker.getContiguousSeq(ns);
1987
- await this.drainOrderedMessages(ns);
1988
- if (ackContig !== pageContigBefore && !hasDissolvedEvent) {
1989
- this.persistSeq(ns);
2164
+ try {
2165
+ const scanTarget = Math.max(pageContigBefore, effectiveFloor, positiveSafeSequenceHint(result.contiguous_seq), ...eventSeqs);
2166
+ await this.drainOrderedMessages(ns, undefined, false, false, 'pull', undefined, scanTarget);
2167
+ if (generation !== this.captureInlineAckGeneration())
2168
+ throw new Error('pull invalidated');
2169
+ this.ensurePullOperationCurrent(ns);
2170
+ if (hasDissolvedEvent) {
2171
+ dissolved = true;
2172
+ break;
2173
+ }
2174
+ pageTrackerSnapshot = client._seqTracker.snapshotNamespace?.(ns) ?? null;
2175
+ trackerMutated = true;
2176
+ client._seqTracker.onPullResult(ns, eventObjects, nextAfterSeq, Number.isSafeInteger(result.contiguous_seq)
2177
+ ? result.contiguous_seq : undefined);
2178
+ if (client._seqTracker.getContiguousSeq(ns) < effectiveFloor) {
2179
+ client._seqTracker.forceContiguousSeq(ns, effectiveFloor);
2180
+ }
2181
+ if (client._seqTracker.getContiguousSeq(ns) !== pageContigBefore && !hasDissolvedEvent) {
2182
+ await client._commitSeqTrackerState(ns);
2183
+ this.ensurePullOperationCurrent(ns);
2184
+ if (generation !== this.captureInlineAckGeneration())
2185
+ throw new Error('pull invalidated');
2186
+ }
1990
2187
  }
2188
+ catch (exc) {
2189
+ if (trackerMutated && generation === this.captureInlineAckGeneration() && this.isPullOperationCurrent(ns)
2190
+ && typeof client._seqTracker.restoreNamespaceSnapshot === 'function') {
2191
+ (client._seqTracker._restoreNamespaceSnapshotPreservingPush ?? client._seqTracker.restoreNamespaceSnapshot)
2192
+ .call(client._seqTracker, ns, pageTrackerSnapshot);
2193
+ this.dropSeqTrackerPending(ns);
2194
+ }
2195
+ throw exc;
2196
+ }
2197
+ const ackContig = client._seqTracker.getContiguousSeq(ns);
1991
2198
  if (eventObjects.length > 0 && ackContig > 0 && ackContig !== pageContigBefore) {
1992
2199
  const ackSeq = this.clampAckSeq('group.ack_events', 'event_seq', ns, ackContig);
1993
2200
  try {
@@ -2020,6 +2227,7 @@ export class MessageDeliveryEngine {
2020
2227
  const nextAfter = Math.max(eventSeqs.length > 0 ? Math.max(...eventSeqs) : nextAfterSeq, nextAfterSeq);
2021
2228
  const pageAdvanced = eventObjects.length > 0 && nextAfter > nextAfterSeq;
2022
2229
  const needsMore = result.has_more === true
2230
+ || positiveSafeSequenceHint(result.remaining) > 0
2023
2231
  || client._seqTracker.getMaxSeenSeq(ns) > nextAfter;
2024
2232
  if (singlePage && pageAdvanced && needsMore) {
2025
2233
  continuationAfterSeq = nextAfter;
@@ -2038,19 +2246,22 @@ export class MessageDeliveryEngine {
2038
2246
  client._clientLog.warn(`group event gap fill failed: ${formatDeliveryError(exc)}`);
2039
2247
  }
2040
2248
  finally {
2041
- client._gapFillDone.delete(dedupKey);
2249
+ if (generation === this.captureInlineAckGeneration())
2250
+ client._gapFillDone.delete(dedupKey);
2042
2251
  if (legacyGateToken !== null && typeof client._releasePullGate === 'function') {
2043
2252
  client._releasePullGate(ns, legacyGateToken);
2044
2253
  }
2045
- if (singlePage && continuationAfterSeq > afterSeq) {
2254
+ if (generation !== this.captureInlineAckGeneration())
2255
+ return;
2256
+ if (dissolved)
2257
+ this.backgroundPullNeeds.delete(ns);
2258
+ if (!dissolved && singlePage && continuationAfterSeq > afterSeq) {
2046
2259
  this.enqueueOnlineUnreadEventHint({
2047
2260
  group_id: groupId,
2048
2261
  _online_hint_after_event_seq: continuationAfterSeq,
2049
2262
  });
2050
2263
  }
2051
- else if (!singlePage && filled > 0 && client._seqTracker.getContiguousSeq(ns) > afterSeq) {
2052
- void this.fillGroupEventGap(groupId);
2053
- }
2264
+ this.finishBackgroundPull(ns, generation, afterSeq, failed);
2054
2265
  if (!client._rpcPipeline)
2055
2266
  this.syncStopped(failed ? 'aborted' : 'pull_drained');
2056
2267
  }
@@ -2060,6 +2271,7 @@ export class MessageDeliveryEngine {
2060
2271
  const groupId = String(data.group_id ?? '').trim();
2061
2272
  if (!groupId || client._sessionOptions?.background_sync === false)
2062
2273
  return;
2274
+ this.recordBackgroundPullNeed(`group_event:${groupId}`, positiveSafeSequenceHint(data.event_seq), true);
2063
2275
  const rawAfter = Number(data._online_hint_after_event_seq ?? 0);
2064
2276
  const afterSeq = Number.isSafeInteger(rawAfter) && rawAfter > 0 ? rawAfter : 0;
2065
2277
  client._safeAsync(this.fillGroupEventGap(groupId, {
@@ -2864,7 +3076,8 @@ export class MessageDeliveryEngine {
2864
3076
  return false;
2865
3077
  const client = this.runtime.client;
2866
3078
  const tracker = client._seqTracker;
2867
- const snapshot = typeof tracker.snapshotNamespace === 'function' ? tracker.snapshotNamespace(ns) : null;
3079
+ let snapshot = null;
3080
+ let trackerMutated = false;
2868
3081
  let delivered = false;
2869
3082
  try {
2870
3083
  const selfSync = (event === 'message.received' && this.isSelfSentP2PMessage(payload))
@@ -2879,6 +3092,8 @@ export class MessageDeliveryEngine {
2879
3092
  delivered = true;
2880
3093
  if (!this.inlineGenerationIsCurrent(generation))
2881
3094
  return false;
3095
+ snapshot = typeof tracker.snapshotNamespace === 'function' ? tracker.snapshotNamespace(ns) : null;
3096
+ trackerMutated = true;
2882
3097
  const needsPull = Boolean(tracker.onMessageSeq(ns, seq));
2883
3098
  if (!this.inlineGenerationIsCurrent(generation))
2884
3099
  return false;
@@ -2901,9 +3116,10 @@ export class MessageDeliveryEngine {
2901
3116
  return true;
2902
3117
  }
2903
3118
  catch (exc) {
2904
- if (this.inlineGenerationIsCurrent(generation)
2905
- && typeof tracker.restoreNamespaceSnapshot === 'function')
2906
- tracker.restoreNamespaceSnapshot(ns, snapshot);
3119
+ if (trackerMutated && this.inlineGenerationIsCurrent(generation)
3120
+ && typeof tracker.restoreNamespaceSnapshot === 'function') {
3121
+ (tracker._restoreNamespaceSnapshotPreservingPush ?? tracker.restoreNamespaceSnapshot).call(tracker, ns, snapshot);
3122
+ }
2907
3123
  if (this.inlineGenerationIsCurrent(generation) && delivered)
2908
3124
  this.markPublishedSeq(ns, seq);
2909
3125
  client._clientLog.debug(`inline realtime consume rolled back: ns=${ns} seq=${seq} err=${formatDeliveryError(exc)}`);
@@ -2929,11 +3145,13 @@ export class MessageDeliveryEngine {
2929
3145
  throw new Error('inline realtime terminal cursor did not collapse after consume');
2930
3146
  }
2931
3147
  await client._commitSeqTrackerState(ns);
2932
- return true;
3148
+ return this.inlineGenerationIsCurrent(generation);
2933
3149
  }
2934
3150
  catch (exc) {
2935
- if (typeof tracker.restoreNamespaceSnapshot === 'function')
2936
- tracker.restoreNamespaceSnapshot(ns, snapshot);
3151
+ if (this.inlineGenerationIsCurrent(generation)
3152
+ && typeof tracker.restoreNamespaceSnapshot === 'function') {
3153
+ (tracker._restoreNamespaceSnapshotPreservingPush ?? tracker.restoreNamespaceSnapshot).call(tracker, ns, snapshot);
3154
+ }
2937
3155
  client._clientLog.debug(`inline realtime terminal consume rolled back: ns=${ns} seq=${seq} err=${formatDeliveryError(exc)}`);
2938
3156
  return false;
2939
3157
  }
@@ -3126,6 +3344,7 @@ export class MessageDeliveryEngine {
3126
3344
  }
3127
3345
  async onV2PushNotification(data, expectedVersion, inlineGeneration) {
3128
3346
  const client = this.runtime.client;
3347
+ const syncGeneration = this.captureInlineAckGeneration();
3129
3348
  if (inlineGeneration !== undefined && !this.inlineGenerationIsCurrent(inlineGeneration))
3130
3349
  return;
3131
3350
  const pushData = isJsonObject(data) ? data : null;
@@ -3180,6 +3399,7 @@ export class MessageDeliveryEngine {
3180
3399
  this.retryPendingP2PInlineAck(ns, pushSeq, 'covered inline push');
3181
3400
  return;
3182
3401
  }
3402
+ this.recordBackgroundPullNeed(ns, pushSeq);
3183
3403
  const pullActive = client._rpcPipeline?.hasPullActivity?.(ns) === true;
3184
3404
  if (pullActive || !this.beginRealtimeSync(ns)) {
3185
3405
  client._recordPendingP2pPull(ns, pushSeq);
@@ -3196,7 +3416,9 @@ export class MessageDeliveryEngine {
3196
3416
  client._clientLog.warn(`V2 P2P push auto-sync failed: ${formatDeliveryError(exc)}`);
3197
3417
  }
3198
3418
  finally {
3199
- if (inlineGeneration === undefined || this.inlineGenerationIsCurrent(inlineGeneration)) {
3419
+ if (syncGeneration === this.captureInlineAckGeneration()
3420
+ && (inlineGeneration === undefined || this.inlineGenerationIsCurrent(inlineGeneration))) {
3421
+ this.finishBackgroundPull(ns, syncGeneration, this.syncState(ns).ack, false);
3200
3422
  this.endRealtimeSync(ns);
3201
3423
  client._schedulePendingP2pPullIfNeeded?.(ns, 'push-auto-pull-complete');
3202
3424
  }
@@ -3208,6 +3430,9 @@ export class MessageDeliveryEngine {
3208
3430
  if (!groupId)
3209
3431
  return;
3210
3432
  const ns = `group:${groupId}`;
3433
+ const generation = this.captureInlineAckGeneration();
3434
+ const afterSeq = this.syncState(ns).ack;
3435
+ this.recordBackgroundPullNeed(ns, positiveSafeSequenceHint(data.seq));
3211
3436
  const targets = this.onlineUnreadHintTargets ?? new Map();
3212
3437
  const tasks = this.onlineUnreadHintTasks ?? new Map();
3213
3438
  this.onlineUnreadHintTargets = targets;
@@ -3215,10 +3440,13 @@ export class MessageDeliveryEngine {
3215
3440
  targets.set(ns, Math.max(targets.get(ns) ?? 0, positiveSafeSequenceHint(data.seq)));
3216
3441
  if (tasks.has(ns))
3217
3442
  return;
3443
+ let attemptAfterSeq = afterSeq;
3218
3444
  const task = Promise.resolve().then(async () => {
3445
+ let failed = false;
3219
3446
  try {
3220
3447
  while (true) {
3221
3448
  const ackBefore = this.syncState(ns).ack;
3449
+ attemptAfterSeq = ackBefore;
3222
3450
  await this.runGroupForwardRecovery(groupId, ns, 50, true, targets.get(ns) ?? 0);
3223
3451
  const ackAfter = this.syncState(ns).ack;
3224
3452
  if (ackAfter >= (targets.get(ns) ?? 0) || ackAfter <= ackBefore)
@@ -3226,12 +3454,14 @@ export class MessageDeliveryEngine {
3226
3454
  }
3227
3455
  }
3228
3456
  catch (exc) {
3457
+ failed = true;
3229
3458
  client._clientLog.debug(`online unread hint background Forward failed: ns=${ns} err=${formatDeliveryError(exc)}`);
3230
3459
  }
3231
3460
  finally {
3232
3461
  targets.delete(ns);
3233
3462
  if (tasks.get(ns) === task)
3234
3463
  tasks.delete(ns);
3464
+ this.finishBackgroundPull(ns, generation, attemptAfterSeq, failed);
3235
3465
  client._rpcPipeline?.requestPullDrainedCheck?.();
3236
3466
  }
3237
3467
  });
@@ -3240,6 +3470,7 @@ export class MessageDeliveryEngine {
3240
3470
  }
3241
3471
  async onRawGroupV2MessageCreated(data, expectedVersion, inlineGeneration) {
3242
3472
  const client = this.runtime.client;
3473
+ const syncGeneration = this.captureInlineAckGeneration();
3243
3474
  if (inlineGeneration !== undefined && !this.inlineGenerationIsCurrent(inlineGeneration))
3244
3475
  return;
3245
3476
  if (!isJsonObject(data)) {
@@ -3302,6 +3533,7 @@ export class MessageDeliveryEngine {
3302
3533
  }
3303
3534
  return;
3304
3535
  }
3536
+ this.recordBackgroundPullNeed(ns, seq);
3305
3537
  const pullActive = client._rpcPipeline?.hasPullActivity?.(ns) === true;
3306
3538
  if (pullActive || !this.beginRealtimeSync(ns)) {
3307
3539
  this.recordPendingGroupPull(ns, seq);
@@ -3317,7 +3549,9 @@ export class MessageDeliveryEngine {
3317
3549
  return;
3318
3550
  client._clientLog.warn(`V2 group push auto-sync failed: group=${groupId} err=${formatDeliveryError(exc)}`);
3319
3551
  }).finally(() => {
3320
- if (inlineGeneration === undefined || this.inlineGenerationIsCurrent(inlineGeneration)) {
3552
+ if (syncGeneration === this.captureInlineAckGeneration()
3553
+ && (inlineGeneration === undefined || this.inlineGenerationIsCurrent(inlineGeneration))) {
3554
+ this.finishBackgroundPull(ns, syncGeneration, this.syncState(ns).ack, false);
3321
3555
  this.endRealtimeSync(ns);
3322
3556
  this.schedulePendingGroupPullIfNeeded(ns, 'push-auto-pull-complete');
3323
3557
  }
@@ -3379,9 +3613,14 @@ export class MessageDeliveryEngine {
3379
3613
  if (!client._aid || !client._v2Session)
3380
3614
  return;
3381
3615
  const ns = `p2p:${client._aid}`;
3616
+ const generation = this.captureInlineAckGeneration();
3617
+ const afterSeq = this.syncState(ns).ack;
3618
+ this.recordBackgroundPullNeed(ns, positiveSafeSequenceHint(data.seq), true);
3619
+ let failed = false;
3382
3620
  client._safeAsync(this.runP2PForwardRecovery(ns, 50, true).catch((exc) => {
3621
+ failed = true;
3383
3622
  client._clientLog.debug(`P2P online unread hint background Forward failed: ns=${ns} err=${formatDeliveryError(exc)}`);
3384
- }));
3623
+ }).finally(() => this.finishBackgroundPull(ns, generation, afterSeq, failed)));
3385
3624
  }
3386
3625
  async saveSeqTrackerState() {
3387
3626
  const client = this.runtime.client;
@@ -3711,8 +3950,9 @@ export class MessageDeliveryEngine {
3711
3950
  }
3712
3951
  return params;
3713
3952
  }
3714
- async drainOrderedMessages(ns, beforeSeq, pullResponse = false, persist = true, source = 'push', context) {
3953
+ async drainOrderedMessages(ns, beforeSeq, pullResponse = false, persist = true, source = 'push', context, throughSeq) {
3715
3954
  const client = this.runtime.client;
3955
+ const generation = this.captureInlineAckGeneration();
3716
3956
  const pullScoped = this.isPullScopedSource(source);
3717
3957
  if (pullScoped)
3718
3958
  this.ensurePullOperationCurrent(ns);
@@ -3728,11 +3968,11 @@ export class MessageDeliveryEngine {
3728
3968
  this.ensurePullOperationCurrent(ns);
3729
3969
  const contig = client._seqTracker.getContiguousSeq(ns);
3730
3970
  const ready = [...queue.keys()]
3731
- .filter((seq) => seq <= contig && (beforeSeq === undefined || seq < beforeSeq))
3971
+ .filter((seq) => seq <= Math.max(contig, throughSeq ?? contig) && (beforeSeq === undefined || seq < beforeSeq))
3732
3972
  .sort((a, b) => a - b);
3733
3973
  let seq = ready[0];
3734
3974
  if (seq === undefined) {
3735
- const nextSeq = contig + 1;
3975
+ const nextSeq = Math.max(contig, throughSeq ?? contig) + 1;
3736
3976
  if (beforeSeq !== undefined && nextSeq >= beforeSeq)
3737
3977
  break;
3738
3978
  if (!queue.has(nextSeq))
@@ -3742,10 +3982,10 @@ export class MessageDeliveryEngine {
3742
3982
  if (seq === undefined)
3743
3983
  continue;
3744
3984
  const item = queue.get(seq);
3745
- queue.delete(seq);
3746
3985
  if (!item)
3747
3986
  continue;
3748
3987
  if (client._pushedSeqs.get(ns)?.has(seq)) {
3988
+ queue.delete(seq);
3749
3989
  client._clientLog.debug(`publish ordered drain skipped duplicate: ns=${ns}, seq=${seq}, event=${item.event}`);
3750
3990
  client._markOrderedSeqDelivered(ns, seq);
3751
3991
  continue;
@@ -3763,10 +4003,18 @@ export class MessageDeliveryEngine {
3763
4003
  }
3764
4004
  }
3765
4005
  await this.publishOrderedQueueItem(ns, item.event, seq, item.payload, itemSource, pullResponse, itemContext);
4006
+ if (generation !== this.captureInlineAckGeneration())
4007
+ throw new Error('pull invalidated');
3766
4008
  if (pullScoped)
3767
4009
  this.ensurePullOperationCurrent(ns);
4010
+ queue.delete(seq);
4011
+ if (item.event === 'group.changed' && isJsonObject(item.payload)
4012
+ && item.payload.action === 'dissolved')
4013
+ break;
3768
4014
  this.markPublishedSeq(ns, seq);
3769
4015
  client._markOrderedSeqDelivered(ns, seq);
4016
+ if (throughSeq !== undefined)
4017
+ throughSeq = Math.max(throughSeq, seq);
3770
4018
  delivered = true;
3771
4019
  client._clientLog.debug(`publish ordered drain delivered: ns=${ns}, seq=${seq}, event=${item.event}`);
3772
4020
  }
@@ -3810,6 +4058,7 @@ export class MessageDeliveryEngine {
3810
4058
  }
3811
4059
  async publishOrderedMessageInternal(event, ns, seq, payload, source, context) {
3812
4060
  const client = this.runtime.client;
4061
+ const generation = this.captureInlineAckGeneration();
3813
4062
  const pullScoped = this.isPullScopedSource(source);
3814
4063
  if (pullScoped)
3815
4064
  this.ensurePullOperationCurrent(ns);
@@ -3853,12 +4102,14 @@ export class MessageDeliveryEngine {
3853
4102
  return false;
3854
4103
  }
3855
4104
  const queue = client._pendingOrderedMsgs.get(ns);
3856
- queue?.delete(seqNum);
3857
- if (queue && queue.size === 0)
3858
- client._pendingOrderedMsgs.delete(ns);
3859
4105
  await this.publishOrderedQueueItem(ns, event, seqNum, payload, source, false, context);
4106
+ if (generation !== this.captureInlineAckGeneration())
4107
+ throw new Error('pull invalidated');
3860
4108
  if (pullScoped)
3861
4109
  this.ensurePullOperationCurrent(ns);
4110
+ queue?.delete(seqNum);
4111
+ if (queue && queue.size === 0)
4112
+ client._pendingOrderedMsgs.delete(ns);
3862
4113
  this.markPublishedSeq(ns, seqNum);
3863
4114
  client._markOrderedSeqDelivered(ns, seqNum);
3864
4115
  client._clientLog.debug(`publish ordered delivered: event=${event}, ns=${ns}, seq=${seqNum}`);
@@ -3872,6 +4123,7 @@ export class MessageDeliveryEngine {
3872
4123
  }
3873
4124
  async publishPulledMessage(event, ns, seq, payload, persist = true, source = 'pull') {
3874
4125
  const client = this.runtime.client;
4126
+ const generation = this.captureInlineAckGeneration();
3875
4127
  const pullScoped = this.isPullScopedSource(source);
3876
4128
  if (pullScoped)
3877
4129
  this.ensurePullOperationCurrent(ns);
@@ -3907,13 +4159,15 @@ export class MessageDeliveryEngine {
3907
4159
  await this.drainOrderedMessages(ns, seqNum, true, persist, source);
3908
4160
  if (pullScoped)
3909
4161
  this.ensurePullOperationCurrent(ns);
3910
- queue?.delete(seqNum);
3911
- if (queue && queue.size === 0)
3912
- client._pendingOrderedMsgs.delete(ns);
3913
4162
  if (event === 'message.recalled') {
3914
4163
  const recallPublished = await this.publishMessageRecallTombstone(seqNum, payload, source);
4164
+ if (generation !== this.captureInlineAckGeneration())
4165
+ throw new Error('pull invalidated');
3915
4166
  if (pullScoped)
3916
4167
  this.ensurePullOperationCurrent(ns);
4168
+ queue?.delete(seqNum);
4169
+ if (queue && queue.size === 0)
4170
+ client._pendingOrderedMsgs.delete(ns);
3917
4171
  this.markPublishedSeq(ns, seqNum);
3918
4172
  client._clientLog.debug(`publish pulled delivered: event=${event}, ns=${ns}, seq=${seqNum}`);
3919
4173
  await this.drainOrderedMessages(ns, undefined, true, persist, source);
@@ -3924,8 +4178,13 @@ export class MessageDeliveryEngine {
3924
4178
  const published = client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload, source));
3925
4179
  if (isPromiseLike(published))
3926
4180
  await published;
4181
+ if (generation !== this.captureInlineAckGeneration())
4182
+ throw new Error('pull invalidated');
3927
4183
  if (pullScoped)
3928
4184
  this.ensurePullOperationCurrent(ns);
4185
+ queue?.delete(seqNum);
4186
+ if (queue && queue.size === 0)
4187
+ client._pendingOrderedMsgs.delete(ns);
3929
4188
  this.markPublishedSeq(ns, seqNum);
3930
4189
  client._clientLog.debug(`publish pulled delivered: event=${event}, ns=${ns}, seq=${seqNum}`);
3931
4190
  await this.drainOrderedMessages(ns, undefined, true, persist, source);