@agentunion/fastaun 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 (95) 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 +2 -0
  21. package/dist/agent-md-schema.js +46 -9
  22. package/dist/agent-md-schema.js.map +1 -1
  23. package/dist/agent-md.d.ts +46 -1
  24. package/dist/agent-md.js +492 -30
  25. package/dist/agent-md.js.map +1 -1
  26. package/dist/aid-store.d.ts +9 -1
  27. package/dist/aid-store.js +139 -18
  28. package/dist/aid-store.js.map +1 -1
  29. package/dist/aid.d.ts +17 -1
  30. package/dist/aid.js +67 -7
  31. package/dist/aid.js.map +1 -1
  32. package/dist/auth.d.ts +6 -1
  33. package/dist/auth.js +428 -15
  34. package/dist/auth.js.map +1 -1
  35. package/dist/cert-utils.d.ts +1 -1
  36. package/dist/cert-utils.js +19 -7
  37. package/dist/cert-utils.js.map +1 -1
  38. package/dist/client/delivery.d.ts +4 -1
  39. package/dist/client/delivery.js +264 -68
  40. package/dist/client/delivery.js.map +1 -1
  41. package/dist/client/rpc-pipeline.js +29 -14
  42. package/dist/client/rpc-pipeline.js.map +1 -1
  43. package/dist/client/v2-e2ee.d.ts +8 -1
  44. package/dist/client/v2-e2ee.js +893 -529
  45. package/dist/client/v2-e2ee.js.map +1 -1
  46. package/dist/client.d.ts +87 -1
  47. package/dist/client.js +816 -35
  48. package/dist/client.js.map +1 -1
  49. package/dist/errors.js +4 -3
  50. package/dist/errors.js.map +1 -1
  51. package/dist/events.d.ts +5 -1
  52. package/dist/events.js +47 -6
  53. package/dist/events.js.map +1 -1
  54. package/dist/facades.d.ts +85 -3
  55. package/dist/facades.js +104 -4
  56. package/dist/facades.js.map +1 -1
  57. package/dist/index.d.ts +9 -4
  58. package/dist/index.js +3 -1
  59. package/dist/index.js.map +1 -1
  60. package/dist/keystore/aid-db.d.ts +5 -0
  61. package/dist/keystore/aid-db.js +48 -0
  62. package/dist/keystore/aid-db.js.map +1 -1
  63. package/dist/keystore/index.d.ts +8 -0
  64. package/dist/keystore/local-identity-store.js +10 -3
  65. package/dist/keystore/local-identity-store.js.map +1 -1
  66. package/dist/keystore/local-token-store.d.ts +6 -1
  67. package/dist/keystore/local-token-store.js +24 -1
  68. package/dist/keystore/local-token-store.js.map +1 -1
  69. package/dist/org-protocol.d.ts +98 -0
  70. package/dist/org-protocol.js +1689 -0
  71. package/dist/org-protocol.js.map +1 -0
  72. package/dist/ownership-proof.d.ts +117 -0
  73. package/dist/ownership-proof.js +436 -0
  74. package/dist/ownership-proof.js.map +1 -0
  75. package/dist/ownership-v21.d.ts +178 -0
  76. package/dist/ownership-v21.js +635 -0
  77. package/dist/ownership-v21.js.map +1 -0
  78. package/dist/ownership-workflow.d.ts +82 -0
  79. package/dist/ownership-workflow.js +872 -0
  80. package/dist/ownership-workflow.js.map +1 -0
  81. package/dist/seq-tracker.d.ts +1 -0
  82. package/dist/seq-tracker.js +10 -1
  83. package/dist/seq-tracker.js.map +1 -1
  84. package/dist/tools/cross-sdk-agent.d.ts +89 -1
  85. package/dist/tools/cross-sdk-agent.js +338 -7
  86. package/dist/tools/cross-sdk-agent.js.map +1 -1
  87. package/dist/transport.d.ts +7 -0
  88. package/dist/transport.js +108 -4
  89. package/dist/transport.js.map +1 -1
  90. package/dist/v2/e2ee/decrypt.d.ts +1 -0
  91. package/dist/v2/e2ee/decrypt.js +9 -1
  92. package/dist/v2/e2ee/decrypt.js.map +1 -1
  93. package/dist/version.d.ts +1 -1
  94. package/dist/version.js +1 -1
  95. package/package.json +8 -8
@@ -175,9 +175,8 @@ export class MessageDeliveryEngine {
175
175
  const pageInlineGeneration = this.captureInlineAckGeneration();
176
176
  const deferredServerCursor = Number(coordinator.pendingForwardCursor(ns, pageContigBefore) ?? 0);
177
177
  const deferredForwardAck = Number(coordinator.pendingForwardAck(ns, pageContigBefore) ?? 0);
178
- const pageTrackerSnapshot = typeof client._seqTracker.snapshotNamespace === 'function'
179
- ? client._seqTracker.snapshotNamespace(ns)
180
- : null;
178
+ let pageTrackerSnapshot = null;
179
+ let trackerMutated = false;
181
180
  response._contig_before = pageContigBefore;
182
181
  const cursor = isJsonObject(response.cursor)
183
182
  ? response.cursor
@@ -225,6 +224,18 @@ export class MessageDeliveryEngine {
225
224
  }
226
225
  }
227
226
  this.ensurePullOperationCurrent(ns);
227
+ if (pageInlineGeneration !== this.captureInlineAckGeneration())
228
+ throw new Error('pull invalidated');
229
+ const previousWindow = client._seqTracker.getSyncState(ns);
230
+ const scanTarget = Math.max(pageContigBefore, ...messages.map((message) => positiveSafeSequenceHint(message.seq)), positiveSafeSequenceHint(response.contiguous_seq), retentionFloor, visibilityFloor, deferredServerCursor);
231
+ const deliveryTarget = scanTarget >= previousWindow.tail - 1
232
+ ? Math.max(scanTarget, previousWindow.head) : scanTarget;
233
+ await this.drainOrderedMessages(ns, undefined, false, false, 'pull', undefined, deliveryTarget);
234
+ this.ensurePullOperationCurrent(ns);
235
+ if (pageInlineGeneration !== this.captureInlineAckGeneration())
236
+ throw new Error('pull invalidated');
237
+ pageTrackerSnapshot = client._seqTracker.snapshotNamespace?.(ns) ?? null;
238
+ trackerMutated = true;
228
239
  client._seqTracker.onPullResult(ns, messages, afterSeq, Number.isSafeInteger(result.contiguous_seq)
229
240
  ? result.contiguous_seq
230
241
  : undefined);
@@ -233,16 +244,19 @@ export class MessageDeliveryEngine {
233
244
  client._seqTracker.forceContiguousSeq(ns, commitTarget);
234
245
  }
235
246
  if (client._seqTracker.getContiguousSeq(ns) !== pageContigBefore) {
236
- this.ensurePullOperationCurrent(ns);
237
- await this.drainOrderedMessages(ns, undefined, false, false, 'pull');
238
247
  this.ensurePullOperationCurrent(ns);
239
248
  await client._commitSeqTrackerState(ns);
249
+ this.ensurePullOperationCurrent(ns);
250
+ if (pageInlineGeneration !== this.captureInlineAckGeneration())
251
+ throw new Error('pull invalidated');
240
252
  }
241
253
  committed = true;
242
254
  }
243
255
  catch (exc) {
244
- if (this.isPullOperationCurrent(ns) && !committed && typeof client._seqTracker.restoreNamespaceSnapshot === 'function') {
245
- client._seqTracker.restoreNamespaceSnapshot(ns, pageTrackerSnapshot);
256
+ if (trackerMutated && pageInlineGeneration === this.captureInlineAckGeneration() && this.isPullOperationCurrent(ns)
257
+ && !committed && typeof client._seqTracker.restoreNamespaceSnapshot === 'function') {
258
+ (client._seqTracker._restoreNamespaceSnapshotPreservingPush ?? client._seqTracker.restoreNamespaceSnapshot)
259
+ .call(client._seqTracker, ns, pageTrackerSnapshot);
246
260
  this.dropSeqTrackerPending(ns);
247
261
  }
248
262
  throw exc;
@@ -702,6 +716,18 @@ export class MessageDeliveryEngine {
702
716
  }
703
717
  this.schedulePendingGroupPullIfNeeded(ns, 'pull-gate-idle');
704
718
  }
719
+ onForegroundPullsDrained() {
720
+ const client = this.runtime.client;
721
+ if (!client._aid || client._sessionOptions?.background_sync === false
722
+ || client.state !== ConnectionState.READY || client._closing)
723
+ return;
724
+ const ns = `p2p:${client._aid}`;
725
+ const state = this.syncState(ns);
726
+ const maxSeen = client._seqTracker.getMaxSeenSeq(ns);
727
+ if (maxSeen <= state.ack && state.ack >= state.tail - 1)
728
+ return;
729
+ client._safeAsync(this.fillP2pGap());
730
+ }
705
731
  recordPendingGroupPull(ns, seq) {
706
732
  if (!ns || !Number.isSafeInteger(seq) || seq <= 0)
707
733
  return;
@@ -792,12 +818,12 @@ export class MessageDeliveryEngine {
792
818
  return;
793
819
  }
794
820
  if (pullResponse) {
795
- const published = client._withPullResponseProcessing(ns, () => this.publishAppEvent(event, payload, source, deliveryContext));
821
+ const published = client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload, source, deliveryContext));
796
822
  if (isPromiseLike(published))
797
823
  await published;
798
824
  }
799
825
  else {
800
- const published = this.publishAppEvent(event, payload, source, deliveryContext);
826
+ const published = client._publishAppEvent(event, payload, source, deliveryContext);
801
827
  if (isPromiseLike(published))
802
828
  await published;
803
829
  }
@@ -806,6 +832,12 @@ export class MessageDeliveryEngine {
806
832
  }
807
833
  async publishOrderedGroupChanged(payload, source = 'ordered') {
808
834
  const client = this.runtime.client;
835
+ const generation = this.captureInlineAckGeneration();
836
+ const published = client._publishAppEvent('group.changed', payload, source);
837
+ if (isPromiseLike(published))
838
+ await published;
839
+ if (generation !== this.captureInlineAckGeneration())
840
+ throw new Error('pull invalidated');
809
841
  if (isJsonObject(payload)) {
810
842
  const eventPayload = payload;
811
843
  client._groupState?.handleGroupChangedV2Membership?.(eventPayload);
@@ -816,9 +848,6 @@ export class MessageDeliveryEngine {
816
848
  client._cleanupDissolvedGroup?.(groupId);
817
849
  }
818
850
  }
819
- const published = client._publishAppEvent('group.changed', payload, source);
820
- if (isPromiseLike(published))
821
- await published;
822
851
  }
823
852
  isInstanceScopedMessageEvent(event) {
824
853
  return event === 'message.received'
@@ -1131,6 +1160,7 @@ export class MessageDeliveryEngine {
1131
1160
  }
1132
1161
  async publishMessageRecallTombstone(seq, message, source = 'push') {
1133
1162
  const client = this.runtime.client;
1163
+ const generation = this.captureInlineAckGeneration();
1134
1164
  const eventPayload = this.recallEventFromMessage(message);
1135
1165
  if (!eventPayload)
1136
1166
  return false;
@@ -1144,13 +1174,17 @@ export class MessageDeliveryEngine {
1144
1174
  client._clientLog.debug(`message.recalled dedup suppressed: seq=${String(seq)} key=${dedupKey}`);
1145
1175
  return false;
1146
1176
  }
1177
+ await client._publishAppEvent('message.recalled', eventPayload, source);
1178
+ if (generation !== this.captureInlineAckGeneration())
1179
+ throw new Error('pull invalidated');
1180
+ if (this.isPullScopedSource(source))
1181
+ this.ensurePullOperationCurrent(`p2p:${client._aid ?? ''}`);
1147
1182
  seen.set(dedupKey, Date.now());
1148
1183
  if (seen.size > MESSAGE_RECALL_SEEN_LIMIT) {
1149
1184
  const drop = [...seen.entries()].sort((a, b) => a[1] - b[1]).slice(0, seen.size - MESSAGE_RECALL_SEEN_LIMIT);
1150
1185
  for (const [oldKey] of drop)
1151
1186
  seen.delete(oldKey);
1152
1187
  }
1153
- await client._publishAppEvent('message.recalled', eventPayload, source);
1154
1188
  client._clientLog.debug(`message.recalled published: seq=${String(seq)} ids=${JSON.stringify(eventPayload.message_ids)}`);
1155
1189
  return true;
1156
1190
  }
@@ -1235,6 +1269,7 @@ export class MessageDeliveryEngine {
1235
1269
  }
1236
1270
  async publishGroupRecallTombstone(groupId, seq, message, source = 'push') {
1237
1271
  const client = this.runtime.client;
1272
+ const generation = this.captureInlineAckGeneration();
1238
1273
  const eventPayload = this.recallEventFromGroupMessage(message);
1239
1274
  if (!eventPayload)
1240
1275
  return false;
@@ -1250,13 +1285,17 @@ export class MessageDeliveryEngine {
1250
1285
  client._clientLog.debug(`group.message_recalled dedup suppressed: group=${groupId} seq=${String(seq)} key=${dedupKey}`);
1251
1286
  return false;
1252
1287
  }
1288
+ await client._publishAppEvent('group.message_recalled', eventPayload, source);
1289
+ if (generation !== this.captureInlineAckGeneration())
1290
+ throw new Error('pull invalidated');
1291
+ if (this.isPullScopedSource(source))
1292
+ this.ensurePullOperationCurrent(`group:${groupId}`);
1253
1293
  seen.set(dedupKey, Date.now());
1254
1294
  if (seen.size > GROUP_RECALL_SEEN_LIMIT) {
1255
1295
  const drop = [...seen.entries()].sort((a, b) => a[1] - b[1]).slice(0, seen.size - GROUP_RECALL_SEEN_LIMIT);
1256
1296
  for (const [oldKey] of drop)
1257
1297
  seen.delete(oldKey);
1258
1298
  }
1259
- await client._publishAppEvent('group.message_recalled', eventPayload, source);
1260
1299
  client._clientLog.debug(`group.message_recalled published: group=${groupId} seq=${String(seq)} ids=${JSON.stringify(eventPayload.message_ids)}`);
1261
1300
  return true;
1262
1301
  }
@@ -1325,11 +1364,14 @@ export class MessageDeliveryEngine {
1325
1364
  const generation = this.deliveryChangedGeneration;
1326
1365
  const normalized = this.normalizePublishedMessagePayload(event, payload, source);
1327
1366
  const dispatcher = client._dispatcher;
1328
- const result = typeof dispatcher.enqueue === 'function'
1329
- ? (dispatcher.enqueue(event, normalized), undefined)
1330
- : typeof dispatcher.publishSyncAware === 'function'
1331
- ? dispatcher.publishSyncAware(event, normalized)
1332
- : dispatcher.publish?.(event, normalized);
1367
+ const result = ['message.received', 'group.message_created', 'message.recalled', 'group.message_recalled', 'group.changed'].includes(event)
1368
+ && typeof dispatcher._publishAndWait === 'function'
1369
+ ? dispatcher._publishAndWait(event, normalized)
1370
+ : typeof dispatcher.enqueue === 'function'
1371
+ ? (dispatcher.enqueue(event, normalized), undefined)
1372
+ : typeof dispatcher.publishSyncAware === 'function'
1373
+ ? dispatcher.publishSyncAware(event, normalized)
1374
+ : dispatcher.publish?.(event, normalized);
1333
1375
  const note = () => {
1334
1376
  if (generation === this.deliveryChangedGeneration) {
1335
1377
  this.noteDeliveryChanged(event, payload, source, deliveryContext);
@@ -1460,6 +1502,24 @@ export class MessageDeliveryEngine {
1460
1502
  return false;
1461
1503
  return senderDevice.trim() === String(client._deviceId).trim();
1462
1504
  }
1505
+ isSelfSentP2PMessage(message) {
1506
+ if (!isJsonObject(message))
1507
+ return false;
1508
+ const client = this.runtime.client;
1509
+ if (String(message.direction ?? '').trim() !== 'outbound_sync')
1510
+ return false;
1511
+ const selfAid = String(client._aid ?? '').trim().toLowerCase();
1512
+ const senderAid = String(message.sender_aid ?? message.from_aid ?? message.from ?? '').trim().toLowerCase();
1513
+ if (!selfAid || senderAid !== selfAid)
1514
+ return false;
1515
+ const senderDevice = Object.prototype.hasOwnProperty.call(message, 'sender_device_id')
1516
+ ? message.sender_device_id
1517
+ : message.from_device_id;
1518
+ return typeof senderDevice === 'string'
1519
+ && Boolean(senderDevice.trim())
1520
+ && Boolean(String(client._deviceId ?? '').trim())
1521
+ && senderDevice.trim() === String(client._deviceId).trim();
1522
+ }
1463
1523
  async runPushProcessSerialized(ns, operation, inlineGeneration) {
1464
1524
  const client = this.runtime.client;
1465
1525
  if (inlineGeneration === undefined) {
@@ -1589,7 +1649,7 @@ export class MessageDeliveryEngine {
1589
1649
  // 在旧 seq tracking / ordered publish 之前转入前台 Tail 协调器。
1590
1650
  const inlinePresent = hasNonEmptyInlineMessage(msg.inline_message);
1591
1651
  const legacyPushSeq = this.realtimeHeadSeq(msg.seq, inlinePresent);
1592
- if (client._v2Session && client._aid && (msg.seq !== undefined && msg.seq !== null || inlinePresent)) {
1652
+ if ((client._v2Session || inlinePresent) && client._aid && (msg.seq !== undefined && msg.seq !== null || inlinePresent)) {
1593
1653
  if (legacyPushSeq === null) {
1594
1654
  if (inlinePresent)
1595
1655
  await this.recoverInvalidP2PRealtimeHead(`p2p:${client._aid}`, msg.seq);
@@ -1606,10 +1666,18 @@ export class MessageDeliveryEngine {
1606
1666
  const ns = `p2p:${client._aid}`;
1607
1667
  if (seq > 0)
1608
1668
  client._seqTracker.updateMaxSeen(ns, seq);
1669
+ if (encryptedPush) {
1670
+ if (Number(seq) > client._seqTracker.getContiguousSeq(ns) && client._sessionOptions?.background_sync !== false) {
1671
+ client._clientLog.debug(`P2P push triggering Forward: ns=${ns}, seq=${seq}, contiguous=${client._seqTracker.getContiguousSeq(ns)}`);
1672
+ this.fillP2pGap().catch((exc) => client._clientLog.warn(`background gap fill trigger failed: ${formatDeliveryError(exc)}`));
1673
+ }
1674
+ return;
1675
+ }
1609
1676
  if (Number(seq) > client._seqTracker.getContiguousSeq(ns) && client._sessionOptions?.background_sync !== false) {
1610
1677
  client._clientLog.debug(`P2P push triggering Forward: ns=${ns}, seq=${seq}, contiguous=${client._seqTracker.getContiguousSeq(ns)}`);
1611
1678
  this.fillP2pGap().catch((exc) => client._clientLog.warn(`background gap fill trigger failed: ${formatDeliveryError(exc)}`));
1612
1679
  }
1680
+ await this.publishOrderedMessage('message.received', ns, seq, msg, 'push');
1613
1681
  return;
1614
1682
  }
1615
1683
  else {
@@ -1617,6 +1685,8 @@ export class MessageDeliveryEngine {
1617
1685
  await client._publishEncryptedPushMessage('message.received', 'message.undecryptable', '', seq ?? 0, msg, false);
1618
1686
  return;
1619
1687
  }
1688
+ if (this.isSelfSentP2PMessage(msg))
1689
+ return;
1620
1690
  await client._publishAppEvent('message.received', msg, 'push');
1621
1691
  }
1622
1692
  }
@@ -1671,7 +1741,7 @@ export class MessageDeliveryEngine {
1671
1741
  // 不能让 payload 路径直接推进旧 ordered publish 状态。
1672
1742
  const inlinePresent = hasNonEmptyInlineMessage(msg.inline_message);
1673
1743
  const legacyPushSeq = this.realtimeHeadSeq(seq, inlinePresent);
1674
- if (client._v2Session && groupId && (seq !== undefined && seq !== null || inlinePresent)) {
1744
+ if ((client._v2Session || inlinePresent) && groupId && (seq !== undefined && seq !== null || inlinePresent)) {
1675
1745
  if (legacyPushSeq === null) {
1676
1746
  if (inlinePresent)
1677
1747
  await this.recoverInvalidGroupRealtimeHead(groupId, `group:${groupId}`, seq);
@@ -1694,10 +1764,20 @@ export class MessageDeliveryEngine {
1694
1764
  const ns = `group:${groupId}`;
1695
1765
  if (seq > 0)
1696
1766
  client._seqTracker.updateMaxSeen(ns, seq);
1767
+ if (encryptedPush) {
1768
+ if (Number(seq) > client._seqTracker.getContiguousSeq(ns) && client._sessionOptions?.background_sync !== false) {
1769
+ client._clientLog.debug(`group push triggering Forward: group=${groupId}, seq=${seq}, contiguous=${client._seqTracker.getContiguousSeq(ns)}`);
1770
+ this.fillGroupGap(groupId).catch((exc) => client._clientLog.warn(`background gap fill trigger failed: ${formatDeliveryError(exc)}`));
1771
+ }
1772
+ return;
1773
+ }
1697
1774
  if (Number(seq) > client._seqTracker.getContiguousSeq(ns) && client._sessionOptions?.background_sync !== false) {
1698
1775
  client._clientLog.debug(`group push triggering Forward: group=${groupId}, seq=${seq}, contiguous=${client._seqTracker.getContiguousSeq(ns)}`);
1699
1776
  this.fillGroupGap(groupId).catch((exc) => client._clientLog.warn(`background gap fill trigger failed: ${formatDeliveryError(exc)}`));
1700
1777
  }
1778
+ if (!this.isSelfSentGroupMessage(msg)) {
1779
+ await this.publishOrderedMessage('group.message_created', ns, seq, msg, 'group-push');
1780
+ }
1701
1781
  return;
1702
1782
  }
1703
1783
  else {
@@ -1845,6 +1925,7 @@ export class MessageDeliveryEngine {
1845
1925
  if (!groupId)
1846
1926
  return;
1847
1927
  const ns = `group_event:${groupId}`;
1928
+ const generation = this.captureInlineAckGeneration();
1848
1929
  const requestedAfterSeq = Number(options.afterSeq ?? 0);
1849
1930
  const afterSeq = Number.isSafeInteger(requestedAfterSeq) && requestedAfterSeq > 0
1850
1931
  ? requestedAfterSeq
@@ -1867,6 +1948,7 @@ export class MessageDeliveryEngine {
1867
1948
  let filled = 0;
1868
1949
  let continuationAfterSeq = 0;
1869
1950
  let failed = false;
1951
+ let dissolved = false;
1870
1952
  try {
1871
1953
  let nextAfterSeq = afterSeq;
1872
1954
  const maxPages = singlePage ? 1 : 100;
@@ -1896,10 +1978,9 @@ export class MessageDeliveryEngine {
1896
1978
  if (!Array.isArray(events))
1897
1979
  return;
1898
1980
  const pageContigBefore = client._seqTracker.getContiguousSeq(ns);
1981
+ let pageTrackerSnapshot = null;
1982
+ let trackerMutated = false;
1899
1983
  const eventObjects = events.filter((evt) => isJsonObject(evt));
1900
- client._seqTracker.onPullResult(ns, eventObjects, nextAfterSeq, Number.isSafeInteger(result.contiguous_seq)
1901
- ? result.contiguous_seq
1902
- : undefined);
1903
1984
  const cursor = isJsonObject(result.cursor)
1904
1985
  ? result.cursor
1905
1986
  : null;
@@ -1909,13 +1990,6 @@ export class MessageDeliveryEngine {
1909
1990
  const earliestAvailable = Number(result.earliest_available_event_seq ?? 0);
1910
1991
  const visibilityFloor = Math.max(0, Number.isFinite(Number(cursor?.join_seq)) ? Number(cursor?.join_seq) : 0, Number.isFinite(earliestAvailable) && earliestAvailable > 0 ? earliestAvailable - 1 : 0);
1911
1992
  const effectiveFloor = Math.max(retentionFloor, serverAckFloor, visibilityFloor);
1912
- if (effectiveFloor > 0) {
1913
- const contigBeforeFloor = client._seqTracker.getContiguousSeq(ns);
1914
- if (contigBeforeFloor < effectiveFloor) {
1915
- client._clientLog.info(`group.pull_events floor advance: ns=${ns} contiguous=${contigBeforeFloor} -> effective_floor=${effectiveFloor} retention_floor=${retentionFloor} server_ack=${serverAckFloor} visibility_floor=${visibilityFloor}`);
1916
- client._seqTracker.forceContiguousSeq(ns, effectiveFloor);
1917
- }
1918
- }
1919
1993
  const eventSeqs = [];
1920
1994
  let hasDissolvedEvent = false;
1921
1995
  let publishedEventCount = 0;
@@ -1945,11 +2019,40 @@ export class MessageDeliveryEngine {
1945
2019
  if (et !== 'group.message_created')
1946
2020
  filled += 1;
1947
2021
  }
1948
- const ackContig = client._seqTracker.getContiguousSeq(ns);
1949
- await this.drainOrderedMessages(ns);
1950
- if (ackContig !== pageContigBefore && !hasDissolvedEvent) {
1951
- this.persistSeq(ns);
2022
+ try {
2023
+ const scanTarget = Math.max(pageContigBefore, effectiveFloor, positiveSafeSequenceHint(result.contiguous_seq), ...eventSeqs);
2024
+ await this.drainOrderedMessages(ns, undefined, false, false, 'pull', undefined, scanTarget);
2025
+ if (generation !== this.captureInlineAckGeneration())
2026
+ throw new Error('pull invalidated');
2027
+ this.ensurePullOperationCurrent(ns);
2028
+ if (hasDissolvedEvent) {
2029
+ dissolved = true;
2030
+ break;
2031
+ }
2032
+ pageTrackerSnapshot = client._seqTracker.snapshotNamespace?.(ns) ?? null;
2033
+ trackerMutated = true;
2034
+ client._seqTracker.onPullResult(ns, eventObjects, nextAfterSeq, Number.isSafeInteger(result.contiguous_seq)
2035
+ ? result.contiguous_seq : undefined);
2036
+ if (client._seqTracker.getContiguousSeq(ns) < effectiveFloor) {
2037
+ client._seqTracker.forceContiguousSeq(ns, effectiveFloor);
2038
+ }
2039
+ if (client._seqTracker.getContiguousSeq(ns) !== pageContigBefore && !hasDissolvedEvent) {
2040
+ await client._commitSeqTrackerState(ns);
2041
+ this.ensurePullOperationCurrent(ns);
2042
+ if (generation !== this.captureInlineAckGeneration())
2043
+ throw new Error('pull invalidated');
2044
+ }
2045
+ }
2046
+ catch (exc) {
2047
+ if (trackerMutated && generation === this.captureInlineAckGeneration() && this.isPullOperationCurrent(ns)
2048
+ && typeof client._seqTracker.restoreNamespaceSnapshot === 'function') {
2049
+ (client._seqTracker._restoreNamespaceSnapshotPreservingPush ?? client._seqTracker.restoreNamespaceSnapshot)
2050
+ .call(client._seqTracker, ns, pageTrackerSnapshot);
2051
+ this.dropSeqTrackerPending(ns);
2052
+ }
2053
+ throw exc;
1952
2054
  }
2055
+ const ackContig = client._seqTracker.getContiguousSeq(ns);
1953
2056
  if (eventObjects.length > 0 && ackContig > 0 && ackContig !== pageContigBefore) {
1954
2057
  const ackSeq = this.clampAckSeq('group.ack_events', 'event_seq', ns, ackContig);
1955
2058
  try {
@@ -2004,13 +2107,13 @@ export class MessageDeliveryEngine {
2004
2107
  if (legacyGateToken !== null && typeof client._releasePullGate === 'function') {
2005
2108
  client._releasePullGate(ns, legacyGateToken);
2006
2109
  }
2007
- if (singlePage && continuationAfterSeq > afterSeq) {
2110
+ if (!dissolved && singlePage && continuationAfterSeq > afterSeq) {
2008
2111
  this.enqueueOnlineUnreadEventHint({
2009
2112
  group_id: groupId,
2010
2113
  _online_hint_after_event_seq: continuationAfterSeq,
2011
2114
  });
2012
2115
  }
2013
- else if (!singlePage && filled > 0 && client._seqTracker.getContiguousSeq(ns) > afterSeq) {
2116
+ else if (!dissolved && !singlePage && filled > 0 && client._seqTracker.getContiguousSeq(ns) > afterSeq) {
2014
2117
  void this.fillGroupEventGap(groupId);
2015
2118
  }
2016
2119
  if (!client._rpcPipeline)
@@ -2374,7 +2477,7 @@ export class MessageDeliveryEngine {
2374
2477
  after_seq: state.ack,
2375
2478
  limit: pageLimit,
2376
2479
  ...(headForward ? { max_pages: Math.max(2, requiredPages) } : order[0] === 'tail' ? { max_pages: requiredPages } : {}),
2377
- }, !foregroundForward, headForward || tailMissedDuringPull);
2480
+ }, true, headForward || tailMissedDuringPull);
2378
2481
  }
2379
2482
  if (tailHead !== null && this.syncState(ns).ack < this.syncState(ns).tail - 1) {
2380
2483
  this.scheduleRealtimeWindowForward(ns);
@@ -2451,7 +2554,7 @@ export class MessageDeliveryEngine {
2451
2554
  after_seq: state.ack,
2452
2555
  limit: pageLimit,
2453
2556
  ...(headForward ? { max_pages: Math.max(2, requiredPages) } : order[0] === 'tail' ? { max_pages: requiredPages } : {}),
2454
- }, !foregroundForward, headForward || tailMissedDuringPull);
2557
+ }, true, headForward || tailMissedDuringPull);
2455
2558
  }
2456
2559
  if (tailHead !== null && this.syncState(ns).ack < this.syncState(ns).tail - 1) {
2457
2560
  this.scheduleRealtimeWindowForward(ns, groupId);
@@ -2722,10 +2825,10 @@ export class MessageDeliveryEngine {
2722
2825
  const pending = client._pendingOrderedMsgs.get(ns);
2723
2826
  return !pending || pending.size === 0;
2724
2827
  }
2725
- async decodeInlineP2P(row) {
2828
+ async decodeInlineP2P(row, deferStatus) {
2726
2829
  const client = this.runtime.client;
2727
2830
  if (this.inlineRowKind(row) === 'v2') {
2728
- const decrypted = await client._decryptV2PushMessage(row);
2831
+ const decrypted = await client._decryptV2PushMessage(row, deferStatus);
2729
2832
  return isJsonObject(decrypted) ? decrypted : null;
2730
2833
  }
2731
2834
  const legacy = isJsonObject(row.legacy_v1) ? row.legacy_v1 : null;
@@ -2750,16 +2853,20 @@ export class MessageDeliveryEngine {
2750
2853
  timestamp: row.t_server,
2751
2854
  payload: payload,
2752
2855
  encrypted: false,
2753
- direction: String(row.direction ?? '').trim() || (fromAid && fromAid === client._aid ? 'outbound_sync' : 'inbound'),
2754
2856
  };
2857
+ const direction = String(row.direction ?? '').trim();
2858
+ if (direction)
2859
+ result.direction = direction;
2860
+ else if (fromAid && fromAid !== client._aid)
2861
+ result.direction = 'inbound';
2755
2862
  if (row.sender_device_id !== undefined)
2756
2863
  result.sender_device_id = row.sender_device_id;
2757
2864
  return result;
2758
2865
  }
2759
- async decodeInlineGroup(row, groupId, groupAid) {
2866
+ async decodeInlineGroup(row, groupId, groupAid, deferStatus) {
2760
2867
  const client = this.runtime.client;
2761
2868
  if (this.inlineRowKind(row) === 'v2') {
2762
- const decrypted = await client._decryptV2PushMessage(row);
2869
+ const decrypted = await client._decryptV2PushMessage(row, deferStatus);
2763
2870
  if (!isJsonObject(decrypted))
2764
2871
  return null;
2765
2872
  const decoded = decrypted;
@@ -2822,17 +2929,24 @@ export class MessageDeliveryEngine {
2822
2929
  return false;
2823
2930
  const client = this.runtime.client;
2824
2931
  const tracker = client._seqTracker;
2825
- const snapshot = typeof tracker.snapshotNamespace === 'function' ? tracker.snapshotNamespace(ns) : null;
2932
+ let snapshot = null;
2933
+ let trackerMutated = false;
2826
2934
  let delivered = false;
2827
2935
  try {
2828
- const published = client._publishAppEvent(event, payload, 'inline-push');
2829
- if (isPromiseLike(published))
2830
- await published;
2936
+ const selfSync = (event === 'message.received' && this.isSelfSentP2PMessage(payload))
2937
+ || (event === 'group.message_created' && this.isSelfSentGroupMessage(payload));
2938
+ if (!selfSync) {
2939
+ const published = client._publishAppEvent(event, payload, 'inline-push');
2940
+ if (isPromiseLike(published))
2941
+ await published;
2942
+ }
2831
2943
  if (!this.inlineGenerationIsCurrent(generation))
2832
2944
  return false;
2833
2945
  delivered = true;
2834
2946
  if (!this.inlineGenerationIsCurrent(generation))
2835
2947
  return false;
2948
+ snapshot = typeof tracker.snapshotNamespace === 'function' ? tracker.snapshotNamespace(ns) : null;
2949
+ trackerMutated = true;
2836
2950
  const needsPull = Boolean(tracker.onMessageSeq(ns, seq));
2837
2951
  if (!this.inlineGenerationIsCurrent(generation))
2838
2952
  return false;
@@ -2855,15 +2969,46 @@ export class MessageDeliveryEngine {
2855
2969
  return true;
2856
2970
  }
2857
2971
  catch (exc) {
2858
- if (this.inlineGenerationIsCurrent(generation)
2859
- && typeof tracker.restoreNamespaceSnapshot === 'function')
2860
- tracker.restoreNamespaceSnapshot(ns, snapshot);
2972
+ if (trackerMutated && this.inlineGenerationIsCurrent(generation)
2973
+ && typeof tracker.restoreNamespaceSnapshot === 'function') {
2974
+ (tracker._restoreNamespaceSnapshotPreservingPush ?? tracker.restoreNamespaceSnapshot).call(tracker, ns, snapshot);
2975
+ }
2861
2976
  if (this.inlineGenerationIsCurrent(generation) && delivered)
2862
2977
  this.markPublishedSeq(ns, seq);
2863
2978
  client._clientLog.debug(`inline realtime consume rolled back: ns=${ns} seq=${seq} err=${formatDeliveryError(exc)}`);
2864
2979
  return false;
2865
2980
  }
2866
2981
  }
2982
+ async commitInlineTerminalDecryptSeq(ns, seq, generation) {
2983
+ const client = this.runtime.client;
2984
+ const tracker = client._seqTracker;
2985
+ if (!this.inlineGenerationIsCurrent(generation) || !this.inlineStateIsCaughtUp(ns, seq))
2986
+ return false;
2987
+ const snapshot = typeof tracker.snapshotNamespace === 'function' ? tracker.snapshotNamespace(ns) : null;
2988
+ try {
2989
+ const needsPull = Boolean(tracker.onMessageSeq(ns, seq));
2990
+ tracker.commitTailWindow(ns, {
2991
+ windowStartSeq: seq,
2992
+ headSeq: seq,
2993
+ coveredThroughSeq: seq,
2994
+ pageLimit: 1,
2995
+ });
2996
+ const committed = this.syncState(ns);
2997
+ if (needsPull || committed.ack !== seq || committed.tail !== seq || committed.head !== seq) {
2998
+ throw new Error('inline realtime terminal cursor did not collapse after consume');
2999
+ }
3000
+ await client._commitSeqTrackerState(ns);
3001
+ return this.inlineGenerationIsCurrent(generation);
3002
+ }
3003
+ catch (exc) {
3004
+ if (this.inlineGenerationIsCurrent(generation)
3005
+ && typeof tracker.restoreNamespaceSnapshot === 'function') {
3006
+ (tracker._restoreNamespaceSnapshotPreservingPush ?? tracker.restoreNamespaceSnapshot).call(tracker, ns, snapshot);
3007
+ }
3008
+ client._clientLog.debug(`inline realtime terminal consume rolled back: ns=${ns} seq=${seq} err=${formatDeliveryError(exc)}`);
3009
+ return false;
3010
+ }
3011
+ }
2867
3012
  async tryConsumeInlineP2P(head, ns, pushSeq, expectedVersion, inlineGeneration) {
2868
3013
  const client = this.runtime.client;
2869
3014
  if (!isJsonObject(head.inline_message))
@@ -2883,7 +3028,14 @@ export class MessageDeliveryEngine {
2883
3028
  return false;
2884
3029
  let committed = false;
2885
3030
  try {
2886
- const decoded = await this.decodeInlineP2P(row);
3031
+ const decryptStatus = {};
3032
+ const decoded = await this.decodeInlineP2P(row, decryptStatus);
3033
+ if (!decoded && decryptStatus.terminal) {
3034
+ if (!await this.commitInlineTerminalDecryptSeq(ns, pushSeq, generation))
3035
+ return false;
3036
+ committed = this.markPendingP2PInlineAckCommitted(ns, pushSeq);
3037
+ return committed;
3038
+ }
2887
3039
  if (!this.inlineGenerationIsCurrent(generation) || !decoded || !this.inlineStateIsCaughtUp(ns, pushSeq)
2888
3040
  || !this.decodedInlineMatches(head, decoded, pushSeq))
2889
3041
  return false;
@@ -2921,7 +3073,14 @@ export class MessageDeliveryEngine {
2921
3073
  return false;
2922
3074
  let committed = false;
2923
3075
  try {
2924
- const decoded = await this.decodeInlineGroup(row, groupId, groupAid || groupId);
3076
+ const decryptStatus = {};
3077
+ const decoded = await this.decodeInlineGroup(row, groupId, groupAid || groupId, decryptStatus);
3078
+ if (!decoded && decryptStatus.terminal) {
3079
+ if (!await this.commitInlineTerminalDecryptSeq(ns, pushSeq, generation))
3080
+ return false;
3081
+ committed = this.markPendingGroupInlineAckCommitted(ns, pushSeq);
3082
+ return committed;
3083
+ }
2925
3084
  if (!this.inlineGenerationIsCurrent(generation) || !decoded || !this.inlineStateIsCaughtUp(ns, pushSeq)
2926
3085
  || !this.decodedInlineMatches(head, decoded, pushSeq, groupId))
2927
3086
  return false;
@@ -3040,13 +3199,14 @@ export class MessageDeliveryEngine {
3040
3199
  const client = this.runtime.client;
3041
3200
  if (inlineGeneration !== undefined && !this.inlineGenerationIsCurrent(inlineGeneration))
3042
3201
  return;
3043
- if (!client._v2Session)
3044
- return;
3045
3202
  const pushData = isJsonObject(data) ? data : null;
3046
3203
  const rawPushSeq = pushData?.seq;
3047
3204
  const ns = client._aid ? `p2p:${client._aid}` : '';
3048
3205
  if (!pushData || !ns)
3049
3206
  return;
3207
+ const inlinePresent = hasNonEmptyInlineMessage(pushData.inline_message);
3208
+ if (!client._v2Session && !(expectedVersion === 'v1' && inlinePresent))
3209
+ return;
3050
3210
  if (this.isVolatileMessage(pushData)) {
3051
3211
  await client._v2E2EE.publishV2VolatileMessages([pushData], 'push', false);
3052
3212
  return;
@@ -3057,7 +3217,6 @@ export class MessageDeliveryEngine {
3057
3217
  this.enqueueOnlineUnreadP2pHint(pushData);
3058
3218
  return;
3059
3219
  }
3060
- const inlinePresent = hasNonEmptyInlineMessage(pushData.inline_message);
3061
3220
  const pushSeq = this.realtimeHeadSeq(rawPushSeq, inlinePresent);
3062
3221
  if (pushSeq === null) {
3063
3222
  if (inlinePresent) {
@@ -3154,8 +3313,8 @@ export class MessageDeliveryEngine {
3154
3313
  const client = this.runtime.client;
3155
3314
  if (inlineGeneration !== undefined && !this.inlineGenerationIsCurrent(inlineGeneration))
3156
3315
  return;
3157
- if (!isJsonObject(data) || !client._v2Session) {
3158
- client._clientLog.debug(`_onRawGroupV2MessageCreated skipped: is_object=${String(isJsonObject(data))}, has_v2_session=${String(!!client._v2Session)}`);
3316
+ if (!isJsonObject(data)) {
3317
+ client._clientLog.debug('_onRawGroupV2MessageCreated skipped: is_object=false');
3159
3318
  return;
3160
3319
  }
3161
3320
  const d = data;
@@ -3163,6 +3322,8 @@ export class MessageDeliveryEngine {
3163
3322
  const groupId = typeof d.group_id === 'string' ? d.group_id.trim() : '';
3164
3323
  const rawSeq = d.seq;
3165
3324
  const inlinePresent = hasNonEmptyInlineMessage(d.inline_message);
3325
+ if (!client._v2Session && !(expectedVersion === 'v1' && inlinePresent))
3326
+ return;
3166
3327
  const seq = this.realtimeHeadSeq(rawSeq, inlinePresent);
3167
3328
  if (!groupId) {
3168
3329
  client._clientLog.debug(`_onRawGroupV2MessageCreated skipped: group=${groupId || '<empty>'}, seq=${String(d.seq ?? '')}`);
@@ -3621,8 +3782,9 @@ export class MessageDeliveryEngine {
3621
3782
  }
3622
3783
  return params;
3623
3784
  }
3624
- async drainOrderedMessages(ns, beforeSeq, pullResponse = false, persist = true, source = 'push', context) {
3785
+ async drainOrderedMessages(ns, beforeSeq, pullResponse = false, persist = true, source = 'push', context, throughSeq) {
3625
3786
  const client = this.runtime.client;
3787
+ const generation = this.captureInlineAckGeneration();
3626
3788
  const pullScoped = this.isPullScopedSource(source);
3627
3789
  if (pullScoped)
3628
3790
  this.ensurePullOperationCurrent(ns);
@@ -3638,11 +3800,11 @@ export class MessageDeliveryEngine {
3638
3800
  this.ensurePullOperationCurrent(ns);
3639
3801
  const contig = client._seqTracker.getContiguousSeq(ns);
3640
3802
  const ready = [...queue.keys()]
3641
- .filter((seq) => seq <= contig && (beforeSeq === undefined || seq < beforeSeq))
3803
+ .filter((seq) => seq <= Math.max(contig, throughSeq ?? contig) && (beforeSeq === undefined || seq < beforeSeq))
3642
3804
  .sort((a, b) => a - b);
3643
3805
  let seq = ready[0];
3644
3806
  if (seq === undefined) {
3645
- const nextSeq = contig + 1;
3807
+ const nextSeq = Math.max(contig, throughSeq ?? contig) + 1;
3646
3808
  if (beforeSeq !== undefined && nextSeq >= beforeSeq)
3647
3809
  break;
3648
3810
  if (!queue.has(nextSeq))
@@ -3652,10 +3814,10 @@ export class MessageDeliveryEngine {
3652
3814
  if (seq === undefined)
3653
3815
  continue;
3654
3816
  const item = queue.get(seq);
3655
- queue.delete(seq);
3656
3817
  if (!item)
3657
3818
  continue;
3658
3819
  if (client._pushedSeqs.get(ns)?.has(seq)) {
3820
+ queue.delete(seq);
3659
3821
  client._clientLog.debug(`publish ordered drain skipped duplicate: ns=${ns}, seq=${seq}, event=${item.event}`);
3660
3822
  client._markOrderedSeqDelivered(ns, seq);
3661
3823
  continue;
@@ -3673,10 +3835,18 @@ export class MessageDeliveryEngine {
3673
3835
  }
3674
3836
  }
3675
3837
  await this.publishOrderedQueueItem(ns, item.event, seq, item.payload, itemSource, pullResponse, itemContext);
3838
+ if (generation !== this.captureInlineAckGeneration())
3839
+ throw new Error('pull invalidated');
3676
3840
  if (pullScoped)
3677
3841
  this.ensurePullOperationCurrent(ns);
3842
+ queue.delete(seq);
3843
+ if (item.event === 'group.changed' && isJsonObject(item.payload)
3844
+ && item.payload.action === 'dissolved')
3845
+ break;
3678
3846
  this.markPublishedSeq(ns, seq);
3679
3847
  client._markOrderedSeqDelivered(ns, seq);
3848
+ if (throughSeq !== undefined)
3849
+ throughSeq = Math.max(throughSeq, seq);
3680
3850
  delivered = true;
3681
3851
  client._clientLog.debug(`publish ordered drain delivered: ns=${ns}, seq=${seq}, event=${item.event}`);
3682
3852
  }
@@ -3720,6 +3890,7 @@ export class MessageDeliveryEngine {
3720
3890
  }
3721
3891
  async publishOrderedMessageInternal(event, ns, seq, payload, source, context) {
3722
3892
  const client = this.runtime.client;
3893
+ const generation = this.captureInlineAckGeneration();
3723
3894
  const pullScoped = this.isPullScopedSource(source);
3724
3895
  if (pullScoped)
3725
3896
  this.ensurePullOperationCurrent(ns);
@@ -3739,6 +3910,11 @@ export class MessageDeliveryEngine {
3739
3910
  client._pendingOrderedMsgs.delete(ns);
3740
3911
  return false;
3741
3912
  }
3913
+ if (event === 'message.received' && this.isSelfSentP2PMessage(payload)) {
3914
+ this.markPublishedSeq(ns, seqNum);
3915
+ client._clientLog.debug(`publish ordered skipped current-device self-sync: ns=${ns}, seq=${seqNum}`);
3916
+ return false;
3917
+ }
3742
3918
  const contig = client._seqTracker.getContiguousSeq(ns);
3743
3919
  if (seqNum > contig && seqNum !== contig + 1) {
3744
3920
  client._clientLog.debug(`publish ordered enqueue(gap): event=${event}, ns=${ns}, seq=${seqNum}, contiguous=${contig}`);
@@ -3752,13 +3928,20 @@ export class MessageDeliveryEngine {
3752
3928
  client._clientLog.debug(`publish ordered skipped after-drain duplicate: event=${event}, ns=${ns}, seq=${seqNum}`);
3753
3929
  return false;
3754
3930
  }
3931
+ if (event === 'message.received' && this.isSelfSentP2PMessage(payload)) {
3932
+ this.markPublishedSeq(ns, seqNum);
3933
+ client._clientLog.debug(`publish pulled skipped current-device self-sync: ns=${ns}, seq=${seqNum}`);
3934
+ return false;
3935
+ }
3755
3936
  const queue = client._pendingOrderedMsgs.get(ns);
3756
- queue?.delete(seqNum);
3757
- if (queue && queue.size === 0)
3758
- client._pendingOrderedMsgs.delete(ns);
3759
3937
  await this.publishOrderedQueueItem(ns, event, seqNum, payload, source, false, context);
3938
+ if (generation !== this.captureInlineAckGeneration())
3939
+ throw new Error('pull invalidated');
3760
3940
  if (pullScoped)
3761
3941
  this.ensurePullOperationCurrent(ns);
3942
+ queue?.delete(seqNum);
3943
+ if (queue && queue.size === 0)
3944
+ client._pendingOrderedMsgs.delete(ns);
3762
3945
  this.markPublishedSeq(ns, seqNum);
3763
3946
  client._markOrderedSeqDelivered(ns, seqNum);
3764
3947
  client._clientLog.debug(`publish ordered delivered: event=${event}, ns=${ns}, seq=${seqNum}`);
@@ -3772,6 +3955,7 @@ export class MessageDeliveryEngine {
3772
3955
  }
3773
3956
  async publishPulledMessage(event, ns, seq, payload, persist = true, source = 'pull') {
3774
3957
  const client = this.runtime.client;
3958
+ const generation = this.captureInlineAckGeneration();
3775
3959
  const pullScoped = this.isPullScopedSource(source);
3776
3960
  if (pullScoped)
3777
3961
  this.ensurePullOperationCurrent(ns);
@@ -3799,16 +3983,23 @@ export class MessageDeliveryEngine {
3799
3983
  client._pendingOrderedMsgs.delete(ns);
3800
3984
  return false;
3801
3985
  }
3986
+ if (event === 'message.received' && this.isSelfSentP2PMessage(payload)) {
3987
+ this.markPublishedSeq(ns, seqNum);
3988
+ client._clientLog.debug('publish pulled skipped current-device self-sync');
3989
+ return source === 'inline_push';
3990
+ }
3802
3991
  await this.drainOrderedMessages(ns, seqNum, true, persist, source);
3803
3992
  if (pullScoped)
3804
3993
  this.ensurePullOperationCurrent(ns);
3805
- queue?.delete(seqNum);
3806
- if (queue && queue.size === 0)
3807
- client._pendingOrderedMsgs.delete(ns);
3808
3994
  if (event === 'message.recalled') {
3809
3995
  const recallPublished = await this.publishMessageRecallTombstone(seqNum, payload, source);
3996
+ if (generation !== this.captureInlineAckGeneration())
3997
+ throw new Error('pull invalidated');
3810
3998
  if (pullScoped)
3811
3999
  this.ensurePullOperationCurrent(ns);
4000
+ queue?.delete(seqNum);
4001
+ if (queue && queue.size === 0)
4002
+ client._pendingOrderedMsgs.delete(ns);
3812
4003
  this.markPublishedSeq(ns, seqNum);
3813
4004
  client._clientLog.debug(`publish pulled delivered: event=${event}, ns=${ns}, seq=${seqNum}`);
3814
4005
  await this.drainOrderedMessages(ns, undefined, true, persist, source);
@@ -3819,8 +4010,13 @@ export class MessageDeliveryEngine {
3819
4010
  const published = client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload, source));
3820
4011
  if (isPromiseLike(published))
3821
4012
  await published;
4013
+ if (generation !== this.captureInlineAckGeneration())
4014
+ throw new Error('pull invalidated');
3822
4015
  if (pullScoped)
3823
4016
  this.ensurePullOperationCurrent(ns);
4017
+ queue?.delete(seqNum);
4018
+ if (queue && queue.size === 0)
4019
+ client._pendingOrderedMsgs.delete(ns);
3824
4020
  this.markPublishedSeq(ns, seqNum);
3825
4021
  client._clientLog.debug(`publish pulled delivered: event=${event}, ns=${ns}, seq=${seqNum}`);
3826
4022
  await this.drainOrderedMessages(ns, undefined, true, persist, source);