@agentunion/fastaun 0.5.23 → 0.5.25

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 (54) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/_packed_docs/CHANGELOG.md +62 -0
  3. package/_packed_docs/INDEX.md +27 -15
  4. package/_packed_docs/KITE_DOCS_GUIDE.md +22 -19
  5. package/_packed_docs/TS/346/263/250/345/206/214/350/207/252/345/212/250/347/273/221/345/256/232Owner/347/232/204E2E/351/252/214/350/257/201/344/273/243/347/240/201.md +196 -0
  6. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2112.1.md +2 -2
  7. package/_packed_docs/sdk/0.5.24-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +401 -0
  8. package/_packed_docs/sdk/0.5.24-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +354 -0
  9. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +17 -7
  10. package/_packed_docs/sdk/INDEX.md +30 -6
  11. package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/2272.1.md +49 -2
  12. 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 +123 -72
  13. 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 +173 -96
  14. package/dist/agent-md.d.ts +2 -1
  15. package/dist/agent-md.js +52 -22
  16. package/dist/agent-md.js.map +1 -1
  17. package/dist/aid-store.d.ts +1 -0
  18. package/dist/aid-store.js +34 -12
  19. package/dist/aid-store.js.map +1 -1
  20. package/dist/auth.d.ts +1 -0
  21. package/dist/auth.js +44 -10
  22. package/dist/auth.js.map +1 -1
  23. package/dist/client/delivery.d.ts +3 -0
  24. package/dist/client/delivery.js +197 -47
  25. package/dist/client/delivery.js.map +1 -1
  26. package/dist/client/rpc-pipeline.js +10 -0
  27. package/dist/client/rpc-pipeline.js.map +1 -1
  28. package/dist/client/v2-e2ee.js +17 -5
  29. package/dist/client/v2-e2ee.js.map +1 -1
  30. package/dist/client.d.ts +10 -10
  31. package/dist/client.js +158 -119
  32. package/dist/client.js.map +1 -1
  33. package/dist/group-fs.js +5 -0
  34. package/dist/group-fs.js.map +1 -1
  35. package/dist/index.d.ts +1 -1
  36. package/dist/keystore/aid-db.d.ts +1 -8
  37. package/dist/keystore/aid-db.js +46 -204
  38. package/dist/keystore/aid-db.js.map +1 -1
  39. package/dist/keystore/index.d.ts +0 -15
  40. package/dist/keystore/local-token-store.d.ts +1 -6
  41. package/dist/keystore/local-token-store.js +0 -15
  42. package/dist/keystore/local-token-store.js.map +1 -1
  43. package/dist/ownership-v21.d.ts +4 -2
  44. package/dist/ownership-v21.js +86 -42
  45. package/dist/ownership-v21.js.map +1 -1
  46. package/dist/ownership-workflow.d.ts +30 -59
  47. package/dist/ownership-workflow.js +260 -1667
  48. package/dist/ownership-workflow.js.map +1 -1
  49. package/dist/tools/cross-sdk-agent.d.ts +1 -2
  50. package/dist/tools/cross-sdk-agent.js +7 -14
  51. package/dist/tools/cross-sdk-agent.js.map +1 -1
  52. package/dist/version.d.ts +1 -1
  53. package/dist/version.js +1 -1
  54. package/package.json +1 -1
@@ -149,6 +149,7 @@ export class MessageDeliveryEngine {
149
149
  peerPageChains = new Map();
150
150
  automaticPullFailures = new Map();
151
151
  terminalGroupPulls = new Map();
152
+ groupPullVersions = new Map();
152
153
  automaticPullOperations = new WeakSet();
153
154
  automaticPullContext = new AsyncLocalStorage();
154
155
  automaticPullAbort = new AbortController();
@@ -160,6 +161,7 @@ export class MessageDeliveryEngine {
160
161
  this.automaticPullAbort = new AbortController();
161
162
  this.automaticPullFailures.clear();
162
163
  this.terminalGroupPulls.clear();
164
+ this.groupPullVersions.clear();
163
165
  }
164
166
  groupPullNamespace(namespace) {
165
167
  const groupId = namespace.startsWith('group_event:') ? namespace.slice('group_event:'.length)
@@ -173,15 +175,43 @@ export class MessageDeliveryEngine {
173
175
  if (!this.automaticPullContext.getStore())
174
176
  this.terminalGroupPulls.delete(this.groupPullNamespace(namespace));
175
177
  }
178
+ observeGroupUnblocked(groupId) {
179
+ const normalized = normalizeGroupAid(groupId);
180
+ if (!normalized)
181
+ return;
182
+ const canonical = `group:${normalized}`;
183
+ this.groupPullVersions.set(canonical, {});
184
+ const stopped = this.terminalGroupPulls.get(canonical);
185
+ if (!stopped)
186
+ return;
187
+ this.terminalGroupPulls.delete(canonical);
188
+ const client = this.runtime.client;
189
+ if (client._sessionOptions?.background_sync === false || client._closing || client.state !== ConnectionState.READY)
190
+ return;
191
+ const namespaces = [...stopped.namespaces, ...Object.keys(client._seqTracker.exportSyncState?.() ?? client._seqTracker.exportState())];
192
+ for (const prefix of ['group:', 'group_event:']) {
193
+ const ns = namespaces.find((candidate) => candidate.startsWith(prefix) && this.groupPullNamespace(candidate) === canonical) ?? `${prefix}${groupId}`;
194
+ this.recordBackgroundPullNeed(ns, client._seqTracker.getMaxSeenSeq(ns), true, true);
195
+ }
196
+ this.requestBackgroundPullRecovery();
197
+ }
176
198
  stopAutomaticGroupPull(namespace, error) {
177
199
  const canonical = this.groupPullNamespace(namespace);
178
200
  if (!canonical)
179
201
  return;
180
- this.terminalGroupPulls.set(canonical, error);
202
+ const namespaces = this.terminalGroupPulls.get(canonical)?.namespaces ?? new Set();
203
+ namespaces.add(namespace);
204
+ for (const ns of [...this.backgroundPullTasks, ...(this.realtimeSyncing ?? []), ...(this.onlineUnreadHintTasks?.keys() ?? [])]) {
205
+ if (this.groupPullNamespace(ns) === canonical)
206
+ namespaces.add(ns);
207
+ }
208
+ this.terminalGroupPulls.set(canonical, { error, namespaces });
181
209
  for (const pending of [this.automaticPullFailures, this.backgroundPullNeeds, this.pendingGroupPullUpper, this.onlineUnreadHintTargets]) {
182
210
  for (const ns of pending?.keys() ?? []) {
183
- if (this.groupPullNamespace(ns) === canonical)
211
+ if (this.groupPullNamespace(ns) === canonical) {
212
+ namespaces.add(ns);
184
213
  pending.delete(ns);
214
+ }
185
215
  }
186
216
  }
187
217
  }
@@ -194,10 +224,13 @@ export class MessageDeliveryEngine {
194
224
  async runAutomaticPull(namespace, operation, schedule) {
195
225
  this.ensurePeerContext();
196
226
  if (this.automaticGroupPullStopped(namespace))
197
- throw this.terminalGroupPulls.get(this.groupPullNamespace(namespace));
227
+ throw this.terminalGroupPulls.get(this.groupPullNamespace(namespace)).error;
198
228
  const nested = this.automaticPullContext.getStore() === namespace;
199
229
  const generation = this.captureInlineAckGeneration();
200
230
  const signal = this.automaticPullAbort.signal;
231
+ const groupNamespace = this.groupPullNamespace(namespace);
232
+ const groupVersion = this.groupPullVersions.get(groupNamespace);
233
+ const groupCurrent = () => this.groupPullVersions.get(groupNamespace) === groupVersion;
201
234
  const client = this.runtime.client;
202
235
  while ((this.automaticPullFailures.get(namespace)?.retryAt ?? 0) > Date.now()) {
203
236
  const delay = this.automaticPullFailures.get(namespace).retryAt - Date.now();
@@ -210,20 +243,26 @@ export class MessageDeliveryEngine {
210
243
  signal.addEventListener('abort', abort, { once: true });
211
244
  });
212
245
  }
213
- if (signal.aborted || generation !== this.captureInlineAckGeneration() || client._closing || client.state !== ConnectionState.READY) {
246
+ if (signal.aborted || generation !== this.captureInlineAckGeneration() || !groupCurrent() || client._closing || client.state !== ConnectionState.READY) {
214
247
  throw new Error('pull invalidated');
215
248
  }
216
249
  const attempt = async () => {
250
+ if (!groupCurrent())
251
+ throw new Error('pull invalidated');
217
252
  if (this.automaticGroupPullStopped(namespace))
218
- throw this.terminalGroupPulls.get(this.groupPullNamespace(namespace));
253
+ throw this.terminalGroupPulls.get(this.groupPullNamespace(namespace)).error;
219
254
  const previousFailure = this.automaticPullFailures.get(namespace);
220
255
  try {
221
256
  const result = await this.automaticPullContext.run(namespace, operation);
257
+ if (!groupCurrent())
258
+ throw new Error('pull invalidated');
222
259
  if (!nested && !signal.aborted && generation === this.captureInlineAckGeneration())
223
260
  this.automaticPullFailures.delete(namespace);
224
261
  return result;
225
262
  }
226
263
  catch (error) {
264
+ if (!groupCurrent())
265
+ throw new Error('pull invalidated');
227
266
  if (!signal.aborted && generation === this.captureInlineAckGeneration()
228
267
  && this.groupPullNamespace(namespace) && isTerminalGroupPullError(error)) {
229
268
  this.stopAutomaticGroupPull(namespace, error);
@@ -627,6 +666,11 @@ export class MessageDeliveryEngine {
627
666
  && this.peerCatalogVersion === version.catalog && this.runtime.client._seqTracker.getMaxSeenSeq(namespace) <= committedAck
628
667
  && this.peerPullPriority(namespace) <= committedAck && this.syncState(namespace).head <= committedAck;
629
668
  }
669
+ isPeerHeadCovered(namespace, head) {
670
+ const committedAck = this.peerCommittedAck(namespace);
671
+ return Number.isSafeInteger(head) && head > 0 && head <= committedAck
672
+ && (this.peerServerConfirmedAcks.get(namespace) ?? 0) >= committedAck;
673
+ }
630
674
  shouldTailPeer(namespace, head, limit, expectedForwardNeed) {
631
675
  const state = this.syncState(namespace);
632
676
  const knownHead = Math.max(head, state.head, this.runtime.client._seqTracker.getMaxSeenSeq(namespace), this.peerPullPriority(namespace));
@@ -644,7 +688,7 @@ export class MessageDeliveryEngine {
644
688
  && this.peerPullPriority(namespace) <= committedAck
645
689
  && this.peerPushVersions.get(namespace) === pushVersion
646
690
  && this.peerCatalogVersion === catalogVersion
647
- && (!need || (need === expectedNeed && !need.pending && need.head <= committedAck));
691
+ && (!need || (need === expectedNeed && need.head <= committedAck));
648
692
  }
649
693
  async pullPeers(params = {}, background = false) {
650
694
  const client = this.runtime.client;
@@ -794,12 +838,16 @@ export class MessageDeliveryEngine {
794
838
  }
795
839
  }
796
840
  const namespace = this.peerNamespace(sender);
797
- this.peerPushVersions.set(namespace, {});
841
+ if (!Number.isSafeInteger(head) || head <= 0 || volatile)
842
+ this.peerPushVersions.set(namespace, {});
843
+ else if (this.initializedPeers.has(namespace) && client._seqTracker.snapshotNamespace(namespace) !== null)
844
+ client._seqTracker.updateMaxSeen(namespace, head);
798
845
  if (!this.hasP2pApplicationSubscription() || client._sessionOptions?.background_sync === false) {
799
846
  this.peerForwardNeeds.set(namespace, {});
800
847
  return;
801
848
  }
802
849
  const need = this.peerRecoveryNeeds.get(sender) ?? { head: 0, volatile: false, pending: false };
850
+ need.paused = false;
803
851
  need.head = Math.max(need.head, head);
804
852
  need.volatile ||= volatile;
805
853
  need.pending = true;
@@ -812,6 +860,8 @@ export class MessageDeliveryEngine {
812
860
  if (!this.hasP2pApplicationSubscription())
813
861
  return;
814
862
  const namespace = this.peerNamespace(sender);
863
+ if (Number.isSafeInteger(need.head) && need.head > 0)
864
+ client._seqTracker.updateMaxSeen(namespace, need.head);
815
865
  if (this.shouldTailPeer(namespace, need.head, _INTERNAL_MESSAGE_FLOW_LIMITS.pullPageSize)) {
816
866
  const forwardNeed = {};
817
867
  this.peerForwardNeeds.set(namespace, forwardNeed);
@@ -822,19 +872,28 @@ export class MessageDeliveryEngine {
822
872
  const tail = await this.runAutomaticPull(namespace, async () => {
823
873
  const beforeAck = this.peerCommittedAck(namespace);
824
874
  const beforeVolatile = this.peerVolatilePullCursor(sender);
875
+ const beforeWindow = this.syncState(namespace);
825
876
  const result = await client._v2E2EE.pullPeerV2(sender, { window_mode: 'tail', background: true, retainMessages: false, budget: { remaining: 1 }, autoTail });
826
- if (autoTail.forward && !result.complete && this.peerCommittedAck(namespace) <= beforeAck && this.peerVolatilePullCursor(sender) === beforeVolatile) {
827
- throw new Error('peer recovery made no progress');
877
+ need.volatile ||= result.volatile_has_more === true;
878
+ const afterWindow = this.syncState(namespace);
879
+ if (autoTail.forward && (!result.complete || Number(result.ack_seq) < need.head) && this.peerCommittedAck(namespace) <= beforeAck && this.peerVolatilePullCursor(sender) === beforeVolatile
880
+ && afterWindow.tail === beforeWindow.tail && afterWindow.head === beforeWindow.head) {
881
+ if (!result.pending_ack_seq && !need.pending && this.peerPushVersions.get(namespace) === pushVersion && this.peerCatalogVersion === catalogVersion)
882
+ need.paused = true;
883
+ else if (result.pending_ack_seq)
884
+ throw new Error('peer recovery made no progress');
828
885
  }
829
886
  return result;
830
887
  });
831
888
  if (generation !== this.captureInlineAckGeneration() || this.peerRecoveryNeeds.get(sender) !== need)
832
889
  return;
890
+ if (need.paused)
891
+ return;
833
892
  if (autoTail.forward) {
834
893
  if (tail.complete && this.peerForwardNeeds.get(namespace) === forwardNeed
835
894
  && this.peerPushVersions.get(namespace) === pushVersion && this.peerCatalogVersion === catalogVersion)
836
895
  this.peerForwardNeeds.delete(namespace);
837
- if (tail.complete && Number(tail.ack_seq) >= need.head && !need.pending
896
+ if (tail.complete && Number(tail.ack_seq) >= need.head
838
897
  && this.peerPushVersions.get(namespace) === pushVersion && this.peerCatalogVersion === catalogVersion) {
839
898
  this.peerRecoveryNeeds.delete(sender);
840
899
  return;
@@ -856,18 +915,27 @@ export class MessageDeliveryEngine {
856
915
  const result = await this.runAutomaticPull(namespace, async () => {
857
916
  const beforeAck = this.peerCommittedAck(namespace);
858
917
  const beforeVolatile = this.peerVolatilePullCursor(sender);
918
+ const beforeWindow = this.syncState(namespace);
859
919
  const result = await client._v2E2EE.pullPeerV2(sender, { background: true, retainMessages: false, budget: { remaining: 100 } });
860
- if (!result.complete && this.peerCommittedAck(namespace) <= beforeAck && this.peerVolatilePullCursor(sender) === beforeVolatile) {
861
- throw new Error('peer recovery made no progress');
920
+ need.volatile ||= result.volatile_has_more === true;
921
+ const afterWindow = this.syncState(namespace);
922
+ if ((!result.complete || result.ack_seq < need.head) && this.peerCommittedAck(namespace) <= beforeAck && this.peerVolatilePullCursor(sender) === beforeVolatile
923
+ && afterWindow.tail === beforeWindow.tail && afterWindow.head === beforeWindow.head) {
924
+ if (!result.pending_ack_seq && !need.pending && this.peerPushVersions.get(namespace) === pushVersion && this.peerCatalogVersion === catalogVersion)
925
+ need.paused = true;
926
+ else if (result.pending_ack_seq)
927
+ throw new Error('peer recovery made no progress');
862
928
  }
863
929
  return result;
864
930
  });
865
931
  if (generation !== this.captureInlineAckGeneration() || this.peerRecoveryNeeds.get(sender) !== need)
866
932
  return;
933
+ if (need.paused)
934
+ return;
867
935
  if (result.complete && this.peerForwardNeeds.get(namespace) === forwardNeed
868
936
  && this.peerPushVersions.get(namespace) === pushVersion && this.peerCatalogVersion === catalogVersion)
869
937
  this.peerForwardNeeds.delete(namespace);
870
- if (result.complete && result.ack_seq >= need.head && !need.pending
938
+ if (result.complete && result.ack_seq >= need.head
871
939
  && this.peerPushVersions.get(namespace) === pushVersion && this.peerCatalogVersion === catalogVersion) {
872
940
  this.peerRecoveryNeeds.delete(sender);
873
941
  break;
@@ -879,12 +947,18 @@ export class MessageDeliveryEngine {
879
947
  client._safeAsync(task.catch(() => {
880
948
  if (generation !== this.captureInlineAckGeneration())
881
949
  return;
882
- const timer = setTimeout(() => { if (generation === this.captureInlineAckGeneration())
883
- this.schedulePeerRecovery(sender, need.head, need.volatile); }, BACKGROUND_PULL_RETRY_DELAY_MS);
950
+ const timer = setTimeout(() => {
951
+ if (generation === this.captureInlineAckGeneration() && this.peerRecoveryNeeds.get(sender) === need
952
+ && !need.paused && !this.peerRecoveryTasks.has(sender))
953
+ this.schedulePeerRecovery(sender, need.head, need.volatile);
954
+ }, BACKGROUND_PULL_RETRY_DELAY_MS);
884
955
  timer.unref?.();
885
956
  }).finally(() => {
886
957
  if (generation === this.captureInlineAckGeneration()) {
887
958
  this.peerRecoveryTasks.delete(sender);
959
+ const pending = this.peerRecoveryNeeds.get(sender);
960
+ if (pending?.pending && !pending.paused)
961
+ this.requestBackgroundPullRecovery();
888
962
  if (this.peerCatalogDirty && this.peerRecoveryTasks.size === 0)
889
963
  this.refreshPendingPeers();
890
964
  }
@@ -1587,9 +1661,9 @@ export class MessageDeliveryEngine {
1587
1661
  return this.pendingGroupPullUpper?.get(ns) ?? 0;
1588
1662
  }
1589
1663
  hasAnyPendingPull() {
1590
- return this.peerCatalogDirty || this.peerRecoveryNeeds.size > 0 || this.peerRecoveryTasks.size > 0 || Boolean(this.peerCatalogTask)
1664
+ return this.peerCatalogDirty || [...this.peerRecoveryNeeds.values()].some((need) => !need.paused) || this.peerRecoveryTasks.size > 0 || Boolean(this.peerCatalogTask)
1591
1665
  || [...(this.pendingGroupPullUpper?.values() ?? [])].some((seq) => seq > 0)
1592
- || this.backgroundPullNeeds.size > 0
1666
+ || [...this.backgroundPullNeeds.values()].some((need) => need.retryAt !== Infinity)
1593
1667
  || Boolean(this.onlineUnreadHintTasks?.size)
1594
1668
  || Boolean(this.realtimeSyncing?.size)
1595
1669
  || Boolean(this.runtime.client._v2E2EE?.hasPendingSenderIKWork?.());
@@ -1604,7 +1678,7 @@ export class MessageDeliveryEngine {
1604
1678
  }
1605
1679
  this.schedulePendingGroupPullIfNeeded(ns, 'pull-gate-idle');
1606
1680
  }
1607
- recordBackgroundPullNeed(ns, throughSeq = 0, probe = false) {
1681
+ recordBackgroundPullNeed(ns, throughSeq = 0, probe = false, restart = false) {
1608
1682
  if (this.automaticGroupPullStopped(ns))
1609
1683
  return;
1610
1684
  const client = this.runtime.client;
@@ -1612,8 +1686,14 @@ export class MessageDeliveryEngine {
1612
1686
  || !(ns === `p2p:${client._aid}` || ns.startsWith('group:') || ns.startsWith('group_event:')))
1613
1687
  return;
1614
1688
  const need = this.backgroundPullNeeds.get(ns) ?? {
1615
- throughSeq: 0, hasMore: false, probe, lastAfter: -1, retryAt: 0,
1689
+ throughSeq: 0, hasMore: false, probe, lastAfter: -1, retryAt: 0, empty: false, trigger: {},
1616
1690
  };
1691
+ if (restart) {
1692
+ need.trigger = {};
1693
+ need.retryAt = 0;
1694
+ need.empty = false;
1695
+ need.progress = undefined;
1696
+ }
1617
1697
  need.throughSeq = Math.max(need.throughSeq, throughSeq);
1618
1698
  need.probe ||= probe;
1619
1699
  this.backgroundPullNeeds.set(ns, need);
@@ -1627,30 +1707,46 @@ export class MessageDeliveryEngine {
1627
1707
  return;
1628
1708
  const cursor = isJsonObject(response.cursor)
1629
1709
  ? response.cursor : null;
1630
- 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'
1710
+ const serverHead = Math.max(positiveSafeSequenceHint(cursor?.latest_seq), positiveSafeSequenceHint(response.latest_seq), positiveSafeSequenceHint(response.latest_message_seq), positiveSafeSequenceHint(response.latest_event_seq));
1711
+ const throughSeq = Math.max(serverHead, ...rows.map((row) => row && typeof row === 'object' && row.storage !== 'volatile'
1631
1712
  ? positiveSafeSequenceHint(row.seq ?? row.event_seq) : 0));
1632
1713
  this.recordBackgroundPullNeed(ns, throughSeq);
1633
1714
  const need = this.backgroundPullNeeds.get(ns);
1634
1715
  if (!need || afterSeq < need.lastAfter)
1635
1716
  return;
1636
1717
  need.lastAfter = afterSeq;
1718
+ need.progress = { ...this.syncState(ns), volatile: this.volatilePullCursor() };
1719
+ need.empty = rows.length === 0;
1637
1720
  need.hasMore = response.has_more === true || positiveSafeSequenceHint(response.remaining) > 0
1638
- || (!('has_more' in response) && limit > 0 && rows.length >= limit);
1721
+ || (!('has_more' in response) && serverHead === 0 && limit > 0 && rows.length >= limit);
1639
1722
  need.probe = false;
1640
1723
  }
1641
- finishBackgroundPull(ns, generation, afterSeq, failed) {
1724
+ finishBackgroundPull(ns, generation, afterSeq, failed, trigger) {
1642
1725
  if (generation !== this.captureInlineAckGeneration())
1643
1726
  return;
1644
1727
  if (this.automaticGroupPullStopped(ns))
1645
1728
  return;
1646
1729
  const need = this.backgroundPullNeeds.get(ns);
1730
+ if (need && trigger && need.trigger !== trigger) {
1731
+ need.retryAt = Date.now() + BACKGROUND_PULL_RETRY_DELAY_MS;
1732
+ this.requestBackgroundPullRecovery();
1733
+ return;
1734
+ }
1647
1735
  if (need) {
1648
1736
  if (failed)
1649
1737
  need.probe = true;
1650
1738
  else
1651
1739
  need.probe = false;
1652
- need.retryAt = failed || this.syncState(ns).ack <= afterSeq
1653
- ? Date.now() + BACKGROUND_PULL_RETRY_DELAY_MS : 0;
1740
+ const client = this.runtime.client;
1741
+ const state = this.syncState(ns);
1742
+ const ack = state.ack;
1743
+ const coordinator = client._v2E2EE;
1744
+ const pendingAck = coordinator?.pendingForwardAck?.(ns, ack) ?? 0;
1745
+ const noProgress = need.progress ? ack <= need.progress.ack && state.tail === need.progress.tail && state.head === need.progress.head
1746
+ && this.volatilePullCursor() === need.progress.volatile : need.empty && ack <= Math.max(afterSeq, need.lastAfter);
1747
+ need.retryAt = !failed && noProgress && trigger === need.trigger
1748
+ && !pendingAck && !this.pendingP2PInlineAcks?.has(ns) && !this.pendingGroupInlineAcks?.has(ns)
1749
+ ? Infinity : failed || ack <= afterSeq ? Date.now() + BACKGROUND_PULL_RETRY_DELAY_MS : 0;
1654
1750
  }
1655
1751
  this.requestBackgroundPullRecovery();
1656
1752
  }
@@ -1670,7 +1766,7 @@ export class MessageDeliveryEngine {
1670
1766
  if (this.peerCatalogDirty && !this.peerCatalogTask)
1671
1767
  this.refreshPendingPeers();
1672
1768
  for (const [sender, need] of this.peerRecoveryNeeds) {
1673
- if (!this.peerRecoveryTasks.has(sender))
1769
+ if (!need.paused && !this.peerRecoveryTasks.has(sender))
1674
1770
  this.schedulePeerRecovery(sender, need.head, need.volatile);
1675
1771
  }
1676
1772
  }
@@ -1741,6 +1837,8 @@ export class MessageDeliveryEngine {
1741
1837
  || client.state !== ConnectionState.READY || client._closing)
1742
1838
  return;
1743
1839
  const ns = `p2p:${client._aid}`;
1840
+ if (this.backgroundPullNeeds.get(ns)?.retryAt === Infinity)
1841
+ return;
1744
1842
  const state = this.syncState(ns);
1745
1843
  const maxSeen = client._seqTracker.getMaxSeenSeq(ns);
1746
1844
  if (maxSeen <= state.ack && state.ack >= state.tail - 1)
@@ -2930,6 +3028,7 @@ export class MessageDeliveryEngine {
2930
3028
  return;
2931
3029
  client._gapFillDone.set(dedupKey, Date.now());
2932
3030
  this.recordBackgroundPullNeed(ns, 0, true);
3031
+ const pullTrigger = this.backgroundPullNeeds.get(ns)?.trigger;
2933
3032
  client._clientLog.debug(`P2P message gap fill start: after_seq=${afterSeq}`);
2934
3033
  let filled = 0;
2935
3034
  let failed = false;
@@ -2953,7 +3052,7 @@ export class MessageDeliveryEngine {
2953
3052
  return;
2954
3053
  client._gapFillDone.delete(dedupKey);
2955
3054
  client._schedulePendingP2pPullIfNeeded?.(ns, 'gap-fill-complete');
2956
- this.finishBackgroundPull(ns, generation, afterSeq, failed);
3055
+ this.finishBackgroundPull(ns, generation, afterSeq, failed, pullTrigger);
2957
3056
  }
2958
3057
  }
2959
3058
  async fillGroupGap(groupId) {
@@ -2973,6 +3072,7 @@ export class MessageDeliveryEngine {
2973
3072
  return;
2974
3073
  client._gapFillDone.set(dedupKey, Date.now());
2975
3074
  this.recordBackgroundPullNeed(ns, 0, true);
3075
+ const pullTrigger = this.backgroundPullNeeds.get(ns)?.trigger;
2976
3076
  client._clientLog.debug(`group message gap fill start: group=${groupId}, after_seq=${afterSeq}`);
2977
3077
  let filled = 0;
2978
3078
  let failed = false;
@@ -2994,7 +3094,7 @@ export class MessageDeliveryEngine {
2994
3094
  return;
2995
3095
  client._gapFillDone.delete(dedupKey);
2996
3096
  this.schedulePendingGroupPullIfNeeded(ns, 'gap-fill-complete');
2997
- this.finishBackgroundPull(ns, generation, afterSeq, failed);
3097
+ this.finishBackgroundPull(ns, generation, afterSeq, failed, pullTrigger);
2998
3098
  }
2999
3099
  }
3000
3100
  scheduleRealtimeWindowForward(ns, groupId = '') {
@@ -3058,6 +3158,7 @@ export class MessageDeliveryEngine {
3058
3158
  }
3059
3159
  client._gapFillDone.set(dedupKey, Date.now());
3060
3160
  this.recordBackgroundPullNeed(ns, client._seqTracker.getMaxSeenSeq(ns), true);
3161
+ const pullTrigger = this.backgroundPullNeeds.get(ns)?.trigger;
3061
3162
  this.syncStarted(ns);
3062
3163
  let filled = 0;
3063
3164
  let continuationAfterSeq = 0;
@@ -3245,7 +3346,7 @@ export class MessageDeliveryEngine {
3245
3346
  _online_hint_after_event_seq: continuationAfterSeq,
3246
3347
  });
3247
3348
  }
3248
- this.finishBackgroundPull(ns, generation, afterSeq, failed);
3349
+ this.finishBackgroundPull(ns, generation, afterSeq, failed, pullTrigger);
3249
3350
  if (!client._rpcPipeline)
3250
3351
  this.syncStopped(failed ? 'aborted' : 'pull_drained');
3251
3352
  }
@@ -3257,7 +3358,7 @@ export class MessageDeliveryEngine {
3257
3358
  return;
3258
3359
  if (!groupId || client._sessionOptions?.background_sync === false)
3259
3360
  return;
3260
- this.recordBackgroundPullNeed(`group_event:${groupId}`, positiveSafeSequenceHint(data.event_seq), true);
3361
+ this.recordBackgroundPullNeed(`group_event:${groupId}`, positiveSafeSequenceHint(data.event_seq), true, true);
3261
3362
  const rawAfter = Number(data._online_hint_after_event_seq ?? 0);
3262
3363
  const afterSeq = Number.isSafeInteger(rawAfter) && rawAfter > 0 ? rawAfter : 0;
3263
3364
  client._safeAsync(this.fillGroupEventGap(groupId, {
@@ -4341,17 +4442,36 @@ export class MessageDeliveryEngine {
4341
4442
  if (!isJsonObject(data) || !client._aid || !this.messageTargetsCurrentInstance(data))
4342
4443
  return;
4343
4444
  const head = data;
4344
- if (String(head.kind ?? '') === 'message.online_unread_hint' || !head.sender_aid) {
4445
+ const legacy = isJsonObject(head.legacy_v1) ? head.legacy_v1 : null;
4446
+ const completeVolatile = this.isVolatileMessage(head) && (head.payload != null || legacy?.payload != null
4447
+ || (typeof head.envelope_json === 'string' && head.envelope_json.trim()));
4448
+ const sender = String(head.sender_aid ?? (completeVolatile ? head.from_aid ?? head.from : undefined) ?? '');
4449
+ if (String(head.kind ?? '') === 'message.online_unread_hint' || !sender) {
4345
4450
  this.refreshPendingPeers();
4346
4451
  return;
4347
4452
  }
4348
- if (head.cursor_scope !== 'peer') {
4453
+ if (head.cursor_scope !== 'peer' && (head.cursor_scope !== undefined || !completeVolatile)) {
4349
4454
  this.refreshPendingPeers();
4350
4455
  return;
4351
4456
  }
4352
- const sender = String(head.sender_aid);
4353
4457
  this.ensurePeerContext();
4354
- this.peerPushVersions.set(this.peerNamespace(sender), {});
4458
+ if (completeVolatile) {
4459
+ await client._v2E2EE.publishV2VolatileMessages([head], 'push', false);
4460
+ return;
4461
+ }
4462
+ const namespace = this.peerNamespace(sender);
4463
+ const seq = positiveSafeSequenceHint(head.seq);
4464
+ const committedAck = this.peerCommittedAck(namespace);
4465
+ if (seq > 0 && !this.isVolatileMessage(head) && !this.isVolatileMessage(head.inline_message)
4466
+ && head.volatile_has_more !== true && seq <= committedAck
4467
+ && !this.peerRecoveryNeeds.get(sender)?.volatile
4468
+ && (this.peerServerConfirmedAcks.get(namespace) ?? 0) >= committedAck)
4469
+ return;
4470
+ const unbounded = !seq || this.isVolatileMessage(head) || this.isVolatileMessage(head.inline_message)
4471
+ || head.volatile_has_more === true || this.peerRecoveryNeeds.get(sender)?.volatile === true;
4472
+ if (unbounded)
4473
+ this.peerPushVersions.set(namespace, {});
4474
+ const peerPullVersion = this.capturePeerTailVersion(sender);
4355
4475
  if (!hasNonEmptyInlineMessage(head.inline_message) && !this.isVolatileMessage(head) && !this.hasP2pApplicationSubscription())
4356
4476
  return;
4357
4477
  const generation = this.captureInlineAckGeneration();
@@ -4364,7 +4484,6 @@ export class MessageDeliveryEngine {
4364
4484
  this.schedulePeerRecovery(sender, positiveSafeSequenceHint(head.seq) ?? 0, this.isVolatileMessage(head));
4365
4485
  throw error;
4366
4486
  }
4367
- const seq = positiveSafeSequenceHint(head.seq);
4368
4487
  if (this.isVolatileMessage(head)) {
4369
4488
  await client._v2E2EE.publishV2VolatileMessages([head], 'push', false);
4370
4489
  return;
@@ -4410,16 +4529,20 @@ export class MessageDeliveryEngine {
4410
4529
  this.schedulePeerRecovery(sender, seq);
4411
4530
  throw error;
4412
4531
  });
4413
- if (consumed?.acquired && consumed.value)
4532
+ if (consumed?.acquired && consumed.value && !unbounded)
4414
4533
  return;
4415
4534
  }
4416
- if (seq <= this.syncState(ns).ack || !this.hasP2pApplicationSubscription())
4535
+ if ((!unbounded && this.isPeerHeadCovered(ns, seq)) || !this.hasP2pApplicationSubscription())
4417
4536
  return;
4418
4537
  if (client._sessionOptions?.background_sync !== false) {
4419
- this.schedulePeerRecovery(sender, seq);
4538
+ if (unbounded)
4539
+ this.schedulePeerRecovery(sender, seq, true);
4540
+ else
4541
+ this.schedulePeerRecovery(sender, seq);
4420
4542
  }
4421
4543
  else {
4422
4544
  await this.runAutomaticPull(ns, () => client._v2E2EE.pullPeerV2(sender, { limit: _INTERNAL_MESSAGE_FLOW_LIMITS.pullPageSize, budget: { remaining: 1 },
4545
+ ...(!unbounded ? { autoPeerHead: seq, peerPullVersion } : {}),
4423
4546
  ...(this.shouldTailPeer(ns, seq, _INTERNAL_MESSAGE_FLOW_LIMITS.pullPageSize) ? { window_mode: 'tail', autoTail: { head: seq, forward: false } } : {}) }));
4424
4547
  }
4425
4548
  }
@@ -4486,13 +4609,15 @@ export class MessageDeliveryEngine {
4486
4609
  }
4487
4610
  if (!this.hasP2pApplicationSubscription())
4488
4611
  return;
4489
- this.recordBackgroundPullNeed(ns, pushSeq);
4612
+ this.recordBackgroundPullNeed(ns, pushSeq, false, true);
4613
+ const pullTrigger = this.backgroundPullNeeds.get(ns)?.trigger;
4490
4614
  const pullActive = client._rpcPipeline?.hasPullActivity?.(ns) === true;
4491
4615
  if (pullActive || !this.beginRealtimeSync(ns)) {
4492
4616
  client._recordPendingP2pPull(ns, pushSeq);
4493
4617
  client._clientLog.debug(`V2 P2P push queued behind in-flight realtime sync: ns=${ns} seq=${pushSeq}`);
4494
4618
  return;
4495
4619
  }
4620
+ let pullFailed = false;
4496
4621
  try {
4497
4622
  await this.runP2PRealtimeSync(ns, pushSeq, 50, false);
4498
4623
  if (inlineGeneration !== undefined && !this.inlineGenerationIsCurrent(inlineGeneration))
@@ -4500,12 +4625,13 @@ export class MessageDeliveryEngine {
4500
4625
  // Tail 期间新到达的 Push 已由 realtime sync 在同一周期内补至多一页 Forward。
4501
4626
  }
4502
4627
  catch (exc) {
4628
+ pullFailed = true;
4503
4629
  client._clientLog.warn(`V2 P2P push auto-sync failed: ${formatDeliveryError(exc)}`);
4504
4630
  }
4505
4631
  finally {
4506
4632
  if (syncGeneration === this.captureInlineAckGeneration()
4507
4633
  && (inlineGeneration === undefined || this.inlineGenerationIsCurrent(inlineGeneration))) {
4508
- this.finishBackgroundPull(ns, syncGeneration, this.syncState(ns).ack, false);
4634
+ this.finishBackgroundPull(ns, syncGeneration, this.syncState(ns).ack, pullFailed, pullTrigger);
4509
4635
  this.endRealtimeSync(ns);
4510
4636
  client._schedulePendingP2pPullIfNeeded?.(ns, 'push-auto-pull-complete');
4511
4637
  }
@@ -4519,14 +4645,34 @@ export class MessageDeliveryEngine {
4519
4645
  const ns = `group:${groupId}`;
4520
4646
  if (this.automaticGroupPullStopped(ns))
4521
4647
  return;
4648
+ this.rememberCommittedSeqState(ns);
4522
4649
  const generation = this.captureInlineAckGeneration();
4523
- const afterSeq = this.syncState(ns).ack;
4524
- this.recordBackgroundPullNeed(ns, positiveSafeSequenceHint(data.seq));
4650
+ const state = this.syncState(ns);
4651
+ const afterSeq = state.ack;
4652
+ const hintSeq = positiveSafeSequenceHint(data.seq);
4653
+ const committedAck = Math.min(afterSeq, this.committedSeqTrackerStates.get(ns)?.ack ?? afterSeq);
4654
+ if (hintSeq > 0 && committedAck >= Math.max(hintSeq, state.head, Number(client._seqTracker.getMaxSeenSeq?.(ns) ?? 0), this.pendingPullUpper(ns))
4655
+ && !client._pendingOrderedMsgs.get(ns)?.size) {
4656
+ const coordinator = client._v2E2EE;
4657
+ const pendingAckSeq = Number(coordinator.pendingForwardAck?.(ns, committedAck) ?? 0);
4658
+ if (pendingAckSeq > 0) {
4659
+ client._safeAsync(coordinator.confirmForwardGroupAck(groupId, pendingAckSeq, String(data.group_aid ?? '').trim() || undefined, true));
4660
+ }
4661
+ else {
4662
+ const pendingNs = this.resolvePendingGroupInlineAckNamespace(ns);
4663
+ const pending = this.pendingGroupInlineAcks?.get(pendingNs);
4664
+ if (pending)
4665
+ this.retryPendingGroupInlineAck(pendingNs, pending.seq, 'covered online hint', committedAck);
4666
+ }
4667
+ return;
4668
+ }
4669
+ this.recordBackgroundPullNeed(ns, hintSeq, false, true);
4670
+ const pullTrigger = this.backgroundPullNeeds.get(ns)?.trigger;
4525
4671
  const targets = this.onlineUnreadHintTargets ?? new Map();
4526
4672
  const tasks = this.onlineUnreadHintTasks ?? new Map();
4527
4673
  this.onlineUnreadHintTargets = targets;
4528
4674
  this.onlineUnreadHintTasks = tasks;
4529
- targets.set(ns, Math.max(targets.get(ns) ?? 0, positiveSafeSequenceHint(data.seq)));
4675
+ targets.set(ns, Math.max(targets.get(ns) ?? 0, hintSeq));
4530
4676
  if (tasks.has(ns))
4531
4677
  return;
4532
4678
  let attemptAfterSeq = afterSeq;
@@ -4550,7 +4696,7 @@ export class MessageDeliveryEngine {
4550
4696
  targets.delete(ns);
4551
4697
  if (tasks.get(ns) === task)
4552
4698
  tasks.delete(ns);
4553
- this.finishBackgroundPull(ns, generation, attemptAfterSeq, failed);
4699
+ this.finishBackgroundPull(ns, generation, attemptAfterSeq, failed, pullTrigger);
4554
4700
  client._rpcPipeline?.requestPullDrainedCheck?.();
4555
4701
  }
4556
4702
  });
@@ -4624,25 +4770,28 @@ export class MessageDeliveryEngine {
4624
4770
  }
4625
4771
  return;
4626
4772
  }
4627
- this.recordBackgroundPullNeed(ns, seq);
4773
+ this.recordBackgroundPullNeed(ns, seq, false, true);
4774
+ const pullTrigger = this.backgroundPullNeeds.get(ns)?.trigger;
4628
4775
  const pullActive = client._rpcPipeline?.hasPullActivity?.(ns) === true;
4629
4776
  if (pullActive || !this.beginRealtimeSync(ns)) {
4630
4777
  this.recordPendingGroupPull(ns, seq);
4631
4778
  client._clientLog.debug(`V2 group push queued behind in-flight realtime sync: ns=${ns} seq=${seq}`);
4632
4779
  return;
4633
4780
  }
4781
+ let pullFailed = false;
4634
4782
  void this.runGroupRealtimeSync(groupId, ns, seq, 50, false).then(async () => {
4635
4783
  if (inlineGeneration !== undefined && !this.inlineGenerationIsCurrent(inlineGeneration))
4636
4784
  return;
4637
4785
  // Tail 期间新到达的 Push 已由 realtime sync 在同一周期内补至多一页 Forward。
4638
4786
  }).catch((exc) => {
4787
+ pullFailed = true;
4639
4788
  if (inlineGeneration !== undefined && !this.inlineGenerationIsCurrent(inlineGeneration))
4640
4789
  return;
4641
4790
  client._clientLog.warn(`V2 group push auto-sync failed: group=${groupId} err=${formatDeliveryError(exc)}`);
4642
4791
  }).finally(() => {
4643
4792
  if (syncGeneration === this.captureInlineAckGeneration()
4644
4793
  && (inlineGeneration === undefined || this.inlineGenerationIsCurrent(inlineGeneration))) {
4645
- this.finishBackgroundPull(ns, syncGeneration, this.syncState(ns).ack, false);
4794
+ this.finishBackgroundPull(ns, syncGeneration, this.syncState(ns).ack, pullFailed, pullTrigger);
4646
4795
  this.endRealtimeSync(ns);
4647
4796
  this.schedulePendingGroupPullIfNeeded(ns, 'push-auto-pull-complete');
4648
4797
  }
@@ -4721,12 +4870,13 @@ export class MessageDeliveryEngine {
4721
4870
  const ns = `p2p:${client._aid}`;
4722
4871
  const generation = this.captureInlineAckGeneration();
4723
4872
  const afterSeq = this.syncState(ns).ack;
4724
- this.recordBackgroundPullNeed(ns, positiveSafeSequenceHint(data.seq), true);
4873
+ this.recordBackgroundPullNeed(ns, positiveSafeSequenceHint(data.seq), true, true);
4874
+ const pullTrigger = this.backgroundPullNeeds.get(ns)?.trigger;
4725
4875
  let failed = false;
4726
4876
  client._safeAsync(this.runP2PForwardRecovery(ns, 50, true).catch((exc) => {
4727
4877
  failed = true;
4728
4878
  client._clientLog.debug(`P2P online unread hint background Forward failed: ns=${ns} err=${formatDeliveryError(exc)}`);
4729
- }).finally(() => this.finishBackgroundPull(ns, generation, afterSeq, failed)));
4879
+ }).finally(() => this.finishBackgroundPull(ns, generation, afterSeq, failed, pullTrigger)));
4730
4880
  }
4731
4881
  async saveSeqTrackerState() {
4732
4882
  const client = this.runtime.client;