@agentunion/fastaun-browser 0.5.18 → 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 (118) hide show
  1. package/CHANGELOG.md +44 -2
  2. package/_packed_docs/CHANGELOG.md +44 -2
  3. package/_packed_docs/INDEX.md +136 -14
  4. package/_packed_docs/KITE_DOCS_GUIDE.md +42 -16
  5. package/_packed_docs/agent.md/SCHEMA.md +245 -205
  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 +42 -15
  7. 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
  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 +45 -134
  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 +5 -20
  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 +22 -27
  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 +250 -283
  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 +74 -64
  18. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +25 -5
  19. package/_packed_docs/sdk/INDEX.md +76 -17
  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 +45 -11
  23. package/dist/agent-md-schema.js.map +1 -1
  24. package/dist/agent-md.d.ts +45 -2
  25. package/dist/agent-md.d.ts.map +1 -1
  26. package/dist/agent-md.js +519 -30
  27. package/dist/agent-md.js.map +1 -1
  28. package/dist/aid-store.d.ts +12 -46
  29. package/dist/aid-store.d.ts.map +1 -1
  30. package/dist/aid-store.js +113 -246
  31. package/dist/aid-store.js.map +1 -1
  32. package/dist/aid.d.ts.map +1 -1
  33. package/dist/aid.js +20 -7
  34. package/dist/aid.js.map +1 -1
  35. package/dist/auth.d.ts +4 -1
  36. package/dist/auth.d.ts.map +1 -1
  37. package/dist/auth.js +343 -23
  38. package/dist/auth.js.map +1 -1
  39. package/dist/bundle.js +37330 -32005
  40. package/dist/cert-utils.d.ts +1 -1
  41. package/dist/cert-utils.d.ts.map +1 -1
  42. package/dist/cert-utils.js +19 -7
  43. package/dist/cert-utils.js.map +1 -1
  44. package/dist/client/delivery.d.ts +15 -1
  45. package/dist/client/delivery.d.ts.map +1 -1
  46. package/dist/client/delivery.js +147 -51
  47. package/dist/client/delivery.js.map +1 -1
  48. package/dist/client/rpc-pipeline.d.ts.map +1 -1
  49. package/dist/client/rpc-pipeline.js +19 -5
  50. package/dist/client/rpc-pipeline.js.map +1 -1
  51. package/dist/client/v2-e2ee.d.ts +1 -0
  52. package/dist/client/v2-e2ee.d.ts.map +1 -1
  53. package/dist/client/v2-e2ee.js +104 -30
  54. package/dist/client/v2-e2ee.js.map +1 -1
  55. package/dist/client.d.ts +100 -1
  56. package/dist/client.d.ts.map +1 -1
  57. package/dist/client.js +1204 -23
  58. package/dist/client.js.map +1 -1
  59. package/dist/errors.d.ts.map +1 -1
  60. package/dist/errors.js +3 -2
  61. package/dist/errors.js.map +1 -1
  62. package/dist/events.d.ts +4 -0
  63. package/dist/events.d.ts.map +1 -1
  64. package/dist/events.js +41 -1
  65. package/dist/events.js.map +1 -1
  66. package/dist/facades.d.ts +82 -2
  67. package/dist/facades.d.ts.map +1 -1
  68. package/dist/facades.js +98 -4
  69. package/dist/facades.js.map +1 -1
  70. package/dist/index.d.ts +7 -3
  71. package/dist/index.d.ts.map +1 -1
  72. package/dist/index.js +3 -2
  73. package/dist/index.js.map +1 -1
  74. package/dist/keystore/index.d.ts +17 -0
  75. package/dist/keystore/index.d.ts.map +1 -1
  76. package/dist/keystore/indexeddb-shared.d.ts +10 -1
  77. package/dist/keystore/indexeddb-shared.d.ts.map +1 -1
  78. package/dist/keystore/indexeddb-shared.js +111 -0
  79. package/dist/keystore/indexeddb-shared.js.map +1 -1
  80. package/dist/keystore/indexeddb-token-store.d.ts +7 -1
  81. package/dist/keystore/indexeddb-token-store.d.ts.map +1 -1
  82. package/dist/keystore/indexeddb-token-store.js +30 -1
  83. package/dist/keystore/indexeddb-token-store.js.map +1 -1
  84. package/dist/org-protocol.d.ts +98 -0
  85. package/dist/org-protocol.d.ts.map +1 -0
  86. package/dist/org-protocol.js +1742 -0
  87. package/dist/org-protocol.js.map +1 -0
  88. package/dist/ownership-proof.d.ts +93 -5
  89. package/dist/ownership-proof.d.ts.map +1 -1
  90. package/dist/ownership-proof.js +239 -53
  91. package/dist/ownership-proof.js.map +1 -1
  92. package/dist/ownership-v21.d.ts +177 -0
  93. package/dist/ownership-v21.d.ts.map +1 -0
  94. package/dist/ownership-v21.js +609 -0
  95. package/dist/ownership-v21.js.map +1 -0
  96. package/dist/ownership-workflow.d.ts +146 -0
  97. package/dist/ownership-workflow.d.ts.map +1 -0
  98. package/dist/ownership-workflow.js +1066 -0
  99. package/dist/ownership-workflow.js.map +1 -0
  100. package/dist/seq-tracker.d.ts +1 -0
  101. package/dist/seq-tracker.d.ts.map +1 -1
  102. package/dist/seq-tracker.js +10 -2
  103. package/dist/seq-tracker.js.map +1 -1
  104. package/dist/tools/cross-sdk-agent.d.ts +82 -0
  105. package/dist/tools/cross-sdk-agent.d.ts.map +1 -1
  106. package/dist/tools/cross-sdk-agent.js +332 -7
  107. package/dist/tools/cross-sdk-agent.js.map +1 -1
  108. package/dist/transport.d.ts +6 -0
  109. package/dist/transport.d.ts.map +1 -1
  110. package/dist/transport.js +88 -2
  111. package/dist/transport.js.map +1 -1
  112. package/dist/v2/e2ee/decrypt.d.ts +1 -0
  113. package/dist/v2/e2ee/decrypt.d.ts.map +1 -1
  114. package/dist/v2/e2ee/decrypt.js +9 -1
  115. package/dist/v2/e2ee/decrypt.js.map +1 -1
  116. package/dist/version.d.ts +1 -1
  117. package/dist/version.js +1 -1
  118. package/package.json +1 -1
@@ -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'
@@ -1997,6 +2068,8 @@ export class MessageDeliveryEngine {
1997
2068
  }
1998
2069
  }
1999
2070
  catch (exc) {
2071
+ if (exc?.stringCode === 'persistence_unavailable')
2072
+ throw exc;
2000
2073
  client._clientLog.warn(`P2P push processing failed:${String(exc)}`);
2001
2074
  if (isJsonObject(data)) {
2002
2075
  const src = data;
@@ -2300,6 +2373,7 @@ export class MessageDeliveryEngine {
2300
2373
  }
2301
2374
  async fillGroupEventGap(groupId, options) {
2302
2375
  const client = this.runtime.client;
2376
+ const generation = this.captureInlineGeneration();
2303
2377
  if (client._sessionOptions?.background_sync === false)
2304
2378
  return;
2305
2379
  if (client.state !== ConnectionState.READY || client._closing)
@@ -2343,23 +2417,12 @@ export class MessageDeliveryEngine {
2343
2417
  const pageContigBefore = client._seqTracker.getContiguousSeq(ns);
2344
2418
  const eventObjects = events.filter((evt) => isJsonObject(evt));
2345
2419
  const contiguousSeq = Number.isSafeInteger(result.contiguous_seq) ? result.contiguous_seq : undefined;
2346
- if (contiguousSeq === undefined)
2347
- client._seqTracker.onPullResult(ns, eventObjects, nextAfterSeq);
2348
- else
2349
- client._seqTracker.onPullResult(ns, eventObjects, nextAfterSeq, contiguousSeq);
2350
2420
  const cursor = isJsonObject(result.cursor) ? result.cursor : null;
2351
2421
  const retentionFloor = Math.max(0, Number(result.retention_floor_event_seq ?? 0), Number(cursor?.retention_floor_seq ?? 0));
2352
2422
  const serverAckFloor = Math.max(0, Number(cursor?.current_seq ?? 0));
2353
2423
  const earliestAvailable = Number(result.earliest_available_event_seq ?? 0);
2354
2424
  const visibilityFloor = Math.max(0, Number(cursor?.join_seq ?? 0), Number.isFinite(earliestAvailable) && earliestAvailable > 0 ? earliestAvailable - 1 : 0);
2355
2425
  const effectiveFloor = Math.max(...[retentionFloor, serverAckFloor, visibilityFloor].filter((value) => Number.isFinite(value)));
2356
- if (effectiveFloor > 0) {
2357
- const contigBeforeFloor = client._seqTracker.getContiguousSeq(ns);
2358
- if (contigBeforeFloor < effectiveFloor) {
2359
- client._clientLog.info('group.pull_events floor advance: ns=' + ns + ' contiguous=' + contigBeforeFloor + ' -> effective_floor=' + effectiveFloor + ' retention_floor=' + retentionFloor + ' server_ack=' + serverAckFloor + ' visibility_floor=' + visibilityFloor);
2360
- client._seqTracker.forceContiguousSeq(ns, effectiveFloor);
2361
- }
2362
- }
2363
2426
  const eventSeqs = [];
2364
2427
  let hasDissolvedEvent = false;
2365
2428
  let publishedEventCount = 0;
@@ -2388,8 +2451,20 @@ export class MessageDeliveryEngine {
2388
2451
  publishedEventCount += 1;
2389
2452
  }
2390
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
+ }
2391
2467
  const ackContig = client._seqTracker.getContiguousSeq(ns);
2392
- await this.drainOrderedMessages(ns);
2393
2468
  if (ackContig !== pageContigBefore && !hasDissolvedEvent) {
2394
2469
  this.persistSeq(ns);
2395
2470
  }
@@ -2879,9 +2954,8 @@ export class MessageDeliveryEngine {
2879
2954
  async commitInlineMessage(ns, seq, event, payload, generation) {
2880
2955
  const client = this.runtime.client;
2881
2956
  const tracker = client._seqTracker;
2882
- const snapshot = typeof tracker.snapshotNamespace === 'function'
2883
- ? tracker.snapshotNamespace(ns)
2884
- : null;
2957
+ let snapshot = null;
2958
+ let trackerChanged = false;
2885
2959
  try {
2886
2960
  const selfSync = (event === 'message.received' && this.isSelfSentP2PMessage(payload))
2887
2961
  || (event === 'group.message_created' && this.isSelfSentGroupMessage(payload));
@@ -2892,6 +2966,8 @@ export class MessageDeliveryEngine {
2892
2966
  return false;
2893
2967
  if (!published)
2894
2968
  return false;
2969
+ snapshot = typeof tracker.snapshotNamespace === 'function' ? tracker.snapshotNamespace(ns) : null;
2970
+ trackerChanged = true;
2895
2971
  const needsPull = Boolean(tracker.onMessageSeq(ns, seq));
2896
2972
  const committed = this.syncState(ns);
2897
2973
  if (needsPull || committed.ack !== seq || committed.tail !== seq || committed.head !== seq) {
@@ -2905,8 +2981,8 @@ export class MessageDeliveryEngine {
2905
2981
  return true;
2906
2982
  }
2907
2983
  catch (exc) {
2908
- if (this.isInlineGenerationCurrent(generation) && typeof tracker.restoreNamespaceSnapshot === 'function') {
2909
- tracker.restoreNamespaceSnapshot(ns, snapshot);
2984
+ if (trackerChanged && this.isInlineGenerationCurrent(generation) && typeof tracker.restoreNamespaceSnapshot === 'function') {
2985
+ tracker._restoreNamespaceSnapshotPreservingPush(ns, snapshot);
2910
2986
  }
2911
2987
  client._clientLog.debug(`inline realtime consume rolled back: ns=${ns} seq=${seq} err=${formatDeliveryError(exc)}`);
2912
2988
  return false;
@@ -2995,7 +3071,7 @@ export class MessageDeliveryEngine {
2995
3071
  }
2996
3072
  }
2997
3073
  hasOutstandingRealtimeGap(ns) {
2998
- const state = this.syncState(ns);
3074
+ const state = this.prospectiveTailState(ns);
2999
3075
  // Tail 已建立可靠后缀窗口;后台续拉只针对尚未接上窗口的真实缺口。
3000
3076
  // maxSeen 可能只是 Tail 期间到达的通知,不能单独据此启动全量补洞。
3001
3077
  return state.ack < state.tail - 1;
@@ -3051,8 +3127,8 @@ export class MessageDeliveryEngine {
3051
3127
  }
3052
3128
  async runP2PRealtimeSync(ns, pushSeq, pageLimit = 50, forceTail = false) {
3053
3129
  const client = this.runtime.client;
3054
- const before = this.syncState(ns);
3055
- 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'
3056
3132
  ? client._seqTracker.planRealtimeSync(ns, pushSeq, pageLimit)
3057
3133
  : (() => {
3058
3134
  const gap = pushSeq - before.ack;
@@ -3065,7 +3141,7 @@ export class MessageDeliveryEngine {
3065
3141
  let result = { messages: [], raw_count: 0 };
3066
3142
  let tailCompleted = false;
3067
3143
  const run = async (tail, background, maxPages, forceForward = false, bypassCoveredHead = false) => {
3068
- const live = this.syncState(ns);
3144
+ const live = this.prospectiveTailState(ns);
3069
3145
  if (tail && !bypassCoveredHead && pushSeq <= live.head)
3070
3146
  return { messages: [], raw_count: 0 };
3071
3147
  if (!tail && !forceForward && live.ack >= live.tail - 1)
@@ -3089,7 +3165,7 @@ export class MessageDeliveryEngine {
3089
3165
  };
3090
3166
  if (forceTail || order[0] === 'tail')
3091
3167
  result = await run(true, false, undefined, false, forceTail);
3092
- const state = this.syncState(ns);
3168
+ const state = this.prospectiveTailState(ns);
3093
3169
  const maxSeen = Number(client._seqTracker.getMaxSeenSeq(ns) || 0);
3094
3170
  // Tail 期间到达的新 Push 只记录在 pending Pull 上界,不写入 SeqTracker。
3095
3171
  const pendingUpper = Number(this.pendingP2pPullUpper?.get(ns) ?? 0);
@@ -3104,16 +3180,16 @@ export class MessageDeliveryEngine {
3104
3180
  || (client._sessionOptions?.background_sync !== false && backgroundWindowForward)) {
3105
3181
  const forwardTarget = Math.max(headForward ? Math.max(pushSeq, maxSeen) : 0, (tailForward || backgroundWindowForward) ? state.tail - 1 : 0, tailMissedDuringPull ? observedUpper : 0);
3106
3182
  const forwardMaxPages = this.forwardMaxPages(state.ack, forwardTarget, pageLimit);
3107
- result = await run(false, !(headForward || tailForward), forwardMaxPages, headForward || tailMissedDuringPull);
3183
+ result = await run(false, !(headForward || tailForward), forwardMaxPages, headForward || tailForward || tailMissedDuringPull);
3108
3184
  if (pendingThroughSeq > 0) {
3109
- const committedAck = this.syncState(ns).ack;
3185
+ const committedAck = this.prospectiveTailState(ns).ack;
3110
3186
  if (committedAck >= pendingThroughSeq)
3111
3187
  this.consumePendingPull(ns, pendingThroughSeq);
3112
3188
  else if (committedAck <= state.ack)
3113
3189
  this.markPendingPullNoProgress(ns, committedAck);
3114
3190
  }
3115
3191
  }
3116
- const finalState = this.syncState(ns);
3192
+ const finalState = this.prospectiveTailState(ns);
3117
3193
  if (tailCompleted && finalState.ack < finalState.tail - 1) {
3118
3194
  this.scheduleP2PRealtimeFullGapFill(ns);
3119
3195
  }
@@ -3123,8 +3199,8 @@ export class MessageDeliveryEngine {
3123
3199
  }
3124
3200
  async runGroupRealtimeSync(groupId, ns, pushSeq, pageLimit = 50, forceTail = false) {
3125
3201
  const client = this.runtime.client;
3126
- const before = this.syncState(ns);
3127
- 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'
3128
3204
  ? client._seqTracker.planRealtimeSync(ns, pushSeq, pageLimit)
3129
3205
  : (() => {
3130
3206
  const gap = pushSeq - before.ack;
@@ -3137,7 +3213,7 @@ export class MessageDeliveryEngine {
3137
3213
  let result = { messages: [], raw_count: 0 };
3138
3214
  let tailCompleted = false;
3139
3215
  const run = async (tail, background, maxPages, forceForward = false, bypassCoveredHead = false) => {
3140
- const live = this.syncState(ns);
3216
+ const live = this.prospectiveTailState(ns);
3141
3217
  if (tail && !bypassCoveredHead && pushSeq <= live.head)
3142
3218
  return { messages: [], raw_count: 0 };
3143
3219
  if (!tail && !forceForward && live.ack >= live.tail - 1)
@@ -3161,7 +3237,7 @@ export class MessageDeliveryEngine {
3161
3237
  };
3162
3238
  if (forceTail || order[0] === 'tail')
3163
3239
  result = await run(true, false, undefined, false, forceTail);
3164
- const state = this.syncState(ns);
3240
+ const state = this.prospectiveTailState(ns);
3165
3241
  // 与 P2P 相同:真实完成 Tail 后,至多用一页 Forward 收敛 Tail
3166
3242
  // 期间新到达的序号;绝不重新规划或再执行 Tail。
3167
3243
  const maxSeen = Number(client._seqTracker.getMaxSeenSeq(ns) || 0);
@@ -3177,16 +3253,16 @@ export class MessageDeliveryEngine {
3177
3253
  || (client._sessionOptions?.background_sync !== false && backgroundWindowForward)) {
3178
3254
  const forwardTarget = Math.max(headForward ? Math.max(pushSeq, maxSeen) : 0, (tailForward || backgroundWindowForward) ? state.tail - 1 : 0, tailMissedDuringPull ? observedUpper : 0);
3179
3255
  const forwardMaxPages = this.forwardMaxPages(state.ack, forwardTarget, pageLimit);
3180
- result = await run(false, !(headForward || tailForward), forwardMaxPages, headForward || tailMissedDuringPull);
3256
+ result = await run(false, !(headForward || tailForward), forwardMaxPages, headForward || tailForward || tailMissedDuringPull);
3181
3257
  if (pendingThroughSeq > 0) {
3182
- const committedAck = this.syncState(ns).ack;
3258
+ const committedAck = this.prospectiveTailState(ns).ack;
3183
3259
  if (committedAck >= pendingThroughSeq)
3184
3260
  this.consumePendingPull(ns, pendingThroughSeq);
3185
3261
  else if (committedAck <= state.ack)
3186
3262
  this.markPendingPullNoProgress(ns, committedAck);
3187
3263
  }
3188
3264
  }
3189
- const finalState = this.syncState(ns);
3265
+ const finalState = this.prospectiveTailState(ns);
3190
3266
  if (tailCompleted && finalState.ack < finalState.tail - 1) {
3191
3267
  this.scheduleGroupRealtimeFullGapFill(groupId, ns);
3192
3268
  }
@@ -3803,7 +3879,7 @@ export class MessageDeliveryEngine {
3803
3879
  }
3804
3880
  return params;
3805
3881
  }
3806
- async drainOrderedMessages(ns, beforeSeq, pullResponse = false, persist = true, batch, source = 'pull') {
3882
+ async drainOrderedMessages(ns, beforeSeq, pullResponse = false, persist = true, batch, source = 'pull', throughSeq) {
3807
3883
  const client = this.runtime.client;
3808
3884
  const pullScoped = this.isPullScopedSource(source);
3809
3885
  if (pullScoped)
@@ -3811,7 +3887,8 @@ export class MessageDeliveryEngine {
3811
3887
  const queue = client._pendingOrderedMsgs.get(ns);
3812
3888
  if (!queue || queue.size === 0)
3813
3889
  return;
3814
- const contig = client._seqTracker.getContiguousSeq(ns);
3890
+ const generation = this.captureInlineGeneration();
3891
+ const contig = throughSeq ?? client._seqTracker.getContiguousSeq(ns);
3815
3892
  const ready = [...queue.keys()]
3816
3893
  .filter((seq) => seq <= contig && (beforeSeq === undefined || seq < beforeSeq))
3817
3894
  .sort((a, b) => a - b);
@@ -3822,9 +3899,12 @@ export class MessageDeliveryEngine {
3822
3899
  if (pullScoped)
3823
3900
  this.ensurePullOperationCurrent(ns);
3824
3901
  const item = queue.get(seq);
3825
- queue.delete(seq);
3826
- if (!item || client._pushedSeqs.get(ns)?.has(seq))
3902
+ if (!item)
3827
3903
  continue;
3904
+ if (client._pushedSeqs.get(ns)?.has(seq)) {
3905
+ queue.delete(seq);
3906
+ continue;
3907
+ }
3828
3908
  const itemSource = item.source ?? source;
3829
3909
  let itemBatch = batch;
3830
3910
  if ((itemSource === 'push' || itemSource === 'inline_push')
@@ -3836,8 +3916,13 @@ export class MessageDeliveryEngine {
3836
3916
  }
3837
3917
  }
3838
3918
  await this.publishOrderedQueueItem(ns, item.event, seq, item.payload, pullResponse, itemSource, itemBatch);
3919
+ if (!this.isInlineGenerationCurrent(generation))
3920
+ throw new Error('pull invalidated');
3839
3921
  if (pullScoped)
3840
3922
  this.ensurePullOperationCurrent(ns);
3923
+ queue.delete(seq);
3924
+ if (item.event === 'group.changed' && isJsonObject(item.payload) && item.payload.action === 'dissolved')
3925
+ continue;
3841
3926
  this.markPublishedSeq(ns, seq);
3842
3927
  delivered = true;
3843
3928
  }
@@ -3858,6 +3943,7 @@ export class MessageDeliveryEngine {
3858
3943
  }
3859
3944
  async publishOrderedMessage(event, ns, seq, payload, source = 'push', operationBatch) {
3860
3945
  const client = this.runtime.client;
3946
+ const generation = this.captureInlineGeneration();
3861
3947
  const ownsBatch = !operationBatch && (source === 'push' || source === 'inline_push');
3862
3948
  const batch = operationBatch ?? (ownsBatch ? this.createDeliveryChangeBatch(source) : undefined);
3863
3949
  try {
@@ -3887,10 +3973,12 @@ export class MessageDeliveryEngine {
3887
3973
  if (client._pushedSeqs.get(ns)?.has(seqNum))
3888
3974
  return false;
3889
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');
3890
3979
  queue?.delete(seqNum);
3891
3980
  if (queue && queue.size === 0)
3892
3981
  client._pendingOrderedMsgs.delete(ns);
3893
- await this.publishOrderedQueueItem(ns, event, seqNum, payload, false, source, batch);
3894
3982
  this.markPublishedSeq(ns, seqNum);
3895
3983
  await this.drainOrderedMessages(ns, undefined, false, true, batch, source);
3896
3984
  if (!client._pendingOrderedMsgs.get(ns))
@@ -3904,6 +3992,7 @@ export class MessageDeliveryEngine {
3904
3992
  }
3905
3993
  async publishPulledMessage(event, ns, seq, payload, persist = true, source = 'pull', operationBatch) {
3906
3994
  const client = this.runtime.client;
3995
+ const generation = this.captureInlineGeneration();
3907
3996
  const ownsBatch = !operationBatch && (source === 'push' || source === 'inline_push');
3908
3997
  const batch = operationBatch ?? (ownsBatch ? this.createDeliveryChangeBatch(source) : undefined);
3909
3998
  const pullScoped = this.isPullScopedSource(source);
@@ -3938,19 +4027,26 @@ export class MessageDeliveryEngine {
3938
4027
  await this.drainOrderedMessages(ns, seqNum, false, persist, batch, source);
3939
4028
  if (pullScoped)
3940
4029
  this.ensurePullOperationCurrent(ns);
3941
- queue?.delete(seqNum);
3942
- if (queue && queue.size === 0)
3943
- client._pendingOrderedMsgs.delete(ns);
3944
4030
  if (event === 'message.recalled') {
3945
4031
  const published = await client._withPullResponseProcessing(ns, () => this.publishMessageRecallTombstone(seqNum, payload, source));
4032
+ if (!this.isInlineGenerationCurrent(generation))
4033
+ throw new Error('pull invalidated');
3946
4034
  if (pullScoped)
3947
4035
  this.ensurePullOperationCurrent(ns);
4036
+ queue?.delete(seqNum);
4037
+ if (queue && queue.size === 0)
4038
+ client._pendingOrderedMsgs.delete(ns);
3948
4039
  this.markPublishedSeq(ns, seqNum);
3949
4040
  return published;
3950
4041
  }
3951
4042
  await client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload, source, ns, batch));
4043
+ if (!this.isInlineGenerationCurrent(generation))
4044
+ throw new Error('pull invalidated');
3952
4045
  if (pullScoped)
3953
4046
  this.ensurePullOperationCurrent(ns);
4047
+ queue?.delete(seqNum);
4048
+ if (queue && queue.size === 0)
4049
+ client._pendingOrderedMsgs.delete(ns);
3954
4050
  this.markPublishedSeq(ns, seqNum);
3955
4051
  return true;
3956
4052
  }