@agentunion/fastaun-browser 0.5.8 → 0.5.10

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 (58) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/_packed_docs/CHANGELOG.md +62 -0
  3. package/_packed_docs/agent.md/examples//347/276/244/347/273/204-/345/274/200/345/217/221/345/233/242/351/230/237.md +21 -0
  4. package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +6 -5
  5. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +43 -8
  6. package/_packed_docs/sdk/09-group-rpc-manual.md +18 -3
  7. package/_packed_docs/sdk/09-message-rpc-manual.md +34 -10
  8. package/dist/agent-md.d.ts.map +1 -1
  9. package/dist/agent-md.js +24 -1
  10. package/dist/agent-md.js.map +1 -1
  11. package/dist/aid-store.d.ts.map +1 -1
  12. package/dist/aid-store.js +1 -3
  13. package/dist/aid-store.js.map +1 -1
  14. package/dist/auth.d.ts.map +1 -1
  15. package/dist/auth.js +16 -2
  16. package/dist/auth.js.map +1 -1
  17. package/dist/bundle.js +1247 -269
  18. package/dist/client/delivery.d.ts +30 -7
  19. package/dist/client/delivery.d.ts.map +1 -1
  20. package/dist/client/delivery.js +354 -92
  21. package/dist/client/delivery.js.map +1 -1
  22. package/dist/client/group-state.js +8 -8
  23. package/dist/client/group-state.js.map +1 -1
  24. package/dist/client/lifecycle.d.ts.map +1 -1
  25. package/dist/client/lifecycle.js +41 -34
  26. package/dist/client/lifecycle.js.map +1 -1
  27. package/dist/client/rpc-pipeline.d.ts +12 -0
  28. package/dist/client/rpc-pipeline.d.ts.map +1 -1
  29. package/dist/client/rpc-pipeline.js +205 -36
  30. package/dist/client/rpc-pipeline.js.map +1 -1
  31. package/dist/client/v2-e2ee.d.ts +1 -1
  32. package/dist/client/v2-e2ee.d.ts.map +1 -1
  33. package/dist/client/v2-e2ee.js +72 -18
  34. package/dist/client/v2-e2ee.js.map +1 -1
  35. package/dist/client.d.ts +0 -1
  36. package/dist/client.d.ts.map +1 -1
  37. package/dist/client.js +128 -45
  38. package/dist/client.js.map +1 -1
  39. package/dist/events.d.ts +31 -2
  40. package/dist/events.d.ts.map +1 -1
  41. package/dist/events.js +167 -7
  42. package/dist/events.js.map +1 -1
  43. package/dist/facades.d.ts.map +1 -1
  44. package/dist/facades.js +7 -3
  45. package/dist/facades.js.map +1 -1
  46. package/dist/register-flow.d.ts.map +1 -1
  47. package/dist/register-flow.js +16 -2
  48. package/dist/register-flow.js.map +1 -1
  49. package/dist/transport.d.ts +10 -1
  50. package/dist/transport.d.ts.map +1 -1
  51. package/dist/transport.js +283 -29
  52. package/dist/transport.js.map +1 -1
  53. package/dist/version.d.ts +1 -1
  54. package/dist/version.d.ts.map +1 -1
  55. package/dist/version.js +1 -1
  56. package/dist/version.js.map +1 -1
  57. package/package.json +1 -1
  58. package/_packed_docs//345/217/221/345/270/203/346/212/245/345/221/212-0.5.6.md +0 -260
@@ -1,4 +1,5 @@
1
1
  import { slotIsolationKey } from '../config.js';
2
+ import { normalizeGroupAid } from '../group-id.js';
2
3
  import { ConnectionState, isJsonObject } from '../types.js';
3
4
  import { hasGroupMentionModeField, normalizeGroupMentionMode } from './mention-mode.js';
4
5
  const PUSHED_SEQS_LIMIT = 50_000;
@@ -94,10 +95,15 @@ function p2pAppEventFromPlainPullMessage(message) {
94
95
  }
95
96
  export class MessageDeliveryEngine {
96
97
  runtime;
98
+ pendingPullDeliveryChanges = null;
99
+ deliveryGeneration = 0;
97
100
  realtimeTailResults = null;
98
101
  realtimeSyncing = null;
99
102
  pendingP2pPullUpper = null;
100
103
  pendingGroupPullUpper = null;
104
+ pendingPullNoProgressAcks = null;
105
+ onlineUnreadHintTargets = null;
106
+ onlineUnreadHintOwners = null;
101
107
  realtimeAcking = null;
102
108
  pendingP2PInlineAcks = null;
103
109
  pendingGroupInlineAcks = null;
@@ -111,9 +117,15 @@ export class MessageDeliveryEngine {
111
117
  }
112
118
  resetInlineAckState() {
113
119
  this.inlineGeneration += 1;
120
+ this.deliveryGeneration += 1;
121
+ this.pendingPullDeliveryChanges = null;
122
+ void this.runtime.client._rpcPipeline?.invalidatePulls?.();
114
123
  this.realtimeSyncing = null;
115
124
  this.pendingP2pPullUpper = null;
116
125
  this.pendingGroupPullUpper = null;
126
+ this.pendingPullNoProgressAcks = null;
127
+ this.onlineUnreadHintTargets = null;
128
+ this.onlineUnreadHintOwners = null;
117
129
  this.realtimeTailResults = null;
118
130
  this.realtimeAcking = null;
119
131
  this.pendingP2PInlineAcks = null;
@@ -123,6 +135,20 @@ export class MessageDeliveryEngine {
123
135
  isInlineGenerationCurrent(generation) {
124
136
  return generation === this.inlineGeneration;
125
137
  }
138
+ isPullOperationCurrent() {
139
+ const client = this.runtime.client;
140
+ const generation = client._pullOperationGeneration;
141
+ if (generation === undefined)
142
+ return true;
143
+ const pipeline = client._rpcPipeline;
144
+ return typeof pipeline?.isPullGenerationCurrent === 'function'
145
+ ? pipeline.isPullGenerationCurrent(generation)
146
+ : true;
147
+ }
148
+ ensurePullOperationCurrent() {
149
+ if (!this.isPullOperationCurrent())
150
+ throw new Error('pull invalidated');
151
+ }
126
152
  captureInlineGeneration() {
127
153
  return this.inlineGeneration;
128
154
  }
@@ -146,6 +172,85 @@ export class MessageDeliveryEngine {
146
172
  return;
147
173
  this.schedulePendingPullIfNeeded(ns, 'pull-gate-idle');
148
174
  }
175
+ onPullWorkSettled() {
176
+ const pipeline = this.runtime.client._rpcPipeline;
177
+ if (pipeline?.hasAnyPullActivity?.() === true)
178
+ return;
179
+ void this.flushPullDeliveryChanges();
180
+ }
181
+ deliveryChangeNamespace(event, payload, ns = '') {
182
+ if (event === 'message.received')
183
+ return 'p2p';
184
+ if (event !== 'group.message_created')
185
+ return '';
186
+ if (!isJsonObject(payload))
187
+ return '';
188
+ const aid = String(this.runtime.client._aid ?? '').trim().toLowerCase();
189
+ const dot = aid.indexOf('.');
190
+ const localIssuer = dot > 0 ? aid.slice(dot + 1) : '';
191
+ const fallback = ns.startsWith('group:') ? ns.slice('group:'.length) : '';
192
+ const groupAid = normalizeGroupAid(payload.group_aid ?? payload.group_id ?? fallback, { localIssuer });
193
+ return groupAid.includes('.') ? `group:${groupAid}` : '';
194
+ }
195
+ isDeliverableMessageBody(event, payload) {
196
+ if ((event !== 'message.received' && event !== 'group.message_created') || !isJsonObject(payload))
197
+ return false;
198
+ const messageId = typeof payload.message_id === 'string' && payload.message_id.trim().length > 0;
199
+ const seq = typeof payload.seq === 'number' && Number.isSafeInteger(payload.seq) && payload.seq > 0;
200
+ return messageId || seq;
201
+ }
202
+ recordDeliveryChange(changes, event, payload, ns, generation) {
203
+ if (generation !== this.deliveryGeneration || !this.isDeliverableMessageBody(event, payload))
204
+ return;
205
+ const namespace = this.deliveryChangeNamespace(event, payload, ns);
206
+ if (!namespace)
207
+ return;
208
+ changes.set(namespace, (changes.get(namespace) ?? 0) + 1);
209
+ }
210
+ deliveryChangesPayload(changes) {
211
+ return [...changes.entries()]
212
+ .filter(([, deliveredCount]) => deliveredCount > 0)
213
+ .sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0)
214
+ .map(([namespace, deliveredCount]) => ({ namespace, delivered_count: deliveredCount }));
215
+ }
216
+ createDeliveryChangeBatch(trigger) {
217
+ return { generation: this.deliveryGeneration, trigger, changes: new Map(), closed: false };
218
+ }
219
+ async flushRealtimeDeliveryChanges(batch) {
220
+ if (batch.closed)
221
+ return;
222
+ batch.closed = true;
223
+ if (batch.generation !== this.deliveryGeneration)
224
+ return;
225
+ if (this.deliveryChangesPayload(batch.changes).length === 0)
226
+ return;
227
+ const pendingPull = this.pendingPullDeliveryChanges;
228
+ this.pendingPullDeliveryChanges = null;
229
+ if (pendingPull) {
230
+ for (const [namespace, count] of pendingPull) {
231
+ batch.changes.set(namespace, (batch.changes.get(namespace) ?? 0) + count);
232
+ }
233
+ }
234
+ if (batch.generation !== this.deliveryGeneration)
235
+ return;
236
+ const changes = this.deliveryChangesPayload(batch.changes);
237
+ if (changes.length === 0)
238
+ return;
239
+ this.runtime.client._dispatcher.enqueue('delivery.changed', { trigger: batch.trigger, changes });
240
+ }
241
+ async flushPullDeliveryChanges() {
242
+ const generation = this.deliveryGeneration;
243
+ const pending = this.pendingPullDeliveryChanges;
244
+ if (!pending || pending.size === 0 || generation !== this.deliveryGeneration)
245
+ return;
246
+ this.pendingPullDeliveryChanges = null;
247
+ if (generation !== this.deliveryGeneration)
248
+ return;
249
+ const changes = this.deliveryChangesPayload(pending);
250
+ if (changes.length === 0)
251
+ return;
252
+ this.runtime.client._dispatcher.enqueue('delivery.changed', { trigger: 'pull_drained', changes });
253
+ }
149
254
  recordPendingPull(ns, seq) {
150
255
  if (!ns || !Number.isSafeInteger(seq) || seq <= 0)
151
256
  return;
@@ -160,8 +265,15 @@ export class MessageDeliveryEngine {
160
265
  }
161
266
  consumePendingPull(ns, throughSeq) {
162
267
  const pending = ns.startsWith('p2p:') ? this.pendingP2pPullUpper : this.pendingGroupPullUpper;
163
- if ((pending?.get(ns) ?? 0) <= throughSeq)
268
+ if ((pending?.get(ns) ?? 0) <= throughSeq) {
164
269
  pending?.delete(ns);
270
+ this.pendingPullNoProgressAcks?.delete(ns);
271
+ }
272
+ }
273
+ markPendingPullNoProgress(ns, ack) {
274
+ const blocked = this.pendingPullNoProgressAcks ?? new Map();
275
+ this.pendingPullNoProgressAcks = blocked;
276
+ blocked.set(ns, ack);
165
277
  }
166
278
  schedulePendingPullIfNeeded(ns, reason) {
167
279
  const pending = ns.startsWith('p2p:') ? this.pendingP2pPullUpper : this.pendingGroupPullUpper;
@@ -170,15 +282,21 @@ export class MessageDeliveryEngine {
170
282
  const upper = pending.get(ns) ?? 0;
171
283
  if (upper <= 0) {
172
284
  pending.delete(ns);
285
+ this.pendingPullNoProgressAcks?.delete(ns);
173
286
  return false;
174
287
  }
175
288
  const client = this.runtime.client;
176
289
  const contiguous = client._seqTracker.getContiguousSeq(ns);
177
290
  if (upper <= contiguous) {
178
291
  pending.delete(ns);
292
+ this.pendingPullNoProgressAcks?.delete(ns);
179
293
  client._clientLog?.debug(`pending pull upper already covered: ns=${ns}, upper_seq=${upper}, contiguous=${contiguous}, reason=${reason}`);
180
294
  return false;
181
295
  }
296
+ const blockedAck = this.pendingPullNoProgressAcks?.get(ns);
297
+ if (blockedAck !== undefined && contiguous <= blockedAck)
298
+ return false;
299
+ this.pendingPullNoProgressAcks?.delete(ns);
182
300
  if (client.state !== ConnectionState.READY || client._closing || this.realtimeSyncing?.has(ns)
183
301
  || client._rpcPipeline?.hasPullActivity?.(ns, false))
184
302
  return false;
@@ -620,6 +738,20 @@ export class MessageDeliveryEngine {
620
738
  else if (Object.prototype.hasOwnProperty.call(row, 'ack_seq')) {
621
739
  value = row.ack_seq;
622
740
  }
741
+ else if (Object.prototype.hasOwnProperty.call(row, 'cursor')) {
742
+ const cursor = row.cursor;
743
+ const cursorRow = isJsonObject(cursor)
744
+ ? cursor
745
+ : null;
746
+ if (cursorRow) {
747
+ if (!Object.prototype.hasOwnProperty.call(cursorRow, 'current_seq'))
748
+ return 0;
749
+ value = cursorRow.current_seq;
750
+ }
751
+ else {
752
+ value = cursor;
753
+ }
754
+ }
623
755
  else {
624
756
  return requestedSeq;
625
757
  }
@@ -640,6 +772,7 @@ export class MessageDeliveryEngine {
640
772
  }
641
773
  async confirmPlainForwardAck(ns, method, ackSeq, groupId = '') {
642
774
  const client = this.runtime.client;
775
+ this.ensurePullOperationCurrent();
643
776
  const coordinator = this.forwardCoordinator();
644
777
  const generation = this.captureInlineGeneration();
645
778
  coordinator.recordForwardAck(ns, ackSeq);
@@ -658,6 +791,7 @@ export class MessageDeliveryEngine {
658
791
  _rpc_background: true,
659
792
  };
660
793
  const result = await client._rpcPipeline.rawCall(method, params, { background: true });
794
+ this.ensurePullOperationCurrent();
661
795
  const actualAckSeq = this.resolveForwardAckSeq(result, ackSeq);
662
796
  if (actualAckSeq < ackSeq) {
663
797
  throw new Error(`${method} server ACK watermark ${actualAckSeq} is below requested ${ackSeq}`);
@@ -675,6 +809,7 @@ export class MessageDeliveryEngine {
675
809
  throw new Error(`${method} response must be an object`);
676
810
  }
677
811
  const client = this.runtime.client;
812
+ this.ensurePullOperationCurrent();
678
813
  const response = result;
679
814
  const messages = deduplicatePlainForwardPageMessages(response.messages, afterSeq);
680
815
  const coordinator = this.forwardCoordinator();
@@ -705,25 +840,30 @@ export class MessageDeliveryEngine {
705
840
  let committed = false;
706
841
  try {
707
842
  for (const rawMessage of messages) {
843
+ this.ensurePullOperationCurrent();
708
844
  const seq = positiveSafeSequenceHint(rawMessage.seq);
709
845
  if (method === 'message.pull') {
710
846
  const appEvent = p2pAppEventFromPlainPullMessage(rawMessage);
711
847
  if (await this.publishPulledMessage(appEvent.event, ns, seq, appEvent.payload, false)) {
712
848
  publishedCount += 1;
713
849
  }
850
+ this.ensurePullOperationCurrent();
714
851
  continue;
715
852
  }
716
853
  const message = normalizeGroupMentionMode(rawMessage);
717
854
  if (this.recallEventFromGroupMessage(message)) {
718
855
  if (await this.publishGroupRecallTombstone(groupId, seq, message)) {
856
+ this.ensurePullOperationCurrent();
719
857
  this.markPublishedSeq(ns, seq);
720
858
  publishedCount += 1;
721
859
  }
722
860
  }
723
861
  else if (await this.publishPulledMessage('group.message_created', ns, seq, message, false)) {
862
+ this.ensurePullOperationCurrent();
724
863
  publishedCount += 1;
725
864
  }
726
865
  }
866
+ this.ensurePullOperationCurrent();
727
867
  if (messages.length > 0)
728
868
  client._seqTracker.onPullResult(ns, messages, afterSeq);
729
869
  const commitTarget = Math.max(client._seqTracker.getContiguousSeq(ns), retentionFloor, visibilityFloor, deferredServerCursor);
@@ -731,18 +871,21 @@ export class MessageDeliveryEngine {
731
871
  client._seqTracker.forceContiguousSeq(ns, commitTarget);
732
872
  }
733
873
  if (client._seqTracker.getContiguousSeq(ns) !== pageContigBefore) {
874
+ this.ensurePullOperationCurrent();
734
875
  await this.drainOrderedMessages(ns, undefined, false, false);
876
+ this.ensurePullOperationCurrent();
735
877
  await client._commitSeqTrackerState(ns);
736
878
  }
737
879
  committed = true;
738
880
  }
739
881
  catch (exc) {
740
- if (!committed && typeof client._seqTracker.restoreNamespaceSnapshot === 'function') {
882
+ if (this.isPullOperationCurrent() && !committed && typeof client._seqTracker.restoreNamespaceSnapshot === 'function') {
741
883
  client._seqTracker.restoreNamespaceSnapshot(ns, pageTrackerSnapshot);
742
884
  this.dropSeqTrackerPending(ns);
743
885
  }
744
886
  throw exc;
745
887
  }
888
+ this.ensurePullOperationCurrent();
746
889
  const committedAck = client._seqTracker.getContiguousSeq(ns);
747
890
  if (deferredServerCursor > 0 && committedAck >= deferredServerCursor) {
748
891
  coordinator.clearForwardCursor(ns, committedAck);
@@ -773,18 +916,19 @@ export class MessageDeliveryEngine {
773
916
  if (clampedAckSeq < pendingAckSeq) {
774
917
  throw new Error(`${ackMethod} cannot confirm pending Forward watermark ${pendingAckSeq}`);
775
918
  }
919
+ this.ensurePullOperationCurrent();
776
920
  await this.confirmPlainForwardAck(ns, ackMethod, pendingAckSeq, groupId);
777
921
  }
778
922
  return { rawCount: messages.length, publishedCount };
779
923
  }
780
- enqueueOrderedMessage(ns, event, seq, payload) {
924
+ enqueueOrderedMessage(ns, event, seq, payload, source = 'push') {
781
925
  const client = this.runtime.client;
782
926
  let queue = client._pendingOrderedMsgs.get(ns);
783
927
  if (!queue) {
784
928
  queue = new Map();
785
929
  client._pendingOrderedMsgs.set(ns, queue);
786
930
  }
787
- queue.set(seq, { event, payload });
931
+ queue.set(seq, { event, payload, source });
788
932
  if (queue.size > PENDING_ORDERED_LIMIT) {
789
933
  const drop = [...queue.keys()].sort((a, b) => a - b).slice(0, queue.size - PENDING_ORDERED_LIMIT);
790
934
  for (const oldSeq of drop)
@@ -794,7 +938,7 @@ export class MessageDeliveryEngine {
794
938
  isGroupEventNamespace(ns) {
795
939
  return ns.startsWith('group_event:');
796
940
  }
797
- async publishOrderedQueueItem(ns, event, seq, payload, pullResponse = false) {
941
+ async publishOrderedQueueItem(ns, event, seq, payload, pullResponse = false, source = 'push', batch) {
798
942
  const client = this.runtime.client;
799
943
  if (event === 'group.changed' && this.isGroupEventNamespace(ns)) {
800
944
  await this.publishOrderedGroupChanged(payload);
@@ -805,10 +949,10 @@ export class MessageDeliveryEngine {
805
949
  return;
806
950
  }
807
951
  if (pullResponse) {
808
- await client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload));
952
+ await client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload, source, ns, batch));
809
953
  return;
810
954
  }
811
- await client._publishAppEvent(event, payload);
955
+ await client._publishAppEvent(event, payload, source, ns, batch);
812
956
  }
813
957
  async publishOrderedGroupChanged(payload) {
814
958
  const client = this.runtime.client;
@@ -983,6 +1127,7 @@ export class MessageDeliveryEngine {
983
1127
  setIfPresent('type', firstValue(body.type, params.type, params.message_type, params.payload_type));
984
1128
  setIfPresent('kind', firstValue(body.kind, params.kind));
985
1129
  setIfPresent('version', firstValue(body.version, params.version));
1130
+ setIfPresent('message_id', firstValue(params.message_id, body.message_id, resultObj.message_id));
986
1131
  setIfPresent('timestamp', firstValue(params.timestamp, resultObj.timestamp, resultObj.created_at, resultObj.t_server, Date.now()));
987
1132
  envelope.encrypted = Boolean(encrypted);
988
1133
  const context = this.envelopeMetadata(params.context);
@@ -1325,8 +1470,9 @@ export class MessageDeliveryEngine {
1325
1470
  if (contig !== contigBefore)
1326
1471
  this.persistSeq(ns);
1327
1472
  }
1328
- async publishAppEvent(event, payload) {
1473
+ async publishAppEvent(event, payload, source = '', ns = '', batch) {
1329
1474
  const client = this.runtime.client;
1475
+ const generation = this.deliveryGeneration;
1330
1476
  if ((event === 'message.received' || event === 'group.message_created') && isJsonObject(payload)) {
1331
1477
  client._maybeAppendEchoTraceReceive(payload);
1332
1478
  }
@@ -1346,7 +1492,25 @@ export class MessageDeliveryEngine {
1346
1492
  client._clientLog.debug(`agent_md etag inject skipped: ${String(exc)}`);
1347
1493
  }
1348
1494
  }
1349
- await client._dispatcher.publish(event, this.normalizePublishedMessagePayload(event, payload));
1495
+ client._dispatcher.enqueue(event, this.normalizePublishedMessagePayload(event, payload));
1496
+ if (generation !== this.deliveryGeneration)
1497
+ return;
1498
+ if (source !== 'pull' && source !== 'tail' && source !== 'pending_retry'
1499
+ && source !== 'push' && source !== 'inline_push')
1500
+ return;
1501
+ if (source === 'push' || source === 'inline_push') {
1502
+ const localBatch = batch ?? this.createDeliveryChangeBatch(source);
1503
+ this.recordDeliveryChange(localBatch.changes, event, payload, ns, generation);
1504
+ if (!batch)
1505
+ await this.flushRealtimeDeliveryChanges(localBatch);
1506
+ }
1507
+ else {
1508
+ const changes = this.pendingPullDeliveryChanges ?? new Map();
1509
+ this.pendingPullDeliveryChanges = changes;
1510
+ this.recordDeliveryChange(changes, event, payload, ns, generation);
1511
+ if (client._rpcPipeline?.hasAnyPullActivity?.() !== true)
1512
+ await this.flushPullDeliveryChanges();
1513
+ }
1350
1514
  }
1351
1515
  messageTargetsCurrentInstance(message) {
1352
1516
  if (!isJsonObject(message))
@@ -1502,7 +1666,7 @@ export class MessageDeliveryEngine {
1502
1666
  const client = this.runtime.client;
1503
1667
  try {
1504
1668
  if (!isJsonObject(data)) {
1505
- await client._publishAppEvent('message.received', data);
1669
+ await client._publishAppEvent('message.received', data, 'push');
1506
1670
  return;
1507
1671
  }
1508
1672
  const msg = { ...data };
@@ -1554,7 +1718,7 @@ export class MessageDeliveryEngine {
1554
1718
  await client._publishEncryptedPushMessage('message.received', 'message.undecryptable', '', seq ?? 0, msg, false);
1555
1719
  return;
1556
1720
  }
1557
- await client._publishAppEvent('message.received', msg);
1721
+ await client._publishAppEvent('message.received', msg, 'push');
1558
1722
  }
1559
1723
  }
1560
1724
  catch (exc) {
@@ -1598,7 +1762,7 @@ export class MessageDeliveryEngine {
1598
1762
  const client = this.runtime.client;
1599
1763
  try {
1600
1764
  if (!isJsonObject(data)) {
1601
- await client._publishAppEvent('group.message_created', data);
1765
+ await client._publishAppEvent('group.message_created', data, 'push');
1602
1766
  return;
1603
1767
  }
1604
1768
  const msg = { ...data };
@@ -1677,7 +1841,7 @@ export class MessageDeliveryEngine {
1677
1841
  await client._publishEncryptedPushMessage('group.message_created', 'group.message_undecryptable', '', seq ?? 0, msg, true);
1678
1842
  return;
1679
1843
  }
1680
- await client._publishAppEvent('group.message_created', msg);
1844
+ await client._publishAppEvent('group.message_created', msg, 'push');
1681
1845
  }
1682
1846
  }
1683
1847
  catch (exc) {
@@ -1761,12 +1925,15 @@ export class MessageDeliveryEngine {
1761
1925
  return;
1762
1926
  const delayMs = Math.min(P2P_GAP_FILL_RETRY_BASE_MS * retryAttempt, P2P_GAP_FILL_RETRY_MAX_MS);
1763
1927
  client._gapFillDone.add(retryKey);
1928
+ const releasePullWork = client._rpcPipeline?.reservePullWork?.() ?? (() => { });
1764
1929
  client._clientLog.debug(`P2P message gap-fill retry scheduled: ns=${ns} attempt=${retryAttempt} delay_ms=${delayMs}`);
1765
1930
  globalThis.setTimeout(() => {
1766
1931
  client._gapFillDone.delete(retryKey);
1767
- if (client.state !== ConnectionState.READY || client._closing)
1932
+ if (client.state !== ConnectionState.READY || client._closing) {
1933
+ releasePullWork();
1768
1934
  return;
1769
- client._safeAsync(this.fillP2pGap(retryAttempt));
1935
+ }
1936
+ client._safeAsync(this.fillP2pGap(retryAttempt).finally(releasePullWork));
1770
1937
  }, delayMs);
1771
1938
  }
1772
1939
  async fillP2pGap(retryAttempt = 0) {
@@ -2075,9 +2242,36 @@ export class MessageDeliveryEngine {
2075
2242
  if (!groupId)
2076
2243
  return;
2077
2244
  const ns = `group:${groupId}`;
2078
- client._safeAsync(this.runGroupForwardRecovery(groupId, ns, 50, true).catch((exc) => {
2079
- client._clientLog?.debug(`online unread hint background Forward failed: ns=${ns} err=${formatDeliveryError(exc)}`);
2080
- }));
2245
+ const targets = this.onlineUnreadHintTargets ?? new Map();
2246
+ const owners = this.onlineUnreadHintOwners ?? new Set();
2247
+ this.onlineUnreadHintTargets = targets;
2248
+ this.onlineUnreadHintOwners = owners;
2249
+ targets.set(ns, Math.max(targets.get(ns) ?? 0, positiveSafeSequenceHint(data.seq)));
2250
+ if (owners.has(ns))
2251
+ return;
2252
+ owners.add(ns);
2253
+ client._safeAsync((async () => {
2254
+ try {
2255
+ while (true) {
2256
+ const throughSeq = targets.get(ns) ?? 0;
2257
+ const ackBefore = this.syncState(ns).ack;
2258
+ await this.runGroupForwardRecovery(groupId, ns, 50, true, throughSeq);
2259
+ const ackAfter = this.syncState(ns).ack;
2260
+ const target = targets.get(ns) ?? 0;
2261
+ if (ackAfter >= target || ackAfter <= ackBefore)
2262
+ return;
2263
+ }
2264
+ }
2265
+ catch (exc) {
2266
+ client._clientLog?.debug(`online unread hint background Forward failed: ns=${ns} err=${formatDeliveryError(exc)}`);
2267
+ }
2268
+ finally {
2269
+ if (this.onlineUnreadHintTargets === targets)
2270
+ targets.delete(ns);
2271
+ if (this.onlineUnreadHintOwners === owners)
2272
+ owners.delete(ns);
2273
+ }
2274
+ })());
2081
2275
  }
2082
2276
  enqueueOnlineUnreadEventHint(data) {
2083
2277
  const client = this.runtime.client;
@@ -2109,17 +2303,19 @@ export class MessageDeliveryEngine {
2109
2303
  const key = pipeline.pullGateKeyForCall('message.pull', request);
2110
2304
  return await pipeline.runPullSerialized(key, invoke, background);
2111
2305
  }
2112
- async runGroupForwardRecovery(groupId, ns, pageLimit, background = false) {
2306
+ async runGroupForwardRecovery(groupId, ns, pageLimit, background = false, throughSeq = 0) {
2113
2307
  const client = this.runtime.client;
2308
+ const state = this.syncState(ns);
2309
+ const maxPages = this.forwardMaxPages(state.ack, throughSeq, pageLimit);
2114
2310
  const request = {
2115
2311
  group_id: groupId,
2116
- after_seq: this.syncState(ns).ack,
2312
+ after_seq: state.ack,
2117
2313
  limit: pageLimit,
2118
- max_pages: 1,
2314
+ max_pages: maxPages,
2119
2315
  };
2120
2316
  const invoke = async () => {
2121
2317
  const after = this.syncState(ns).ack;
2122
- const messages = await client._pullGroupV2(groupId, after, pageLimit, { gateLocked: true, maxPages: 1 });
2318
+ const messages = await client._pullGroupV2(groupId, after, pageLimit, { gateLocked: true, maxPages });
2123
2319
  return { messages, raw_count: messages.length };
2124
2320
  };
2125
2321
  const pipeline = client._rpcPipeline;
@@ -2135,6 +2331,10 @@ export class MessageDeliveryEngine {
2135
2331
  const ack = Number(tracker.getContiguousSeq(ns) || 0);
2136
2332
  return { ack, tail: ack, head: Math.max(ack, Number(tracker.getMaxSeenSeq?.(ns) || 0)) };
2137
2333
  }
2334
+ forwardMaxPages(ack, throughSeq, pageLimit) {
2335
+ const limit = Number.isSafeInteger(pageLimit) && pageLimit > 0 ? pageLimit : 1;
2336
+ return Math.max(1, Math.ceil(Math.max(0, throughSeq - ack) / limit));
2337
+ }
2138
2338
  inlineMessage(data) {
2139
2339
  if (!isJsonObject(data))
2140
2340
  return null;
@@ -2434,7 +2634,7 @@ export class MessageDeliveryEngine {
2434
2634
  ? tracker.snapshotNamespace(ns)
2435
2635
  : null;
2436
2636
  try {
2437
- const published = await this.publishPulledMessage(event, ns, seq, payload);
2637
+ const published = await this.publishPulledMessage(event, ns, seq, payload, true, 'inline_push');
2438
2638
  if (!this.isInlineGenerationCurrent(generation))
2439
2639
  return false;
2440
2640
  if (!published)
@@ -2642,16 +2842,24 @@ export class MessageDeliveryEngine {
2642
2842
  // 落盘的 ack 为起点,至多补一页普通 Forward 即可恢复该小窗口。
2643
2843
  const tailMissedDuringPull = tailCompleted
2644
2844
  && Number(client._seqTracker.getMaxSeenSeq(ns) || 0) > state.head;
2845
+ const maxSeen = Number(client._seqTracker.getMaxSeenSeq(ns) || 0);
2645
2846
  const pendingThroughSeq = tailMissedDuringPull
2646
- ? Number(client._seqTracker.getMaxSeenSeq(ns) || 0)
2847
+ ? maxSeen
2647
2848
  : 0;
2648
2849
  const tailForward = tailCompleted && (state.ack < state.tail - 1 || tailMissedDuringPull);
2649
2850
  const backgroundWindowForward = order[0] === 'forward' && !headForward;
2650
2851
  if (headForward || tailForward
2651
2852
  || (client._sessionOptions?.background_sync !== false && backgroundWindowForward)) {
2652
- result = await run(false, !(headForward || tailForward), headForward ? 2 : order[0] === 'tail' ? 1 : undefined, headForward || tailMissedDuringPull);
2653
- if (pendingThroughSeq > 0)
2654
- this.consumePendingPull(ns, pendingThroughSeq);
2853
+ const forwardTarget = Math.max(headForward ? Math.max(pushSeq, maxSeen) : 0, (tailForward || backgroundWindowForward) ? state.tail - 1 : 0, tailMissedDuringPull ? maxSeen : 0);
2854
+ const forwardMaxPages = this.forwardMaxPages(state.ack, forwardTarget, pageLimit);
2855
+ result = await run(false, !(headForward || tailForward), forwardMaxPages, headForward || tailMissedDuringPull);
2856
+ if (pendingThroughSeq > 0) {
2857
+ const committedAck = this.syncState(ns).ack;
2858
+ if (committedAck >= pendingThroughSeq)
2859
+ this.consumePendingPull(ns, pendingThroughSeq);
2860
+ else if (committedAck <= state.ack)
2861
+ this.markPendingPullNoProgress(ns, committedAck);
2862
+ }
2655
2863
  }
2656
2864
  const finalState = this.syncState(ns);
2657
2865
  if (tailCompleted && finalState.ack < finalState.tail - 1) {
@@ -2706,16 +2914,24 @@ export class MessageDeliveryEngine {
2706
2914
  // 期间新到达的序号;绝不重新规划或再执行 Tail。
2707
2915
  const tailMissedDuringPull = tailCompleted
2708
2916
  && Number(client._seqTracker.getMaxSeenSeq(ns) || 0) > state.head;
2917
+ const maxSeen = Number(client._seqTracker.getMaxSeenSeq(ns) || 0);
2709
2918
  const pendingThroughSeq = tailMissedDuringPull
2710
- ? Number(client._seqTracker.getMaxSeenSeq(ns) || 0)
2919
+ ? maxSeen
2711
2920
  : 0;
2712
2921
  const tailForward = tailCompleted && (state.ack < state.tail - 1 || tailMissedDuringPull);
2713
2922
  const backgroundWindowForward = order[0] === 'forward' && !headForward;
2714
2923
  if (headForward || tailForward
2715
2924
  || (client._sessionOptions?.background_sync !== false && backgroundWindowForward)) {
2716
- result = await run(false, !(headForward || tailForward), headForward ? 2 : order[0] === 'tail' ? 1 : undefined, headForward || tailMissedDuringPull);
2717
- if (pendingThroughSeq > 0)
2718
- this.consumePendingPull(ns, pendingThroughSeq);
2925
+ const forwardTarget = Math.max(headForward ? Math.max(pushSeq, maxSeen) : 0, (tailForward || backgroundWindowForward) ? state.tail - 1 : 0, tailMissedDuringPull ? maxSeen : 0);
2926
+ const forwardMaxPages = this.forwardMaxPages(state.ack, forwardTarget, pageLimit);
2927
+ result = await run(false, !(headForward || tailForward), forwardMaxPages, headForward || tailMissedDuringPull);
2928
+ if (pendingThroughSeq > 0) {
2929
+ const committedAck = this.syncState(ns).ack;
2930
+ if (committedAck >= pendingThroughSeq)
2931
+ this.consumePendingPull(ns, pendingThroughSeq);
2932
+ else if (committedAck <= state.ack)
2933
+ this.markPendingPullNoProgress(ns, committedAck);
2934
+ }
2719
2935
  }
2720
2936
  const finalState = this.syncState(ns);
2721
2937
  if (tailCompleted && finalState.ack < finalState.tail - 1) {
@@ -2998,13 +3214,13 @@ export class MessageDeliveryEngine {
2998
3214
  }
2999
3215
  }
3000
3216
  catch (exc) {
3001
- client._dispatcher.publish('seq_tracker.persist_error', {
3217
+ client._dispatcher.enqueue('seq_tracker.persist_error', {
3002
3218
  phase: 'restore',
3003
3219
  aid,
3004
3220
  device_id: deviceId,
3005
3221
  slot_id: slotId,
3006
3222
  error: String(exc),
3007
- }).catch(() => { });
3223
+ });
3008
3224
  }
3009
3225
  }
3010
3226
  async migrateSeqStateGroupIds(state) {
@@ -3212,13 +3428,13 @@ export class MessageDeliveryEngine {
3212
3428
  catch (exc) {
3213
3429
  const error = formatDeliveryError(exc);
3214
3430
  client._clientLog.warn(`save SeqTracker state failed: ${error}`);
3215
- client._dispatcher.publish('seq_tracker.persist_error', {
3431
+ client._dispatcher.enqueue('seq_tracker.persist_error', {
3216
3432
  phase: 'save',
3217
3433
  aid,
3218
3434
  device_id: deviceId,
3219
3435
  slot_id: slotId,
3220
3436
  error: String(error),
3221
- }).catch(() => { });
3437
+ });
3222
3438
  if (throwOnError)
3223
3439
  throw exc;
3224
3440
  }
@@ -3337,8 +3553,9 @@ export class MessageDeliveryEngine {
3337
3553
  }
3338
3554
  return params;
3339
3555
  }
3340
- async drainOrderedMessages(ns, beforeSeq, pullResponse = false, persist = true) {
3556
+ async drainOrderedMessages(ns, beforeSeq, pullResponse = false, persist = true, batch, source = 'pull') {
3341
3557
  const client = this.runtime.client;
3558
+ this.ensurePullOperationCurrent();
3342
3559
  const queue = client._pendingOrderedMsgs.get(ns);
3343
3560
  if (!queue || queue.size === 0)
3344
3561
  return;
@@ -3347,83 +3564,128 @@ export class MessageDeliveryEngine {
3347
3564
  .filter((seq) => seq <= contig && (beforeSeq === undefined || seq < beforeSeq))
3348
3565
  .sort((a, b) => a - b);
3349
3566
  let delivered = false;
3350
- for (const seq of ready) {
3351
- const item = queue.get(seq);
3352
- queue.delete(seq);
3353
- if (!item || client._pushedSeqs.get(ns)?.has(seq))
3354
- continue;
3355
- await this.publishOrderedQueueItem(ns, item.event, seq, item.payload, pullResponse);
3356
- this.markPublishedSeq(ns, seq);
3357
- delivered = true;
3567
+ const drainBatches = new Map();
3568
+ try {
3569
+ for (const seq of ready) {
3570
+ this.ensurePullOperationCurrent();
3571
+ const item = queue.get(seq);
3572
+ queue.delete(seq);
3573
+ if (!item || client._pushedSeqs.get(ns)?.has(seq))
3574
+ continue;
3575
+ const itemSource = item.source ?? source;
3576
+ let itemBatch = batch;
3577
+ if ((itemSource === 'push' || itemSource === 'inline_push')
3578
+ && (!batch || batch.trigger !== itemSource)) {
3579
+ itemBatch = drainBatches.get(itemSource);
3580
+ if (!itemBatch) {
3581
+ itemBatch = this.createDeliveryChangeBatch(itemSource);
3582
+ drainBatches.set(itemSource, itemBatch);
3583
+ }
3584
+ }
3585
+ await this.publishOrderedQueueItem(ns, item.event, seq, item.payload, pullResponse, itemSource, itemBatch);
3586
+ this.ensurePullOperationCurrent();
3587
+ this.markPublishedSeq(ns, seq);
3588
+ delivered = true;
3589
+ }
3590
+ }
3591
+ finally {
3592
+ for (const drainBatch of drainBatches.values()) {
3593
+ await this.flushRealtimeDeliveryChanges(drainBatch);
3594
+ }
3358
3595
  }
3359
3596
  if (queue.size === 0) {
3360
3597
  client._pendingOrderedMsgs.delete(ns);
3361
- if (delivered && persist)
3598
+ if (delivered && persist) {
3599
+ this.ensurePullOperationCurrent();
3362
3600
  await this.saveSeqTrackerState();
3601
+ }
3363
3602
  }
3364
3603
  }
3365
- async publishOrderedMessage(event, ns, seq, payload) {
3604
+ async publishOrderedMessage(event, ns, seq, payload, source = 'push', operationBatch) {
3366
3605
  const client = this.runtime.client;
3367
- const seqNum = Number(seq);
3368
- if (!Number.isFinite(seqNum) || !Number.isInteger(seqNum) || seqNum <= 0) {
3369
- await this.publishOrderedQueueItem(ns, event, seqNum, payload);
3370
- return true;
3371
- }
3372
- if (client._pushedSeqs.get(ns)?.has(seqNum)) {
3606
+ const ownsBatch = !operationBatch && (source === 'push' || source === 'inline_push');
3607
+ const batch = operationBatch ?? (ownsBatch ? this.createDeliveryChangeBatch(source) : undefined);
3608
+ try {
3609
+ const seqNum = Number(seq);
3610
+ if (!Number.isFinite(seqNum) || !Number.isInteger(seqNum) || seqNum <= 0) {
3611
+ await this.publishOrderedQueueItem(ns, event, seqNum, payload, false, source, batch);
3612
+ return true;
3613
+ }
3614
+ if (client._pushedSeqs.get(ns)?.has(seqNum)) {
3615
+ const queue = client._pendingOrderedMsgs.get(ns);
3616
+ queue?.delete(seqNum);
3617
+ if (queue && queue.size === 0)
3618
+ client._pendingOrderedMsgs.delete(ns);
3619
+ return false;
3620
+ }
3621
+ const contig = client._seqTracker.getContiguousSeq(ns);
3622
+ if (seqNum > contig) {
3623
+ this.enqueueOrderedMessage(ns, event, seqNum, payload, source);
3624
+ return false;
3625
+ }
3626
+ await this.drainOrderedMessages(ns, seqNum, true, true, batch, source);
3627
+ if (client._pushedSeqs.get(ns)?.has(seqNum))
3628
+ return false;
3373
3629
  const queue = client._pendingOrderedMsgs.get(ns);
3374
3630
  queue?.delete(seqNum);
3375
3631
  if (queue && queue.size === 0)
3376
3632
  client._pendingOrderedMsgs.delete(ns);
3377
- return false;
3633
+ await this.publishOrderedQueueItem(ns, event, seqNum, payload, false, source, batch);
3634
+ this.markPublishedSeq(ns, seqNum);
3635
+ await this.drainOrderedMessages(ns, undefined, false, true, batch, source);
3636
+ if (!client._pendingOrderedMsgs.get(ns))
3637
+ await this.saveSeqTrackerState();
3638
+ return true;
3378
3639
  }
3379
- const contig = client._seqTracker.getContiguousSeq(ns);
3380
- if (seqNum > contig) {
3381
- this.enqueueOrderedMessage(ns, event, seqNum, payload);
3382
- return false;
3640
+ finally {
3641
+ if (ownsBatch && batch)
3642
+ await this.flushRealtimeDeliveryChanges(batch);
3383
3643
  }
3384
- await this.drainOrderedMessages(ns, seqNum, true);
3385
- if (client._pushedSeqs.get(ns)?.has(seqNum))
3386
- return false;
3387
- const queue = client._pendingOrderedMsgs.get(ns);
3388
- queue?.delete(seqNum);
3389
- if (queue && queue.size === 0)
3390
- client._pendingOrderedMsgs.delete(ns);
3391
- await this.publishOrderedQueueItem(ns, event, seqNum, payload);
3392
- this.markPublishedSeq(ns, seqNum);
3393
- await this.drainOrderedMessages(ns);
3394
- if (!client._pendingOrderedMsgs.get(ns))
3395
- await this.saveSeqTrackerState();
3396
- return true;
3397
3644
  }
3398
- async publishPulledMessage(event, ns, seq, payload, persist = true) {
3645
+ async publishPulledMessage(event, ns, seq, payload, persist = true, source = 'pull', operationBatch) {
3399
3646
  const client = this.runtime.client;
3400
- const seqNum = Number(seq);
3401
- if (!Number.isFinite(seqNum) || !Number.isInteger(seqNum) || seqNum <= 0 || !ns) {
3402
- if (event === 'message.recalled') {
3403
- return await client._withPullResponseProcessing(ns, () => this.publishMessageRecallTombstone(seq, payload));
3647
+ const ownsBatch = !operationBatch && (source === 'push' || source === 'inline_push');
3648
+ const batch = operationBatch ?? (ownsBatch ? this.createDeliveryChangeBatch(source) : undefined);
3649
+ try {
3650
+ this.ensurePullOperationCurrent();
3651
+ const seqNum = Number(seq);
3652
+ if (!Number.isFinite(seqNum) || !Number.isInteger(seqNum) || seqNum <= 0 || !ns) {
3653
+ if (event === 'message.recalled') {
3654
+ const published = await client._withPullResponseProcessing(ns, () => this.publishMessageRecallTombstone(seq, payload));
3655
+ this.ensurePullOperationCurrent();
3656
+ return published;
3657
+ }
3658
+ await client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload, source, ns, batch));
3659
+ this.ensurePullOperationCurrent();
3660
+ return true;
3404
3661
  }
3405
- await client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload));
3406
- return true;
3407
- }
3408
- const queue = client._pendingOrderedMsgs.get(ns);
3409
- if (client._pushedSeqs.get(ns)?.has(seqNum)) {
3662
+ const queue = client._pendingOrderedMsgs.get(ns);
3663
+ if (client._pushedSeqs.get(ns)?.has(seqNum)) {
3664
+ queue?.delete(seqNum);
3665
+ if (queue && queue.size === 0)
3666
+ client._pendingOrderedMsgs.delete(ns);
3667
+ return false;
3668
+ }
3669
+ await this.drainOrderedMessages(ns, seqNum, false, persist, batch, source);
3670
+ this.ensurePullOperationCurrent();
3410
3671
  queue?.delete(seqNum);
3411
3672
  if (queue && queue.size === 0)
3412
3673
  client._pendingOrderedMsgs.delete(ns);
3413
- return false;
3414
- }
3415
- await this.drainOrderedMessages(ns, seqNum, false, persist);
3416
- queue?.delete(seqNum);
3417
- if (queue && queue.size === 0)
3418
- client._pendingOrderedMsgs.delete(ns);
3419
- if (event === 'message.recalled') {
3420
- const published = await client._withPullResponseProcessing(ns, () => this.publishMessageRecallTombstone(seqNum, payload));
3674
+ if (event === 'message.recalled') {
3675
+ const published = await client._withPullResponseProcessing(ns, () => this.publishMessageRecallTombstone(seqNum, payload));
3676
+ this.ensurePullOperationCurrent();
3677
+ this.markPublishedSeq(ns, seqNum);
3678
+ return published;
3679
+ }
3680
+ await client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload, source, ns, batch));
3681
+ this.ensurePullOperationCurrent();
3421
3682
  this.markPublishedSeq(ns, seqNum);
3422
- return published;
3683
+ return true;
3684
+ }
3685
+ finally {
3686
+ if (ownsBatch && batch)
3687
+ await this.flushRealtimeDeliveryChanges(batch);
3423
3688
  }
3424
- await client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload));
3425
- this.markPublishedSeq(ns, seqNum);
3426
- return true;
3427
3689
  }
3428
3690
  }
3429
3691
  //# sourceMappingURL=delivery.js.map