@agentunion/fastaun-browser 0.5.17 → 0.5.19

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 (119) hide show
  1. package/CHANGELOG.md +83 -0
  2. package/_packed_docs/CHANGELOG.md +83 -0
  3. package/_packed_docs/INDEX.md +163 -16
  4. package/_packed_docs/KITE_DOCS_GUIDE.md +52 -20
  5. package/_packed_docs/agent.md/SCHEMA.md +266 -207
  6. 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 +64 -31
  7. package/_packed_docs/aun/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +8 -6
  8. 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 +39 -39
  9. 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 +1 -1
  10. package/_packed_docs/protocol/10-Group-/345/255/220/345/215/217/350/256/256.md +28 -9
  11. package/_packed_docs/protocol/11-Storage-/345/255/220/345/215/217/350/256/256.md +20 -2
  12. package/_packed_docs/protocol/agent.md/SCHEMA.md +4 -0
  13. package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +5 -2
  14. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +246 -241
  15. package/_packed_docs/sdk/09-group-rpc-manual.md +46 -14
  16. package/_packed_docs/sdk/09-message-rpc-manual.md +40 -2
  17. package/_packed_docs/sdk/09-storage-rpc-manual.md +69 -38
  18. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +25 -5
  19. package/_packed_docs/sdk/INDEX.md +78 -18
  20. package/dist/agent-md-schema.d.ts +3 -0
  21. package/dist/agent-md-schema.d.ts.map +1 -1
  22. package/dist/agent-md-schema.js +46 -10
  23. package/dist/agent-md-schema.js.map +1 -1
  24. package/dist/agent-md.d.ts +44 -1
  25. package/dist/agent-md.d.ts.map +1 -1
  26. package/dist/agent-md.js +518 -26
  27. package/dist/agent-md.js.map +1 -1
  28. package/dist/aid-store.d.ts +12 -1
  29. package/dist/aid-store.d.ts.map +1 -1
  30. package/dist/aid-store.js +113 -18
  31. package/dist/aid-store.js.map +1 -1
  32. package/dist/aid.d.ts +17 -1
  33. package/dist/aid.d.ts.map +1 -1
  34. package/dist/aid.js +69 -9
  35. package/dist/aid.js.map +1 -1
  36. package/dist/auth.d.ts +4 -1
  37. package/dist/auth.d.ts.map +1 -1
  38. package/dist/auth.js +343 -23
  39. package/dist/auth.js.map +1 -1
  40. package/dist/bundle.js +37513 -31501
  41. package/dist/cert-utils.d.ts +1 -1
  42. package/dist/cert-utils.d.ts.map +1 -1
  43. package/dist/cert-utils.js +24 -10
  44. package/dist/cert-utils.js.map +1 -1
  45. package/dist/client/delivery.d.ts +16 -1
  46. package/dist/client/delivery.d.ts.map +1 -1
  47. package/dist/client/delivery.js +198 -58
  48. package/dist/client/delivery.js.map +1 -1
  49. package/dist/client/rpc-pipeline.d.ts.map +1 -1
  50. package/dist/client/rpc-pipeline.js +28 -6
  51. package/dist/client/rpc-pipeline.js.map +1 -1
  52. package/dist/client/v2-e2ee.d.ts +1 -0
  53. package/dist/client/v2-e2ee.d.ts.map +1 -1
  54. package/dist/client/v2-e2ee.js +858 -484
  55. package/dist/client/v2-e2ee.js.map +1 -1
  56. package/dist/client.d.ts +100 -1
  57. package/dist/client.d.ts.map +1 -1
  58. package/dist/client.js +1216 -24
  59. package/dist/client.js.map +1 -1
  60. package/dist/errors.d.ts.map +1 -1
  61. package/dist/errors.js +3 -2
  62. package/dist/errors.js.map +1 -1
  63. package/dist/events.d.ts +4 -0
  64. package/dist/events.d.ts.map +1 -1
  65. package/dist/events.js +41 -1
  66. package/dist/events.js.map +1 -1
  67. package/dist/facades.d.ts +84 -2
  68. package/dist/facades.d.ts.map +1 -1
  69. package/dist/facades.js +104 -4
  70. package/dist/facades.js.map +1 -1
  71. package/dist/index.d.ts +8 -3
  72. package/dist/index.d.ts.map +1 -1
  73. package/dist/index.js +3 -1
  74. package/dist/index.js.map +1 -1
  75. package/dist/keystore/index.d.ts +17 -0
  76. package/dist/keystore/index.d.ts.map +1 -1
  77. package/dist/keystore/indexeddb-shared.d.ts +10 -1
  78. package/dist/keystore/indexeddb-shared.d.ts.map +1 -1
  79. package/dist/keystore/indexeddb-shared.js +111 -0
  80. package/dist/keystore/indexeddb-shared.js.map +1 -1
  81. package/dist/keystore/indexeddb-token-store.d.ts +7 -1
  82. package/dist/keystore/indexeddb-token-store.d.ts.map +1 -1
  83. package/dist/keystore/indexeddb-token-store.js +30 -1
  84. package/dist/keystore/indexeddb-token-store.js.map +1 -1
  85. package/dist/org-protocol.d.ts +98 -0
  86. package/dist/org-protocol.d.ts.map +1 -0
  87. package/dist/org-protocol.js +1742 -0
  88. package/dist/org-protocol.js.map +1 -0
  89. package/dist/ownership-proof.d.ts +118 -0
  90. package/dist/ownership-proof.d.ts.map +1 -0
  91. package/dist/ownership-proof.js +283 -0
  92. package/dist/ownership-proof.js.map +1 -0
  93. package/dist/ownership-v21.d.ts +177 -0
  94. package/dist/ownership-v21.d.ts.map +1 -0
  95. package/dist/ownership-v21.js +609 -0
  96. package/dist/ownership-v21.js.map +1 -0
  97. package/dist/ownership-workflow.d.ts +146 -0
  98. package/dist/ownership-workflow.d.ts.map +1 -0
  99. package/dist/ownership-workflow.js +1066 -0
  100. package/dist/ownership-workflow.js.map +1 -0
  101. package/dist/seq-tracker.d.ts +1 -0
  102. package/dist/seq-tracker.d.ts.map +1 -1
  103. package/dist/seq-tracker.js +10 -2
  104. package/dist/seq-tracker.js.map +1 -1
  105. package/dist/tools/cross-sdk-agent.d.ts +82 -0
  106. package/dist/tools/cross-sdk-agent.d.ts.map +1 -1
  107. package/dist/tools/cross-sdk-agent.js +332 -7
  108. package/dist/tools/cross-sdk-agent.js.map +1 -1
  109. package/dist/transport.d.ts +6 -0
  110. package/dist/transport.d.ts.map +1 -1
  111. package/dist/transport.js +88 -2
  112. package/dist/transport.js.map +1 -1
  113. package/dist/v2/e2ee/decrypt.d.ts +1 -0
  114. package/dist/v2/e2ee/decrypt.d.ts.map +1 -1
  115. package/dist/v2/e2ee/decrypt.js +9 -1
  116. package/dist/v2/e2ee/decrypt.js.map +1 -1
  117. package/dist/version.d.ts +1 -1
  118. package/dist/version.js +1 -1
  119. package/package.json +5 -5
@@ -1,5 +1,6 @@
1
1
  import { slotIsolationKey } from '../config.js';
2
2
  import { normalizeGroupAid } from '../group-id.js';
3
+ import { SeqTracker } from '../seq-tracker.js';
3
4
  import { ConnectionState, isJsonObject } from '../types.js';
4
5
  import { hasGroupMentionModeField, normalizeGroupMentionMode } from './mention-mode.js';
5
6
  const PUSHED_SEQS_LIMIT = 50_000;
@@ -136,6 +137,7 @@ export class MessageDeliveryEngine {
136
137
  pendingP2PInlineAcks = null;
137
138
  pendingGroupInlineAcks = null;
138
139
  pendingGroupInlineAckNamespaces = null;
140
+ pendingTailWindows = new Map();
139
141
  volatileContext = '';
140
142
  volatilePullCursorValue = '';
141
143
  volatileSeenMessages = new Map();
@@ -164,6 +166,7 @@ export class MessageDeliveryEngine {
164
166
  this.pendingP2PInlineAcks = null;
165
167
  this.pendingGroupInlineAcks = null;
166
168
  this.pendingGroupInlineAckNamespaces = null;
169
+ this.pendingTailWindows.clear();
167
170
  this.syncingNamespaces.clear();
168
171
  this.syncSessions.clear();
169
172
  }
@@ -186,6 +189,35 @@ export class MessageDeliveryEngine {
186
189
  captureInlineGeneration() {
187
190
  return this.inlineGeneration;
188
191
  }
192
+ deferTailWindow(ns, proof) {
193
+ const tracker = new SeqTracker();
194
+ tracker.restoreNamespaceSnapshot(ns, this.runtime.client._seqTracker.snapshotNamespace(ns));
195
+ tracker.commitTailWindow(ns, proof);
196
+ this.pendingTailWindows.set(ns, proof);
197
+ }
198
+ prospectiveTailState(ns) {
199
+ const proof = this.pendingTailWindows.get(ns);
200
+ if (!proof)
201
+ return this.syncState(ns);
202
+ const tracker = new SeqTracker();
203
+ tracker.restoreNamespaceSnapshot(ns, this.runtime.client._seqTracker.snapshotNamespace(ns));
204
+ tracker.commitTailWindow(ns, proof);
205
+ return tracker.getSyncState(ns);
206
+ }
207
+ pendingTailDeliveryThrough(ns, scanThrough) {
208
+ const proof = this.pendingTailWindows.get(ns);
209
+ return proof && scanThrough >= proof.windowStartSeq - 1 ? Math.max(scanThrough, proof.headSeq) : scanThrough;
210
+ }
211
+ commitPendingTailWindow(ns, scanThrough) {
212
+ const proof = this.pendingTailWindows.get(ns);
213
+ if (proof && scanThrough >= proof.windowStartSeq - 1)
214
+ this.runtime.client._seqTracker.commitTailWindow(ns, proof);
215
+ }
216
+ completePendingTailWindow(ns) {
217
+ const proof = this.pendingTailWindows.get(ns);
218
+ if (proof && this.runtime.client._seqTracker.getContiguousSeq(ns) >= proof.headSeq)
219
+ this.pendingTailWindows.delete(ns);
220
+ }
189
221
  beginRealtimeSync(ns) {
190
222
  if (!ns || this.realtimeSyncing?.has(ns))
191
223
  return false;
@@ -295,6 +327,18 @@ export class MessageDeliveryEngine {
295
327
  return;
296
328
  this.schedulePendingPullIfNeeded(ns, 'pull-gate-idle');
297
329
  }
330
+ onForegroundPullsDrained() {
331
+ const client = this.runtime.client;
332
+ if (!client._aid || client._sessionOptions?.background_sync === false
333
+ || client.state !== ConnectionState.READY || client._closing)
334
+ return;
335
+ const ns = `p2p:${client._aid}`;
336
+ const state = this.syncState(ns);
337
+ const maxSeen = client._seqTracker.getMaxSeenSeq(ns);
338
+ if (maxSeen <= state.ack && state.ack >= state.tail - 1)
339
+ return;
340
+ client._safeAsync(this.fillP2pGap());
341
+ }
298
342
  onPullWorkSettled() {
299
343
  const pipeline = this.runtime.client._rpcPipeline;
300
344
  if (pipeline?.hasAnyPullActivity?.() === true)
@@ -952,9 +996,7 @@ export class MessageDeliveryEngine {
952
996
  const pageInlineGeneration = this.captureInlineGeneration();
953
997
  const deferredServerCursor = Number(coordinator.pendingForwardCursor(ns, pageContigBefore) ?? 0);
954
998
  const deferredForwardAck = Number(coordinator.pendingForwardAck(ns, pageContigBefore) ?? 0);
955
- const pageTrackerSnapshot = typeof client._seqTracker.snapshotNamespace === 'function'
956
- ? client._seqTracker.snapshotNamespace(ns)
957
- : null;
999
+ let pageTrackerSnapshot;
958
1000
  response._contig_before = pageContigBefore;
959
1001
  const cursor = isJsonObject(response.cursor)
960
1002
  ? response.cursor
@@ -1005,6 +1047,15 @@ export class MessageDeliveryEngine {
1005
1047
  const contiguousSeq = Number.isSafeInteger(result.contiguous_seq)
1006
1048
  ? result.contiguous_seq
1007
1049
  : undefined;
1050
+ const scanThrough = Math.max(pageContigBefore, contiguousSeq ?? 0, retentionFloor, visibilityFloor, deferredServerCursor, ...messages.map((message) => positiveSafeSequenceHint(message.seq)));
1051
+ const state = this.syncState(ns);
1052
+ const deliveryThrough = this.pendingTailDeliveryThrough(ns, scanThrough >= state.tail - 1 ? Math.max(scanThrough, state.head) : scanThrough);
1053
+ await this.drainOrderedMessages(ns, undefined, false, false, undefined, 'pull', deliveryThrough);
1054
+ this.ensurePullOperationCurrent(ns);
1055
+ if (!this.isInlineGenerationCurrent(pageInlineGeneration))
1056
+ throw new Error('pull invalidated');
1057
+ pageTrackerSnapshot = client._seqTracker.snapshotNamespace(ns);
1058
+ this.commitPendingTailWindow(ns, scanThrough);
1008
1059
  if (contiguousSeq === undefined)
1009
1060
  client._seqTracker.onPullResult(ns, messages, afterSeq);
1010
1061
  else
@@ -1014,16 +1065,19 @@ export class MessageDeliveryEngine {
1014
1065
  client._seqTracker.forceContiguousSeq(ns, commitTarget);
1015
1066
  }
1016
1067
  if (client._seqTracker.getContiguousSeq(ns) !== pageContigBefore) {
1017
- this.ensurePullOperationCurrent(ns);
1018
- await this.drainOrderedMessages(ns, undefined, false, false);
1019
1068
  this.ensurePullOperationCurrent(ns);
1020
1069
  await client._commitSeqTrackerState(ns);
1070
+ this.ensurePullOperationCurrent(ns);
1071
+ if (!this.isInlineGenerationCurrent(pageInlineGeneration))
1072
+ throw new Error('pull invalidated');
1021
1073
  }
1074
+ this.completePendingTailWindow(ns);
1022
1075
  committed = true;
1023
1076
  }
1024
1077
  catch (exc) {
1025
- if (this.isPullOperationCurrent(ns) && !committed && typeof client._seqTracker.restoreNamespaceSnapshot === 'function') {
1026
- client._seqTracker.restoreNamespaceSnapshot(ns, pageTrackerSnapshot);
1078
+ if (this.isPullOperationCurrent(ns) && this.isInlineGenerationCurrent(pageInlineGeneration)
1079
+ && pageTrackerSnapshot !== undefined && !committed && typeof client._seqTracker.restoreNamespaceSnapshot === 'function') {
1080
+ client._seqTracker._restoreNamespaceSnapshotPreservingPush(ns, pageTrackerSnapshot);
1027
1081
  this.dropSeqTrackerPending(ns);
1028
1082
  }
1029
1083
  throw exc;
@@ -1103,6 +1157,8 @@ export class MessageDeliveryEngine {
1103
1157
  }
1104
1158
  if (pullResponse) {
1105
1159
  await client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload, source, ns, batch));
1160
+ if (pullScoped)
1161
+ this.ensurePullOperationCurrent(ns);
1106
1162
  return;
1107
1163
  }
1108
1164
  await client._publishAppEvent(event, payload, source, ns, batch);
@@ -1111,6 +1167,10 @@ export class MessageDeliveryEngine {
1111
1167
  }
1112
1168
  async publishOrderedGroupChanged(payload, source = 'ordered') {
1113
1169
  const client = this.runtime.client;
1170
+ const generation = this.captureInlineGeneration();
1171
+ await client._publishAppEvent('group.changed', payload, source);
1172
+ if (!this.isInlineGenerationCurrent(generation))
1173
+ throw new Error('pull invalidated');
1114
1174
  if (isJsonObject(payload)) {
1115
1175
  const eventPayload = payload;
1116
1176
  client._groupState?.handleGroupChangedV2Membership?.(eventPayload);
@@ -1121,7 +1181,6 @@ export class MessageDeliveryEngine {
1121
1181
  client._cleanupDissolvedGroup?.(groupId);
1122
1182
  }
1123
1183
  }
1124
- await client._publishAppEvent('group.changed', payload, source);
1125
1184
  }
1126
1185
  isInstanceScopedMessageEvent(event) {
1127
1186
  return event === 'message.received'
@@ -1559,6 +1618,7 @@ export class MessageDeliveryEngine {
1559
1618
  }
1560
1619
  async publishMessageRecallTombstone(seq, message, source = 'push') {
1561
1620
  const client = this.runtime.client;
1621
+ const generation = this.captureInlineGeneration();
1562
1622
  const eventPayload = this.recallEventFromMessage(message);
1563
1623
  if (!eventPayload)
1564
1624
  return false;
@@ -1572,13 +1632,15 @@ export class MessageDeliveryEngine {
1572
1632
  client._clientLog.debug(`message.recalled dedup suppressed: seq=${String(seq)} key=${dedupKey}`);
1573
1633
  return false;
1574
1634
  }
1635
+ await client._publishAppEvent('message.recalled', eventPayload, source);
1636
+ if (!this.isInlineGenerationCurrent(generation))
1637
+ throw new Error('pull invalidated');
1575
1638
  seen.set(dedupKey, Date.now());
1576
1639
  if (seen.size > MESSAGE_RECALL_SEEN_LIMIT) {
1577
1640
  const drop = [...seen.entries()].sort((a, b) => a[1] - b[1]).slice(0, seen.size - MESSAGE_RECALL_SEEN_LIMIT);
1578
1641
  for (const [oldKey] of drop)
1579
1642
  seen.delete(oldKey);
1580
1643
  }
1581
- await client._publishAppEvent('message.recalled', eventPayload, source);
1582
1644
  client._clientLog.debug(`message.recalled published: seq=${String(seq)} ids=${JSON.stringify(eventPayload.message_ids)}`);
1583
1645
  return true;
1584
1646
  }
@@ -1663,6 +1725,7 @@ export class MessageDeliveryEngine {
1663
1725
  }
1664
1726
  async publishGroupRecallTombstone(groupId, seq, message, source = 'push') {
1665
1727
  const client = this.runtime.client;
1728
+ const generation = this.captureInlineGeneration();
1666
1729
  const eventPayload = this.recallEventFromGroupMessage(message);
1667
1730
  if (!eventPayload)
1668
1731
  return false;
@@ -1678,13 +1741,15 @@ export class MessageDeliveryEngine {
1678
1741
  client._clientLog.debug(`group.message_recalled dedup suppressed: group=${groupId} seq=${String(seq)} key=${dedupKey}`);
1679
1742
  return false;
1680
1743
  }
1744
+ await client._publishAppEvent('group.message_recalled', eventPayload, source);
1745
+ if (!this.isInlineGenerationCurrent(generation))
1746
+ throw new Error('pull invalidated');
1681
1747
  seen.set(dedupKey, Date.now());
1682
1748
  if (seen.size > GROUP_RECALL_SEEN_LIMIT) {
1683
1749
  const drop = [...seen.entries()].sort((a, b) => a[1] - b[1]).slice(0, seen.size - GROUP_RECALL_SEEN_LIMIT);
1684
1750
  for (const [oldKey] of drop)
1685
1751
  seen.delete(oldKey);
1686
1752
  }
1687
- await client._publishAppEvent('group.message_recalled', eventPayload, source);
1688
1753
  client._clientLog.debug(`group.message_recalled published: group=${groupId} seq=${String(seq)} ids=${JSON.stringify(eventPayload.message_ids)}`);
1689
1754
  return true;
1690
1755
  }
@@ -1755,7 +1820,13 @@ export class MessageDeliveryEngine {
1755
1820
  && canonicalSource !== 'direct') {
1756
1821
  normalized = { ...normalized, source: canonicalSource };
1757
1822
  }
1758
- client._dispatcher.enqueue(event, normalized);
1823
+ if (['message.received', 'group.message_created', 'message.recalled', 'group.message_recalled', 'group.changed'].includes(event)
1824
+ && typeof client._dispatcher._publishAndWait === 'function') {
1825
+ await client._dispatcher._publishAndWait(event, normalized);
1826
+ }
1827
+ else {
1828
+ client._dispatcher.enqueue(event, normalized);
1829
+ }
1759
1830
  if (generation !== this.deliveryGeneration)
1760
1831
  return;
1761
1832
  if (canonicalSource !== 'pull' && canonicalSource !== 'pending_retry'
@@ -1820,6 +1891,24 @@ export class MessageDeliveryEngine {
1820
1891
  return false;
1821
1892
  return senderDevice.trim() === String(client._deviceId).trim();
1822
1893
  }
1894
+ isSelfSentP2PMessage(message) {
1895
+ if (!isJsonObject(message))
1896
+ return false;
1897
+ const client = this.runtime.client;
1898
+ if (String(message.direction ?? '').trim() !== 'outbound_sync')
1899
+ return false;
1900
+ const selfAid = String(client._aid ?? '').trim().toLowerCase();
1901
+ const senderAid = String(message.sender_aid ?? message.from_aid ?? message.from ?? '').trim().toLowerCase();
1902
+ if (!selfAid || senderAid !== selfAid)
1903
+ return false;
1904
+ const senderDevice = Object.prototype.hasOwnProperty.call(message, 'sender_device_id')
1905
+ ? message.sender_device_id
1906
+ : message.from_device_id;
1907
+ return typeof senderDevice === 'string'
1908
+ && Boolean(senderDevice.trim())
1909
+ && Boolean(String(client._deviceId ?? '').trim())
1910
+ && senderDevice.trim() === String(client._deviceId).trim();
1911
+ }
1823
1912
  strictTargetString(source, key) {
1824
1913
  if (!Object.prototype.hasOwnProperty.call(source, key)) {
1825
1914
  return { present: false, valid: false, value: '' };
@@ -1946,15 +2035,19 @@ export class MessageDeliveryEngine {
1946
2035
  await this.processV2P2PNotification(msg, 'v1', inlineGeneration);
1947
2036
  return;
1948
2037
  }
2038
+ const encryptedPush = client._isEncryptedPushMessage(msg);
1949
2039
  if (client._aid && legacyPushSeq > 0) {
1950
2040
  const ns = `p2p:${client._aid}`;
1951
2041
  client._seqTracker.updateMaxSeen(ns, legacyPushSeq);
1952
- if (client._sessionOptions?.background_sync !== false)
1953
- client._safeAsync(this.fillP2pGap());
2042
+ if (encryptedPush) {
2043
+ if (client._sessionOptions?.background_sync !== false)
2044
+ client._safeAsync(this.fillP2pGap());
2045
+ return;
2046
+ }
2047
+ await this.publishPulledMessage('message.received', ns, legacyPushSeq, msg, true, 'push');
1954
2048
  return;
1955
2049
  }
1956
2050
  const seq = msg.seq;
1957
- const encryptedPush = client._isEncryptedPushMessage(msg);
1958
2051
  const ns = client._aid ? `p2p:${client._aid}` : '';
1959
2052
  const seqNum = positiveSafeSequenceHint(seq);
1960
2053
  if (ns && seqNum > 0) {
@@ -1970,11 +2063,13 @@ export class MessageDeliveryEngine {
1970
2063
  if (ns && seq !== undefined && seq !== null) {
1971
2064
  await this.publishOrderedMessage('message.received', ns, seq, msg);
1972
2065
  }
1973
- else {
2066
+ else if (!this.isSelfSentP2PMessage(msg)) {
1974
2067
  await client._publishAppEvent('message.received', msg, 'push');
1975
2068
  }
1976
2069
  }
1977
2070
  catch (exc) {
2071
+ if (exc?.stringCode === 'persistence_unavailable')
2072
+ throw exc;
1978
2073
  client._clientLog.warn(`P2P push processing failed:${String(exc)}`);
1979
2074
  if (isJsonObject(data)) {
1980
2075
  const src = data;
@@ -2035,10 +2130,19 @@ export class MessageDeliveryEngine {
2035
2130
  if (groupId && legacyPushSeq > 0) {
2036
2131
  const ns = `group:${groupId}`;
2037
2132
  client._seqTracker.updateMaxSeen(ns, legacyPushSeq);
2038
- if (client._sessionOptions?.background_sync !== false)
2133
+ if (client._sessionOptions?.background_sync === false)
2134
+ return;
2135
+ const encryptedPush = client._isEncryptedPushMessage(msg);
2136
+ if (encryptedPush) {
2039
2137
  client._safeAsync(this.fillGroupGap(groupId));
2138
+ return;
2139
+ }
2140
+ if (!this.isSelfSentGroupMessage(msg)) {
2141
+ await this.publishPulledMessage('group.message_created', ns, legacyPushSeq, msg, true, 'push');
2142
+ }
2040
2143
  return;
2041
2144
  }
2145
+ const encryptedPush = client._isEncryptedPushMessage(msg);
2042
2146
  if (payload === undefined || payload === null
2043
2147
  || (typeof payload === 'object' && Object.keys(payload).length === 0)) {
2044
2148
  if (client._v2Session)
@@ -2058,7 +2162,6 @@ export class MessageDeliveryEngine {
2058
2162
  await client._publishAppEvent('group.message_created', msg, 'push');
2059
2163
  return;
2060
2164
  }
2061
- const encryptedPush = client._isEncryptedPushMessage(msg);
2062
2165
  const ns = groupId ? `group:${groupId}` : '';
2063
2166
  const seqNum = positiveSafeSequenceHint(seq);
2064
2167
  if (ns && seqNum > 0) {
@@ -2270,6 +2373,7 @@ export class MessageDeliveryEngine {
2270
2373
  }
2271
2374
  async fillGroupEventGap(groupId, options) {
2272
2375
  const client = this.runtime.client;
2376
+ const generation = this.captureInlineGeneration();
2273
2377
  if (client._sessionOptions?.background_sync === false)
2274
2378
  return;
2275
2379
  if (client.state !== ConnectionState.READY || client._closing)
@@ -2313,23 +2417,12 @@ export class MessageDeliveryEngine {
2313
2417
  const pageContigBefore = client._seqTracker.getContiguousSeq(ns);
2314
2418
  const eventObjects = events.filter((evt) => isJsonObject(evt));
2315
2419
  const contiguousSeq = Number.isSafeInteger(result.contiguous_seq) ? result.contiguous_seq : undefined;
2316
- if (contiguousSeq === undefined)
2317
- client._seqTracker.onPullResult(ns, eventObjects, nextAfterSeq);
2318
- else
2319
- client._seqTracker.onPullResult(ns, eventObjects, nextAfterSeq, contiguousSeq);
2320
2420
  const cursor = isJsonObject(result.cursor) ? result.cursor : null;
2321
2421
  const retentionFloor = Math.max(0, Number(result.retention_floor_event_seq ?? 0), Number(cursor?.retention_floor_seq ?? 0));
2322
2422
  const serverAckFloor = Math.max(0, Number(cursor?.current_seq ?? 0));
2323
2423
  const earliestAvailable = Number(result.earliest_available_event_seq ?? 0);
2324
2424
  const visibilityFloor = Math.max(0, Number(cursor?.join_seq ?? 0), Number.isFinite(earliestAvailable) && earliestAvailable > 0 ? earliestAvailable - 1 : 0);
2325
2425
  const effectiveFloor = Math.max(...[retentionFloor, serverAckFloor, visibilityFloor].filter((value) => Number.isFinite(value)));
2326
- if (effectiveFloor > 0) {
2327
- const contigBeforeFloor = client._seqTracker.getContiguousSeq(ns);
2328
- if (contigBeforeFloor < effectiveFloor) {
2329
- client._clientLog.info('group.pull_events floor advance: ns=' + ns + ' contiguous=' + contigBeforeFloor + ' -> effective_floor=' + effectiveFloor + ' retention_floor=' + retentionFloor + ' server_ack=' + serverAckFloor + ' visibility_floor=' + visibilityFloor);
2330
- client._seqTracker.forceContiguousSeq(ns, effectiveFloor);
2331
- }
2332
- }
2333
2426
  const eventSeqs = [];
2334
2427
  let hasDissolvedEvent = false;
2335
2428
  let publishedEventCount = 0;
@@ -2358,8 +2451,20 @@ export class MessageDeliveryEngine {
2358
2451
  publishedEventCount += 1;
2359
2452
  }
2360
2453
  }
2454
+ const deliveryThrough = Math.max(pageContigBefore, contiguousSeq ?? 0, effectiveFloor, ...eventSeqs);
2455
+ await this.drainOrderedMessages(ns, undefined, false, false, undefined, 'pull', deliveryThrough);
2456
+ if (!this.isInlineGenerationCurrent(generation))
2457
+ throw new Error('pull invalidated');
2458
+ if (hasDissolvedEvent)
2459
+ break;
2460
+ if (contiguousSeq === undefined)
2461
+ client._seqTracker.onPullResult(ns, eventObjects, nextAfterSeq);
2462
+ else
2463
+ client._seqTracker.onPullResult(ns, eventObjects, nextAfterSeq, contiguousSeq);
2464
+ if (effectiveFloor > client._seqTracker.getContiguousSeq(ns)) {
2465
+ client._seqTracker.forceContiguousSeq(ns, effectiveFloor);
2466
+ }
2361
2467
  const ackContig = client._seqTracker.getContiguousSeq(ns);
2362
- await this.drainOrderedMessages(ns);
2363
2468
  if (ackContig !== pageContigBefore && !hasDissolvedEvent) {
2364
2469
  this.persistSeq(ns);
2365
2470
  }
@@ -2849,15 +2954,20 @@ export class MessageDeliveryEngine {
2849
2954
  async commitInlineMessage(ns, seq, event, payload, generation) {
2850
2955
  const client = this.runtime.client;
2851
2956
  const tracker = client._seqTracker;
2852
- const snapshot = typeof tracker.snapshotNamespace === 'function'
2853
- ? tracker.snapshotNamespace(ns)
2854
- : null;
2957
+ let snapshot = null;
2958
+ let trackerChanged = false;
2855
2959
  try {
2856
- const published = await this.publishPulledMessage(event, ns, seq, payload, true, 'inline_push');
2960
+ const selfSync = (event === 'message.received' && this.isSelfSentP2PMessage(payload))
2961
+ || (event === 'group.message_created' && this.isSelfSentGroupMessage(payload));
2962
+ const published = selfSync
2963
+ ? (this.markPublishedSeq(ns, seq), true)
2964
+ : await this.publishPulledMessage(event, ns, seq, payload, true, 'inline_push');
2857
2965
  if (!this.isInlineGenerationCurrent(generation))
2858
2966
  return false;
2859
2967
  if (!published)
2860
2968
  return false;
2969
+ snapshot = typeof tracker.snapshotNamespace === 'function' ? tracker.snapshotNamespace(ns) : null;
2970
+ trackerChanged = true;
2861
2971
  const needsPull = Boolean(tracker.onMessageSeq(ns, seq));
2862
2972
  const committed = this.syncState(ns);
2863
2973
  if (needsPull || committed.ack !== seq || committed.tail !== seq || committed.head !== seq) {
@@ -2871,8 +2981,8 @@ export class MessageDeliveryEngine {
2871
2981
  return true;
2872
2982
  }
2873
2983
  catch (exc) {
2874
- if (this.isInlineGenerationCurrent(generation) && typeof tracker.restoreNamespaceSnapshot === 'function') {
2875
- tracker.restoreNamespaceSnapshot(ns, snapshot);
2984
+ if (trackerChanged && this.isInlineGenerationCurrent(generation) && typeof tracker.restoreNamespaceSnapshot === 'function') {
2985
+ tracker._restoreNamespaceSnapshotPreservingPush(ns, snapshot);
2876
2986
  }
2877
2987
  client._clientLog.debug(`inline realtime consume rolled back: ns=${ns} seq=${seq} err=${formatDeliveryError(exc)}`);
2878
2988
  return false;
@@ -2961,7 +3071,7 @@ export class MessageDeliveryEngine {
2961
3071
  }
2962
3072
  }
2963
3073
  hasOutstandingRealtimeGap(ns) {
2964
- const state = this.syncState(ns);
3074
+ const state = this.prospectiveTailState(ns);
2965
3075
  // Tail 已建立可靠后缀窗口;后台续拉只针对尚未接上窗口的真实缺口。
2966
3076
  // maxSeen 可能只是 Tail 期间到达的通知,不能单独据此启动全量补洞。
2967
3077
  return state.ack < state.tail - 1;
@@ -3017,8 +3127,8 @@ export class MessageDeliveryEngine {
3017
3127
  }
3018
3128
  async runP2PRealtimeSync(ns, pushSeq, pageLimit = 50, forceTail = false) {
3019
3129
  const client = this.runtime.client;
3020
- const before = this.syncState(ns);
3021
- const order = forceTail ? ['tail'] : typeof client._seqTracker.planRealtimeSync === 'function'
3130
+ const before = this.prospectiveTailState(ns);
3131
+ const order = forceTail ? ['tail'] : this.pendingTailWindows.has(ns) ? ['forward'] : typeof client._seqTracker.planRealtimeSync === 'function'
3022
3132
  ? client._seqTracker.planRealtimeSync(ns, pushSeq, pageLimit)
3023
3133
  : (() => {
3024
3134
  const gap = pushSeq - before.ack;
@@ -3031,7 +3141,7 @@ export class MessageDeliveryEngine {
3031
3141
  let result = { messages: [], raw_count: 0 };
3032
3142
  let tailCompleted = false;
3033
3143
  const run = async (tail, background, maxPages, forceForward = false, bypassCoveredHead = false) => {
3034
- const live = this.syncState(ns);
3144
+ const live = this.prospectiveTailState(ns);
3035
3145
  if (tail && !bypassCoveredHead && pushSeq <= live.head)
3036
3146
  return { messages: [], raw_count: 0 };
3037
3147
  if (!tail && !forceForward && live.ack >= live.tail - 1)
@@ -3055,7 +3165,7 @@ export class MessageDeliveryEngine {
3055
3165
  };
3056
3166
  if (forceTail || order[0] === 'tail')
3057
3167
  result = await run(true, false, undefined, false, forceTail);
3058
- const state = this.syncState(ns);
3168
+ const state = this.prospectiveTailState(ns);
3059
3169
  const maxSeen = Number(client._seqTracker.getMaxSeenSeq(ns) || 0);
3060
3170
  // Tail 期间到达的新 Push 只记录在 pending Pull 上界,不写入 SeqTracker。
3061
3171
  const pendingUpper = Number(this.pendingP2pPullUpper?.get(ns) ?? 0);
@@ -3070,16 +3180,16 @@ export class MessageDeliveryEngine {
3070
3180
  || (client._sessionOptions?.background_sync !== false && backgroundWindowForward)) {
3071
3181
  const forwardTarget = Math.max(headForward ? Math.max(pushSeq, maxSeen) : 0, (tailForward || backgroundWindowForward) ? state.tail - 1 : 0, tailMissedDuringPull ? observedUpper : 0);
3072
3182
  const forwardMaxPages = this.forwardMaxPages(state.ack, forwardTarget, pageLimit);
3073
- result = await run(false, !(headForward || tailForward), forwardMaxPages, headForward || tailMissedDuringPull);
3183
+ result = await run(false, !(headForward || tailForward), forwardMaxPages, headForward || tailForward || tailMissedDuringPull);
3074
3184
  if (pendingThroughSeq > 0) {
3075
- const committedAck = this.syncState(ns).ack;
3185
+ const committedAck = this.prospectiveTailState(ns).ack;
3076
3186
  if (committedAck >= pendingThroughSeq)
3077
3187
  this.consumePendingPull(ns, pendingThroughSeq);
3078
3188
  else if (committedAck <= state.ack)
3079
3189
  this.markPendingPullNoProgress(ns, committedAck);
3080
3190
  }
3081
3191
  }
3082
- const finalState = this.syncState(ns);
3192
+ const finalState = this.prospectiveTailState(ns);
3083
3193
  if (tailCompleted && finalState.ack < finalState.tail - 1) {
3084
3194
  this.scheduleP2PRealtimeFullGapFill(ns);
3085
3195
  }
@@ -3089,8 +3199,8 @@ export class MessageDeliveryEngine {
3089
3199
  }
3090
3200
  async runGroupRealtimeSync(groupId, ns, pushSeq, pageLimit = 50, forceTail = false) {
3091
3201
  const client = this.runtime.client;
3092
- const before = this.syncState(ns);
3093
- const order = forceTail ? ['tail'] : typeof client._seqTracker.planRealtimeSync === 'function'
3202
+ const before = this.prospectiveTailState(ns);
3203
+ const order = forceTail ? ['tail'] : this.pendingTailWindows.has(ns) ? ['forward'] : typeof client._seqTracker.planRealtimeSync === 'function'
3094
3204
  ? client._seqTracker.planRealtimeSync(ns, pushSeq, pageLimit)
3095
3205
  : (() => {
3096
3206
  const gap = pushSeq - before.ack;
@@ -3103,7 +3213,7 @@ export class MessageDeliveryEngine {
3103
3213
  let result = { messages: [], raw_count: 0 };
3104
3214
  let tailCompleted = false;
3105
3215
  const run = async (tail, background, maxPages, forceForward = false, bypassCoveredHead = false) => {
3106
- const live = this.syncState(ns);
3216
+ const live = this.prospectiveTailState(ns);
3107
3217
  if (tail && !bypassCoveredHead && pushSeq <= live.head)
3108
3218
  return { messages: [], raw_count: 0 };
3109
3219
  if (!tail && !forceForward && live.ack >= live.tail - 1)
@@ -3127,7 +3237,7 @@ export class MessageDeliveryEngine {
3127
3237
  };
3128
3238
  if (forceTail || order[0] === 'tail')
3129
3239
  result = await run(true, false, undefined, false, forceTail);
3130
- const state = this.syncState(ns);
3240
+ const state = this.prospectiveTailState(ns);
3131
3241
  // 与 P2P 相同:真实完成 Tail 后,至多用一页 Forward 收敛 Tail
3132
3242
  // 期间新到达的序号;绝不重新规划或再执行 Tail。
3133
3243
  const maxSeen = Number(client._seqTracker.getMaxSeenSeq(ns) || 0);
@@ -3143,16 +3253,16 @@ export class MessageDeliveryEngine {
3143
3253
  || (client._sessionOptions?.background_sync !== false && backgroundWindowForward)) {
3144
3254
  const forwardTarget = Math.max(headForward ? Math.max(pushSeq, maxSeen) : 0, (tailForward || backgroundWindowForward) ? state.tail - 1 : 0, tailMissedDuringPull ? observedUpper : 0);
3145
3255
  const forwardMaxPages = this.forwardMaxPages(state.ack, forwardTarget, pageLimit);
3146
- result = await run(false, !(headForward || tailForward), forwardMaxPages, headForward || tailMissedDuringPull);
3256
+ result = await run(false, !(headForward || tailForward), forwardMaxPages, headForward || tailForward || tailMissedDuringPull);
3147
3257
  if (pendingThroughSeq > 0) {
3148
- const committedAck = this.syncState(ns).ack;
3258
+ const committedAck = this.prospectiveTailState(ns).ack;
3149
3259
  if (committedAck >= pendingThroughSeq)
3150
3260
  this.consumePendingPull(ns, pendingThroughSeq);
3151
3261
  else if (committedAck <= state.ack)
3152
3262
  this.markPendingPullNoProgress(ns, committedAck);
3153
3263
  }
3154
3264
  }
3155
- const finalState = this.syncState(ns);
3265
+ const finalState = this.prospectiveTailState(ns);
3156
3266
  if (tailCompleted && finalState.ack < finalState.tail - 1) {
3157
3267
  this.scheduleGroupRealtimeFullGapFill(groupId, ns);
3158
3268
  }
@@ -3769,7 +3879,7 @@ export class MessageDeliveryEngine {
3769
3879
  }
3770
3880
  return params;
3771
3881
  }
3772
- async drainOrderedMessages(ns, beforeSeq, pullResponse = false, persist = true, batch, source = 'pull') {
3882
+ async drainOrderedMessages(ns, beforeSeq, pullResponse = false, persist = true, batch, source = 'pull', throughSeq) {
3773
3883
  const client = this.runtime.client;
3774
3884
  const pullScoped = this.isPullScopedSource(source);
3775
3885
  if (pullScoped)
@@ -3777,7 +3887,8 @@ export class MessageDeliveryEngine {
3777
3887
  const queue = client._pendingOrderedMsgs.get(ns);
3778
3888
  if (!queue || queue.size === 0)
3779
3889
  return;
3780
- const contig = client._seqTracker.getContiguousSeq(ns);
3890
+ const generation = this.captureInlineGeneration();
3891
+ const contig = throughSeq ?? client._seqTracker.getContiguousSeq(ns);
3781
3892
  const ready = [...queue.keys()]
3782
3893
  .filter((seq) => seq <= contig && (beforeSeq === undefined || seq < beforeSeq))
3783
3894
  .sort((a, b) => a - b);
@@ -3788,9 +3899,12 @@ export class MessageDeliveryEngine {
3788
3899
  if (pullScoped)
3789
3900
  this.ensurePullOperationCurrent(ns);
3790
3901
  const item = queue.get(seq);
3791
- queue.delete(seq);
3792
- if (!item || client._pushedSeqs.get(ns)?.has(seq))
3902
+ if (!item)
3793
3903
  continue;
3904
+ if (client._pushedSeqs.get(ns)?.has(seq)) {
3905
+ queue.delete(seq);
3906
+ continue;
3907
+ }
3794
3908
  const itemSource = item.source ?? source;
3795
3909
  let itemBatch = batch;
3796
3910
  if ((itemSource === 'push' || itemSource === 'inline_push')
@@ -3802,8 +3916,13 @@ export class MessageDeliveryEngine {
3802
3916
  }
3803
3917
  }
3804
3918
  await this.publishOrderedQueueItem(ns, item.event, seq, item.payload, pullResponse, itemSource, itemBatch);
3919
+ if (!this.isInlineGenerationCurrent(generation))
3920
+ throw new Error('pull invalidated');
3805
3921
  if (pullScoped)
3806
3922
  this.ensurePullOperationCurrent(ns);
3923
+ queue.delete(seq);
3924
+ if (item.event === 'group.changed' && isJsonObject(item.payload) && item.payload.action === 'dissolved')
3925
+ continue;
3807
3926
  this.markPublishedSeq(ns, seq);
3808
3927
  delivered = true;
3809
3928
  }
@@ -3824,6 +3943,7 @@ export class MessageDeliveryEngine {
3824
3943
  }
3825
3944
  async publishOrderedMessage(event, ns, seq, payload, source = 'push', operationBatch) {
3826
3945
  const client = this.runtime.client;
3946
+ const generation = this.captureInlineGeneration();
3827
3947
  const ownsBatch = !operationBatch && (source === 'push' || source === 'inline_push');
3828
3948
  const batch = operationBatch ?? (ownsBatch ? this.createDeliveryChangeBatch(source) : undefined);
3829
3949
  try {
@@ -3839,6 +3959,11 @@ export class MessageDeliveryEngine {
3839
3959
  client._pendingOrderedMsgs.delete(ns);
3840
3960
  return false;
3841
3961
  }
3962
+ if (event === 'message.received' && this.isSelfSentP2PMessage(payload)) {
3963
+ this.markPublishedSeq(ns, seqNum);
3964
+ client._clientLog.debug(`publish ordered skipped current-device self-sync: ns=${ns}, seq=${seqNum}`);
3965
+ return false;
3966
+ }
3842
3967
  const contig = client._seqTracker.getContiguousSeq(ns);
3843
3968
  if (seqNum > contig) {
3844
3969
  this.enqueueOrderedMessage(ns, event, seqNum, payload, source);
@@ -3848,10 +3973,12 @@ export class MessageDeliveryEngine {
3848
3973
  if (client._pushedSeqs.get(ns)?.has(seqNum))
3849
3974
  return false;
3850
3975
  const queue = client._pendingOrderedMsgs.get(ns);
3976
+ await this.publishOrderedQueueItem(ns, event, seqNum, payload, false, source, batch);
3977
+ if (!this.isInlineGenerationCurrent(generation))
3978
+ throw new Error('pull invalidated');
3851
3979
  queue?.delete(seqNum);
3852
3980
  if (queue && queue.size === 0)
3853
3981
  client._pendingOrderedMsgs.delete(ns);
3854
- await this.publishOrderedQueueItem(ns, event, seqNum, payload, false, source, batch);
3855
3982
  this.markPublishedSeq(ns, seqNum);
3856
3983
  await this.drainOrderedMessages(ns, undefined, false, true, batch, source);
3857
3984
  if (!client._pendingOrderedMsgs.get(ns))
@@ -3865,6 +3992,7 @@ export class MessageDeliveryEngine {
3865
3992
  }
3866
3993
  async publishPulledMessage(event, ns, seq, payload, persist = true, source = 'pull', operationBatch) {
3867
3994
  const client = this.runtime.client;
3995
+ const generation = this.captureInlineGeneration();
3868
3996
  const ownsBatch = !operationBatch && (source === 'push' || source === 'inline_push');
3869
3997
  const batch = operationBatch ?? (ownsBatch ? this.createDeliveryChangeBatch(source) : undefined);
3870
3998
  const pullScoped = this.isPullScopedSource(source);
@@ -3891,22 +4019,34 @@ export class MessageDeliveryEngine {
3891
4019
  client._pendingOrderedMsgs.delete(ns);
3892
4020
  return false;
3893
4021
  }
4022
+ if (event === 'message.received' && this.isSelfSentP2PMessage(payload)) {
4023
+ this.markPublishedSeq(ns, seqNum);
4024
+ client._clientLog.debug(`publish pulled skipped current-device self-sync: ns=${ns}, seq=${seqNum}`);
4025
+ return source === 'inline_push';
4026
+ }
3894
4027
  await this.drainOrderedMessages(ns, seqNum, false, persist, batch, source);
3895
4028
  if (pullScoped)
3896
4029
  this.ensurePullOperationCurrent(ns);
3897
- queue?.delete(seqNum);
3898
- if (queue && queue.size === 0)
3899
- client._pendingOrderedMsgs.delete(ns);
3900
4030
  if (event === 'message.recalled') {
3901
4031
  const published = await client._withPullResponseProcessing(ns, () => this.publishMessageRecallTombstone(seqNum, payload, source));
4032
+ if (!this.isInlineGenerationCurrent(generation))
4033
+ throw new Error('pull invalidated');
3902
4034
  if (pullScoped)
3903
4035
  this.ensurePullOperationCurrent(ns);
4036
+ queue?.delete(seqNum);
4037
+ if (queue && queue.size === 0)
4038
+ client._pendingOrderedMsgs.delete(ns);
3904
4039
  this.markPublishedSeq(ns, seqNum);
3905
4040
  return published;
3906
4041
  }
3907
4042
  await client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload, source, ns, batch));
4043
+ if (!this.isInlineGenerationCurrent(generation))
4044
+ throw new Error('pull invalidated');
3908
4045
  if (pullScoped)
3909
4046
  this.ensurePullOperationCurrent(ns);
4047
+ queue?.delete(seqNum);
4048
+ if (queue && queue.size === 0)
4049
+ client._pendingOrderedMsgs.delete(ns);
3910
4050
  this.markPublishedSeq(ns, seqNum);
3911
4051
  return true;
3912
4052
  }