@agentunion/fastaun-browser 0.5.9 → 0.5.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/_packed_docs/CHANGELOG.md +64 -0
  3. package/_packed_docs/INDEX.md +3 -3
  4. package/_packed_docs/KITE_DOCS_GUIDE.md +1 -1
  5. package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +6 -5
  6. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +102 -8
  7. package/_packed_docs/sdk/09-group-rpc-manual.md +18 -3
  8. package/_packed_docs/sdk/09-message-rpc-manual.md +34 -10
  9. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +3 -2
  10. package/_packed_docs/sdk/INDEX.md +2 -1
  11. package/dist/agent-md.d.ts.map +1 -1
  12. package/dist/agent-md.js +23 -1
  13. package/dist/agent-md.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 +1374 -267
  18. package/dist/client/delivery.d.ts +34 -11
  19. package/dist/client/delivery.d.ts.map +1 -1
  20. package/dist/client/delivery.js +390 -100
  21. package/dist/client/delivery.js.map +1 -1
  22. package/dist/client/group-state.js +6 -6
  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 +34 -38
  26. package/dist/client/lifecycle.js.map +1 -1
  27. package/dist/client/rpc-pipeline.d.ts +4 -0
  28. package/dist/client/rpc-pipeline.d.ts.map +1 -1
  29. package/dist/client/rpc-pipeline.js +70 -4
  30. package/dist/client/rpc-pipeline.js.map +1 -1
  31. package/dist/client/v2-e2ee.d.ts +4 -1
  32. package/dist/client/v2-e2ee.d.ts.map +1 -1
  33. package/dist/client/v2-e2ee.js +169 -38
  34. package/dist/client/v2-e2ee.js.map +1 -1
  35. package/dist/client.d.ts +1 -1
  36. package/dist/client.d.ts.map +1 -1
  37. package/dist/client.js +83 -33
  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/register-flow.d.ts.map +1 -1
  44. package/dist/register-flow.js +16 -2
  45. package/dist/register-flow.js.map +1 -1
  46. package/dist/transport.d.ts +10 -1
  47. package/dist/transport.d.ts.map +1 -1
  48. package/dist/transport.js +427 -29
  49. package/dist/transport.js.map +1 -1
  50. package/dist/version.d.ts +1 -1
  51. package/dist/version.d.ts.map +1 -1
  52. package/dist/version.js +1 -1
  53. package/dist/version.js.map +1 -1
  54. package/package.json +1 -1
@@ -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;
@@ -62,6 +63,26 @@ function nonNegativeSafeSequenceHint(value) {
62
63
  return null;
63
64
  return value;
64
65
  }
66
+ function canonicalDeliverySource(source) {
67
+ switch (String(source ?? '').trim()) {
68
+ case 'group-push':
69
+ case 'ordered':
70
+ case 'legacy':
71
+ return 'push';
72
+ case 'tail':
73
+ case 'pull-drained':
74
+ case 'pull_drained':
75
+ return 'pull';
76
+ case 'inline-push':
77
+ return 'inline_push';
78
+ case 'pending-retry':
79
+ return 'pending_retry';
80
+ case '':
81
+ return 'direct';
82
+ default:
83
+ return String(source).trim();
84
+ }
85
+ }
65
86
  function deduplicatePlainForwardPageMessages(value, afterSeq) {
66
87
  if (!Array.isArray(value))
67
88
  return [];
@@ -94,6 +115,11 @@ function p2pAppEventFromPlainPullMessage(message) {
94
115
  }
95
116
  export class MessageDeliveryEngine {
96
117
  runtime;
118
+ syncRun = 0;
119
+ syncingNamespaces = new Set();
120
+ syncSessions = new Map();
121
+ pendingPullDeliveryChanges = null;
122
+ deliveryGeneration = 0;
97
123
  realtimeTailResults = null;
98
124
  realtimeSyncing = null;
99
125
  pendingP2pPullUpper = null;
@@ -113,7 +139,10 @@ export class MessageDeliveryEngine {
113
139
  this.runtime = runtime;
114
140
  }
115
141
  resetInlineAckState() {
142
+ this.syncStopped('aborted');
116
143
  this.inlineGeneration += 1;
144
+ this.deliveryGeneration += 1;
145
+ this.pendingPullDeliveryChanges = null;
117
146
  void this.runtime.client._rpcPipeline?.invalidatePulls?.();
118
147
  this.realtimeSyncing = null;
119
148
  this.pendingP2pPullUpper = null;
@@ -126,6 +155,8 @@ export class MessageDeliveryEngine {
126
155
  this.pendingP2PInlineAcks = null;
127
156
  this.pendingGroupInlineAcks = null;
128
157
  this.pendingGroupInlineAckNamespaces = null;
158
+ this.syncingNamespaces.clear();
159
+ this.syncSessions.clear();
129
160
  }
130
161
  isInlineGenerationCurrent(generation) {
131
162
  return generation === this.inlineGeneration;
@@ -158,6 +189,95 @@ export class MessageDeliveryEngine {
158
189
  endRealtimeSync(ns) {
159
190
  this.realtimeSyncing?.delete(ns);
160
191
  }
192
+ onPullStarted(ns) {
193
+ if (!ns || this.syncingNamespaces.has(ns))
194
+ return;
195
+ if (this.syncingNamespaces.size === 0) {
196
+ this.syncRun += 1;
197
+ this.syncSessions.clear();
198
+ this.runtime.client._dispatcher?.enqueue?.('sync.started', {
199
+ run_id: this.syncRun,
200
+ source: 'pull',
201
+ syncing: true,
202
+ namespaces_pending: 1,
203
+ received_total: 0,
204
+ namespace: ns,
205
+ started_at: Date.now(),
206
+ });
207
+ }
208
+ this.syncingNamespaces.add(ns);
209
+ this.syncSessions.set(ns, { pages: 0, raw: 0, pulled: 0 });
210
+ }
211
+ onPullPage(ns, pulledCount, hasMore, maxSeen, remaining, rawCount = pulledCount, currentSeq, targetSeq) {
212
+ this.onPullStarted(ns);
213
+ if (!ns)
214
+ return;
215
+ const session = this.syncSessions.get(ns) ?? { pages: 0, raw: 0, pulled: 0 };
216
+ session.pages += 1;
217
+ session.raw += Math.max(0, rawCount);
218
+ session.pulled += Math.max(0, pulledCount);
219
+ this.syncSessions.set(ns, session);
220
+ const received = session.pulled;
221
+ const progress = {
222
+ run_id: this.syncRun,
223
+ source: 'pull',
224
+ namespace: ns,
225
+ page: session.pages,
226
+ page_raw_count: Math.max(0, rawCount),
227
+ page_pulled_count: Math.max(0, pulledCount),
228
+ pulled_count: received,
229
+ received_total: received,
230
+ batch_size: Math.max(0, pulledCount),
231
+ estimated_remaining: hasMore ? undefined : 0,
232
+ max_seen: maxSeen,
233
+ has_more: hasMore,
234
+ };
235
+ if (typeof currentSeq === 'number' && Number.isSafeInteger(currentSeq) && currentSeq >= 0) {
236
+ progress.current_seq = currentSeq;
237
+ }
238
+ if (typeof targetSeq === 'number' && Number.isSafeInteger(targetSeq) && targetSeq >= 0) {
239
+ progress.target_seq = targetSeq;
240
+ }
241
+ if (typeof remaining === 'number' && Number.isSafeInteger(remaining) && remaining >= 0) {
242
+ progress.remaining = remaining;
243
+ progress.estimated_remaining = progress.remaining;
244
+ }
245
+ this.runtime.client._dispatcher?.enqueue?.('sync.progress', progress);
246
+ }
247
+ onPullAborted() {
248
+ this.syncStopped('aborted');
249
+ }
250
+ syncStopped(reason = 'pull_drained') {
251
+ if (this.syncingNamespaces.size === 0)
252
+ return;
253
+ const sessions = [...this.syncSessions.entries()];
254
+ const receivedTotal = sessions.reduce((total, [, session]) => total + session.pulled, 0);
255
+ this.syncingNamespaces.clear();
256
+ this.syncSessions.clear();
257
+ const payload = {
258
+ run_id: this.syncRun,
259
+ source: 'pull',
260
+ syncing: false,
261
+ namespaces_pending: 0,
262
+ received_total: receivedTotal,
263
+ reason,
264
+ stopped_at: Date.now(),
265
+ };
266
+ if (sessions.length === 1) {
267
+ const [namespace, session] = sessions[0];
268
+ payload.namespace = namespace;
269
+ payload.pages = session.pages;
270
+ payload.pulled_count = session.pulled;
271
+ }
272
+ else if (sessions.length > 1) {
273
+ payload.namespaces = sessions.map(([namespace]) => namespace);
274
+ payload.pages = sessions.reduce((total, [, session]) => total + session.pages, 0);
275
+ payload.pulled_count = receivedTotal;
276
+ }
277
+ this.runtime.client._dispatcher?.enqueue?.('sync.stopped', {
278
+ ...payload,
279
+ });
280
+ }
161
281
  hasPendingPull(ns) {
162
282
  const pending = ns.startsWith('p2p:') ? this.pendingP2pPullUpper : this.pendingGroupPullUpper;
163
283
  return (pending?.get(ns) ?? 0) > 0;
@@ -167,6 +287,93 @@ export class MessageDeliveryEngine {
167
287
  return;
168
288
  this.schedulePendingPullIfNeeded(ns, 'pull-gate-idle');
169
289
  }
290
+ onPullWorkSettled() {
291
+ const pipeline = this.runtime.client._rpcPipeline;
292
+ if (pipeline?.hasAnyPullActivity?.() === true)
293
+ return;
294
+ void this.flushPullDeliveryChanges().finally(() => this.syncStopped('pull_drained'));
295
+ }
296
+ deliveryChangeNamespace(event, payload, ns = '') {
297
+ if (event === 'message.received')
298
+ return 'p2p';
299
+ if (event !== 'group.message_created')
300
+ return '';
301
+ if (!isJsonObject(payload))
302
+ return '';
303
+ const aid = String(this.runtime.client._aid ?? '').trim().toLowerCase();
304
+ const dot = aid.indexOf('.');
305
+ const localIssuer = dot > 0 ? aid.slice(dot + 1) : '';
306
+ const fallback = ns.startsWith('group:') ? ns.slice('group:'.length) : '';
307
+ const groupAid = normalizeGroupAid(payload.group_aid ?? payload.group_id ?? fallback, { localIssuer });
308
+ return groupAid.includes('.') ? `group:${groupAid}` : '';
309
+ }
310
+ isDeliverableMessageBody(event, payload) {
311
+ if ((event !== 'message.received' && event !== 'group.message_created') || !isJsonObject(payload))
312
+ return false;
313
+ const messageId = typeof payload.message_id === 'string' && payload.message_id.trim().length > 0;
314
+ const seq = typeof payload.seq === 'number' && Number.isSafeInteger(payload.seq) && payload.seq > 0;
315
+ return messageId || seq;
316
+ }
317
+ recordDeliveryChange(changes, event, payload, ns, generation) {
318
+ if (generation !== this.deliveryGeneration || !this.isDeliverableMessageBody(event, payload))
319
+ return;
320
+ const namespace = this.deliveryChangeNamespace(event, payload, ns);
321
+ if (!namespace)
322
+ return;
323
+ changes.set(namespace, (changes.get(namespace) ?? 0) + 1);
324
+ }
325
+ deliveryChangesPayload(changes) {
326
+ return [...changes.entries()]
327
+ .filter(([, deliveredCount]) => deliveredCount > 0)
328
+ .sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0)
329
+ .map(([namespace, deliveredCount]) => ({ namespace, delivered_count: deliveredCount }));
330
+ }
331
+ createDeliveryChangeBatch(trigger) {
332
+ return { generation: this.deliveryGeneration, trigger, changes: new Map(), closed: false };
333
+ }
334
+ async flushRealtimeDeliveryChanges(batch) {
335
+ if (batch.closed)
336
+ return;
337
+ batch.closed = true;
338
+ if (batch.generation !== this.deliveryGeneration)
339
+ return;
340
+ if (this.deliveryChangesPayload(batch.changes).length === 0)
341
+ return;
342
+ const pendingPull = this.pendingPullDeliveryChanges;
343
+ this.pendingPullDeliveryChanges = null;
344
+ if (pendingPull) {
345
+ for (const [namespace, count] of pendingPull) {
346
+ batch.changes.set(namespace, (batch.changes.get(namespace) ?? 0) + count);
347
+ }
348
+ }
349
+ if (batch.generation !== this.deliveryGeneration)
350
+ return;
351
+ const changes = this.deliveryChangesPayload(batch.changes);
352
+ if (changes.length === 0)
353
+ return;
354
+ this.runtime.client._dispatcher.enqueue('delivery.changed', {
355
+ trigger: batch.trigger,
356
+ source: batch.trigger,
357
+ changes,
358
+ });
359
+ }
360
+ async flushPullDeliveryChanges() {
361
+ const generation = this.deliveryGeneration;
362
+ const pending = this.pendingPullDeliveryChanges;
363
+ if (!pending || pending.size === 0 || generation !== this.deliveryGeneration)
364
+ return;
365
+ this.pendingPullDeliveryChanges = null;
366
+ if (generation !== this.deliveryGeneration)
367
+ return;
368
+ const changes = this.deliveryChangesPayload(pending);
369
+ if (changes.length === 0)
370
+ return;
371
+ this.runtime.client._dispatcher.enqueue('delivery.changed', {
372
+ trigger: 'pull_drained',
373
+ source: 'pull',
374
+ changes,
375
+ });
376
+ }
170
377
  recordPendingPull(ns, seq) {
171
378
  if (!ns || !Number.isSafeInteger(seq) || seq <= 0)
172
379
  return;
@@ -768,7 +975,7 @@ export class MessageDeliveryEngine {
768
975
  }
769
976
  const message = normalizeGroupMentionMode(rawMessage);
770
977
  if (this.recallEventFromGroupMessage(message)) {
771
- if (await this.publishGroupRecallTombstone(groupId, seq, message)) {
978
+ if (await this.publishGroupRecallTombstone(groupId, seq, message, 'pull')) {
772
979
  this.ensurePullOperationCurrent();
773
980
  this.markPublishedSeq(ns, seq);
774
981
  publishedCount += 1;
@@ -835,16 +1042,19 @@ export class MessageDeliveryEngine {
835
1042
  this.ensurePullOperationCurrent();
836
1043
  await this.confirmPlainForwardAck(ns, ackMethod, pendingAckSeq, groupId);
837
1044
  }
1045
+ const remaining = typeof response.remaining === 'number' && Number.isSafeInteger(response.remaining) && response.remaining >= 0
1046
+ ? response.remaining : null;
1047
+ this.onPullPage(ns, publishedCount, response.has_more === true, messages.length > 0 ? Number(messages[messages.length - 1].seq ?? 0) : undefined, remaining, messages.length, client._seqTracker.getContiguousSeq(ns), messages.length > 0 ? Number(messages[messages.length - 1].seq ?? 0) : undefined);
838
1048
  return { rawCount: messages.length, publishedCount };
839
1049
  }
840
- enqueueOrderedMessage(ns, event, seq, payload) {
1050
+ enqueueOrderedMessage(ns, event, seq, payload, source = 'push') {
841
1051
  const client = this.runtime.client;
842
1052
  let queue = client._pendingOrderedMsgs.get(ns);
843
1053
  if (!queue) {
844
1054
  queue = new Map();
845
1055
  client._pendingOrderedMsgs.set(ns, queue);
846
1056
  }
847
- queue.set(seq, { event, payload });
1057
+ queue.set(seq, { event, payload, source });
848
1058
  if (queue.size > PENDING_ORDERED_LIMIT) {
849
1059
  const drop = [...queue.keys()].sort((a, b) => a - b).slice(0, queue.size - PENDING_ORDERED_LIMIT);
850
1060
  for (const oldSeq of drop)
@@ -854,23 +1064,23 @@ export class MessageDeliveryEngine {
854
1064
  isGroupEventNamespace(ns) {
855
1065
  return ns.startsWith('group_event:');
856
1066
  }
857
- async publishOrderedQueueItem(ns, event, seq, payload, pullResponse = false) {
1067
+ async publishOrderedQueueItem(ns, event, seq, payload, pullResponse = false, source = 'push', batch) {
858
1068
  const client = this.runtime.client;
859
1069
  if (event === 'group.changed' && this.isGroupEventNamespace(ns)) {
860
- await this.publishOrderedGroupChanged(payload);
1070
+ await this.publishOrderedGroupChanged(payload, source);
861
1071
  return;
862
1072
  }
863
1073
  if (event === 'message.recalled') {
864
- await this.publishMessageRecallTombstone(seq, payload);
1074
+ await this.publishMessageRecallTombstone(seq, payload, source);
865
1075
  return;
866
1076
  }
867
1077
  if (pullResponse) {
868
- await client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload));
1078
+ await client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload, source, ns, batch));
869
1079
  return;
870
1080
  }
871
- await client._publishAppEvent(event, payload);
1081
+ await client._publishAppEvent(event, payload, source, ns, batch);
872
1082
  }
873
- async publishOrderedGroupChanged(payload) {
1083
+ async publishOrderedGroupChanged(payload, source = 'ordered') {
874
1084
  const client = this.runtime.client;
875
1085
  if (isJsonObject(payload)) {
876
1086
  const eventPayload = payload;
@@ -882,7 +1092,7 @@ export class MessageDeliveryEngine {
882
1092
  client._cleanupDissolvedGroup?.(groupId);
883
1093
  }
884
1094
  }
885
- await client._publishAppEvent('group.changed', payload);
1095
+ await client._publishAppEvent('group.changed', payload, source);
886
1096
  }
887
1097
  isInstanceScopedMessageEvent(event) {
888
1098
  return event === 'message.received'
@@ -905,18 +1115,27 @@ export class MessageDeliveryEngine {
905
1115
  }
906
1116
  return result;
907
1117
  }
908
- normalizePublishedMessagePayload(event, payload) {
1118
+ normalizePublishedMessagePayload(event, payload, source = 'direct') {
1119
+ let normalized;
909
1120
  if (this.isInstanceScopedMessageEvent(event)) {
910
1121
  payload = this.stripInlineInternalFields(payload);
911
1122
  if (event === 'group.message_created')
912
1123
  payload = normalizeGroupMentionMode(payload);
913
- return this.attachAppMessageEnvelope(this.stripInternalSenderDeviceFields(this.attachCurrentInstanceContext(payload)));
1124
+ normalized = this.attachAppMessageEnvelope(this.stripInternalSenderDeviceFields(this.attachCurrentInstanceContext(payload)));
914
1125
  }
915
- if (this.isGroupScopedEvent(event)) {
1126
+ else if (this.isGroupScopedEvent(event)) {
916
1127
  payload = this.stripInlineInternalFields(payload);
917
- return this.attachAppGroupEventEnvelope(this.attachCurrentInstanceContext(payload));
1128
+ normalized = this.attachAppGroupEventEnvelope(this.attachCurrentInstanceContext(payload));
918
1129
  }
919
- return payload;
1130
+ else {
1131
+ normalized = payload;
1132
+ }
1133
+ const canonicalSource = canonicalDeliverySource(source);
1134
+ if (canonicalSource === 'direct' || !isJsonObject(normalized))
1135
+ return normalized;
1136
+ if (!this.isInstanceScopedMessageEvent(event) && !this.isGroupScopedEvent(event))
1137
+ return normalized;
1138
+ return { ...normalized, source: canonicalSource };
920
1139
  }
921
1140
  stripInlineInternalFields(payload) {
922
1141
  if (!isJsonObject(payload))
@@ -1043,6 +1262,7 @@ export class MessageDeliveryEngine {
1043
1262
  setIfPresent('type', firstValue(body.type, params.type, params.message_type, params.payload_type));
1044
1263
  setIfPresent('kind', firstValue(body.kind, params.kind));
1045
1264
  setIfPresent('version', firstValue(body.version, params.version));
1265
+ setIfPresent('message_id', firstValue(params.message_id, body.message_id, resultObj.message_id));
1046
1266
  setIfPresent('timestamp', firstValue(params.timestamp, resultObj.timestamp, resultObj.created_at, resultObj.t_server, Date.now()));
1047
1267
  envelope.encrypted = Boolean(encrypted);
1048
1268
  const context = this.envelopeMetadata(params.context);
@@ -1184,7 +1404,7 @@ export class MessageDeliveryEngine {
1184
1404
  return `p2p|tombstone:${tombstoneId}`;
1185
1405
  return `p2p|unknown:${Date.now()}:${Math.random()}`;
1186
1406
  }
1187
- async publishMessageRecallTombstone(seq, message) {
1407
+ async publishMessageRecallTombstone(seq, message, source = 'push') {
1188
1408
  const client = this.runtime.client;
1189
1409
  const eventPayload = this.recallEventFromMessage(message);
1190
1410
  if (!eventPayload)
@@ -1205,7 +1425,7 @@ export class MessageDeliveryEngine {
1205
1425
  for (const [oldKey] of drop)
1206
1426
  seen.delete(oldKey);
1207
1427
  }
1208
- await client._publishAppEvent('message.recalled', eventPayload);
1428
+ await client._publishAppEvent('message.recalled', eventPayload, source);
1209
1429
  client._clientLog.debug(`message.recalled published: seq=${String(seq)} ids=${JSON.stringify(eventPayload.message_ids)}`);
1210
1430
  return true;
1211
1431
  }
@@ -1288,7 +1508,7 @@ export class MessageDeliveryEngine {
1288
1508
  return `${normalizedGroupId}|tombstone:${tombstoneId}`;
1289
1509
  return `${normalizedGroupId}|unknown:${Date.now()}:${Math.random()}`;
1290
1510
  }
1291
- async publishGroupRecallTombstone(groupId, seq, message) {
1511
+ async publishGroupRecallTombstone(groupId, seq, message, source = 'push') {
1292
1512
  const client = this.runtime.client;
1293
1513
  const eventPayload = this.recallEventFromGroupMessage(message);
1294
1514
  if (!eventPayload)
@@ -1311,7 +1531,7 @@ export class MessageDeliveryEngine {
1311
1531
  for (const [oldKey] of drop)
1312
1532
  seen.delete(oldKey);
1313
1533
  }
1314
- await client._publishAppEvent('group.message_recalled', eventPayload);
1534
+ await client._publishAppEvent('group.message_recalled', eventPayload, source);
1315
1535
  client._clientLog.debug(`group.message_recalled published: group=${groupId} seq=${String(seq)} ids=${JSON.stringify(eventPayload.message_ids)}`);
1316
1536
  return true;
1317
1537
  }
@@ -1385,8 +1605,9 @@ export class MessageDeliveryEngine {
1385
1605
  if (contig !== contigBefore)
1386
1606
  this.persistSeq(ns);
1387
1607
  }
1388
- async publishAppEvent(event, payload) {
1608
+ async publishAppEvent(event, payload, source = 'direct', ns = '', batch) {
1389
1609
  const client = this.runtime.client;
1610
+ const generation = this.deliveryGeneration;
1390
1611
  if ((event === 'message.received' || event === 'group.message_created') && isJsonObject(payload)) {
1391
1612
  client._maybeAppendEchoTraceReceive(payload);
1392
1613
  }
@@ -1406,7 +1627,32 @@ export class MessageDeliveryEngine {
1406
1627
  client._clientLog.debug(`agent_md etag inject skipped: ${String(exc)}`);
1407
1628
  }
1408
1629
  }
1409
- await client._dispatcher.publish(event, this.normalizePublishedMessagePayload(event, payload));
1630
+ const canonicalSource = canonicalDeliverySource(source);
1631
+ let normalized = this.normalizePublishedMessagePayload(event, payload, source);
1632
+ if (isJsonObject(normalized)
1633
+ && (this.isInstanceScopedMessageEvent(event) || this.isGroupScopedEvent(event))
1634
+ && canonicalSource !== 'direct') {
1635
+ normalized = { ...normalized, source: canonicalSource };
1636
+ }
1637
+ client._dispatcher.enqueue(event, normalized);
1638
+ if (generation !== this.deliveryGeneration)
1639
+ return;
1640
+ if (canonicalSource !== 'pull' && canonicalSource !== 'pending_retry'
1641
+ && canonicalSource !== 'push' && canonicalSource !== 'inline_push')
1642
+ return;
1643
+ if (canonicalSource === 'push' || canonicalSource === 'inline_push') {
1644
+ const localBatch = batch ?? this.createDeliveryChangeBatch(canonicalSource);
1645
+ this.recordDeliveryChange(localBatch.changes, event, payload, ns, generation);
1646
+ if (!batch)
1647
+ await this.flushRealtimeDeliveryChanges(localBatch);
1648
+ }
1649
+ else {
1650
+ const changes = this.pendingPullDeliveryChanges ?? new Map();
1651
+ this.pendingPullDeliveryChanges = changes;
1652
+ this.recordDeliveryChange(changes, event, payload, ns, generation);
1653
+ if (client._rpcPipeline?.hasAnyPullActivity?.() !== true)
1654
+ await this.flushPullDeliveryChanges();
1655
+ }
1410
1656
  }
1411
1657
  messageTargetsCurrentInstance(message) {
1412
1658
  if (!isJsonObject(message))
@@ -1562,7 +1808,7 @@ export class MessageDeliveryEngine {
1562
1808
  const client = this.runtime.client;
1563
1809
  try {
1564
1810
  if (!isJsonObject(data)) {
1565
- await client._publishAppEvent('message.received', data);
1811
+ await client._publishAppEvent('message.received', data, 'push');
1566
1812
  return;
1567
1813
  }
1568
1814
  const msg = { ...data };
@@ -1614,7 +1860,7 @@ export class MessageDeliveryEngine {
1614
1860
  await client._publishEncryptedPushMessage('message.received', 'message.undecryptable', '', seq ?? 0, msg, false);
1615
1861
  return;
1616
1862
  }
1617
- await client._publishAppEvent('message.received', msg);
1863
+ await client._publishAppEvent('message.received', msg, 'push');
1618
1864
  }
1619
1865
  }
1620
1866
  catch (exc) {
@@ -1630,7 +1876,7 @@ export class MessageDeliveryEngine {
1630
1876
  _decrypt_error: String(exc),
1631
1877
  };
1632
1878
  client._attachV2EnvelopeMetadataFromSource(safeEvent, data);
1633
- await client._publishAppEvent('message.undecryptable', safeEvent);
1879
+ await client._publishAppEvent('message.undecryptable', safeEvent, 'push');
1634
1880
  }
1635
1881
  }
1636
1882
  }
@@ -1658,7 +1904,7 @@ export class MessageDeliveryEngine {
1658
1904
  const client = this.runtime.client;
1659
1905
  try {
1660
1906
  if (!isJsonObject(data)) {
1661
- await client._publishAppEvent('group.message_created', data);
1907
+ await client._publishAppEvent('group.message_created', data, 'push');
1662
1908
  return;
1663
1909
  }
1664
1910
  const msg = { ...data };
@@ -1737,7 +1983,7 @@ export class MessageDeliveryEngine {
1737
1983
  await client._publishEncryptedPushMessage('group.message_created', 'group.message_undecryptable', '', seq ?? 0, msg, true);
1738
1984
  return;
1739
1985
  }
1740
- await client._publishAppEvent('group.message_created', msg);
1986
+ await client._publishAppEvent('group.message_created', msg, 'push');
1741
1987
  }
1742
1988
  }
1743
1989
  catch (exc) {
@@ -1753,7 +1999,7 @@ export class MessageDeliveryEngine {
1753
1999
  _decrypt_error: String(exc),
1754
2000
  };
1755
2001
  client._attachV2EnvelopeMetadataFromSource(safeEvent, data);
1756
- await client._publishAppEvent('group.message_undecryptable', safeEvent);
2002
+ await client._publishAppEvent('group.message_undecryptable', safeEvent, 'push');
1757
2003
  }
1758
2004
  }
1759
2005
  }
@@ -1761,7 +2007,7 @@ export class MessageDeliveryEngine {
1761
2007
  const client = this.runtime.client;
1762
2008
  const groupId = (notification.group_id ?? '');
1763
2009
  if (!groupId) {
1764
- await client._publishAppEvent('group.message_created', notification);
2010
+ await client._publishAppEvent('group.message_created', notification, 'push');
1765
2011
  return;
1766
2012
  }
1767
2013
  if (client._sessionOptions?.background_sync === false) {
@@ -1778,7 +2024,7 @@ export class MessageDeliveryEngine {
1778
2024
  }
1779
2025
  catch (exc) {
1780
2026
  client._clientLog.warn(`auto pull group message failed:${String(exc)}`);
1781
- await client._publishAppEvent('group.message_created', notification);
2027
+ await client._publishAppEvent('group.message_created', notification, 'push');
1782
2028
  return;
1783
2029
  }
1784
2030
  }
@@ -1808,7 +2054,7 @@ export class MessageDeliveryEngine {
1808
2054
  // 两种情况都不能再把原始通知当作第二条业务消息发布。
1809
2055
  client._clientLog.warn(`auto pull group message commit/ack failed:${String(exc)}`);
1810
2056
  if (!rawCompleted) {
1811
- await client._publishAppEvent('group.message_created', notification);
2057
+ await client._publishAppEvent('group.message_created', notification, 'push');
1812
2058
  }
1813
2059
  }
1814
2060
  }
@@ -1821,12 +2067,15 @@ export class MessageDeliveryEngine {
1821
2067
  return;
1822
2068
  const delayMs = Math.min(P2P_GAP_FILL_RETRY_BASE_MS * retryAttempt, P2P_GAP_FILL_RETRY_MAX_MS);
1823
2069
  client._gapFillDone.add(retryKey);
2070
+ const releasePullWork = client._rpcPipeline?.reservePullWork?.() ?? (() => { });
1824
2071
  client._clientLog.debug(`P2P message gap-fill retry scheduled: ns=${ns} attempt=${retryAttempt} delay_ms=${delayMs}`);
1825
2072
  globalThis.setTimeout(() => {
1826
2073
  client._gapFillDone.delete(retryKey);
1827
- if (client.state !== ConnectionState.READY || client._closing)
2074
+ if (client.state !== ConnectionState.READY || client._closing) {
2075
+ releasePullWork();
1828
2076
  return;
1829
- client._safeAsync(this.fillP2pGap(retryAttempt));
2077
+ }
2078
+ client._safeAsync(this.fillP2pGap(retryAttempt).finally(releasePullWork));
1830
2079
  }, delayMs);
1831
2080
  }
1832
2081
  async fillP2pGap(retryAttempt = 0) {
@@ -1943,6 +2192,7 @@ export class MessageDeliveryEngine {
1943
2192
  client._gapFillDone.add(dedupKey);
1944
2193
  this.runtime.delivery.setGapFillActive(true);
1945
2194
  let continuationAfterSeq = 0;
2195
+ let failed = false;
1946
2196
  try {
1947
2197
  let nextAfterSeq = afterSeq;
1948
2198
  const maxPages = singlePage ? 1 : 100;
@@ -1982,6 +2232,7 @@ export class MessageDeliveryEngine {
1982
2232
  }
1983
2233
  const eventSeqs = [];
1984
2234
  let hasDissolvedEvent = false;
2235
+ let publishedEventCount = 0;
1985
2236
  for (const evt of eventObjects) {
1986
2237
  const eventSeq = Number(evt.event_seq ?? 0);
1987
2238
  if (Number.isFinite(eventSeq) && eventSeq > 0)
@@ -2003,7 +2254,8 @@ export class MessageDeliveryEngine {
2003
2254
  }
2004
2255
  }
2005
2256
  if (Number.isFinite(eventSeq) && eventSeq > 0 && !client._pushedSeqs.get(ns)?.has(eventSeq)) {
2006
- this.enqueueOrderedMessage(ns, 'group.changed', eventSeq, evt);
2257
+ this.enqueueOrderedMessage(ns, 'group.changed', eventSeq, evt, 'pull');
2258
+ publishedEventCount += 1;
2007
2259
  }
2008
2260
  }
2009
2261
  const ackContig = client._seqTracker.getContiguousSeq(ns);
@@ -2029,6 +2281,7 @@ export class MessageDeliveryEngine {
2029
2281
  client._clientLog.warn('group event auto-ack failed: group=' + groupId, e);
2030
2282
  }
2031
2283
  }
2284
+ this.onPullPage(ns, publishedEventCount, result.has_more === true, eventObjects.length > 0 ? Number(eventObjects[eventObjects.length - 1].event_seq ?? 0) : undefined, typeof result.remaining === 'number' && Number.isSafeInteger(result.remaining) && result.remaining >= 0 ? result.remaining : null, eventObjects.length, ackContig, eventObjects.length > 0 ? Number(eventObjects[eventObjects.length - 1].event_seq ?? 0) : undefined);
2032
2285
  const nextAfter = Math.max(eventSeqs.length > 0 ? Math.max(...eventSeqs) : nextAfterSeq, nextAfterSeq);
2033
2286
  if (singlePage && result.has_more === true && nextAfter > nextAfterSeq) {
2034
2287
  continuationAfterSeq = nextAfter;
@@ -2042,11 +2295,14 @@ export class MessageDeliveryEngine {
2042
2295
  }
2043
2296
  }
2044
2297
  catch (exc) {
2298
+ failed = true;
2045
2299
  client._clientLog.warn(`group event gap-fill failed:${String(exc)}`);
2046
2300
  }
2047
2301
  finally {
2048
2302
  client._gapFillDone.delete(dedupKey);
2049
2303
  this.runtime.delivery.setGapFillActive(false);
2304
+ if (!client._rpcPipeline)
2305
+ this.syncStopped(failed ? 'aborted' : 'pull_drained');
2050
2306
  if (singlePage && continuationAfterSeq > afterSeq) {
2051
2307
  this.enqueueOnlineUnreadEventHint({
2052
2308
  group_id: groupId,
@@ -2527,7 +2783,7 @@ export class MessageDeliveryEngine {
2527
2783
  ? tracker.snapshotNamespace(ns)
2528
2784
  : null;
2529
2785
  try {
2530
- const published = await this.publishPulledMessage(event, ns, seq, payload);
2786
+ const published = await this.publishPulledMessage(event, ns, seq, payload, true, 'inline_push');
2531
2787
  if (!this.isInlineGenerationCurrent(generation))
2532
2788
  return false;
2533
2789
  if (!published)
@@ -3107,13 +3363,13 @@ export class MessageDeliveryEngine {
3107
3363
  }
3108
3364
  }
3109
3365
  catch (exc) {
3110
- client._dispatcher.publish('seq_tracker.persist_error', {
3366
+ client._dispatcher.enqueue('seq_tracker.persist_error', {
3111
3367
  phase: 'restore',
3112
3368
  aid,
3113
3369
  device_id: deviceId,
3114
3370
  slot_id: slotId,
3115
3371
  error: String(exc),
3116
- }).catch(() => { });
3372
+ });
3117
3373
  }
3118
3374
  }
3119
3375
  async migrateSeqStateGroupIds(state) {
@@ -3321,13 +3577,13 @@ export class MessageDeliveryEngine {
3321
3577
  catch (exc) {
3322
3578
  const error = formatDeliveryError(exc);
3323
3579
  client._clientLog.warn(`save SeqTracker state failed: ${error}`);
3324
- client._dispatcher.publish('seq_tracker.persist_error', {
3580
+ client._dispatcher.enqueue('seq_tracker.persist_error', {
3325
3581
  phase: 'save',
3326
3582
  aid,
3327
3583
  device_id: deviceId,
3328
3584
  slot_id: slotId,
3329
3585
  error: String(error),
3330
- }).catch(() => { });
3586
+ });
3331
3587
  if (throwOnError)
3332
3588
  throw exc;
3333
3589
  }
@@ -3446,7 +3702,7 @@ export class MessageDeliveryEngine {
3446
3702
  }
3447
3703
  return params;
3448
3704
  }
3449
- async drainOrderedMessages(ns, beforeSeq, pullResponse = false, persist = true) {
3705
+ async drainOrderedMessages(ns, beforeSeq, pullResponse = false, persist = true, batch, source = 'pull') {
3450
3706
  const client = this.runtime.client;
3451
3707
  this.ensurePullOperationCurrent();
3452
3708
  const queue = client._pendingOrderedMsgs.get(ns);
@@ -3457,16 +3713,34 @@ export class MessageDeliveryEngine {
3457
3713
  .filter((seq) => seq <= contig && (beforeSeq === undefined || seq < beforeSeq))
3458
3714
  .sort((a, b) => a - b);
3459
3715
  let delivered = false;
3460
- for (const seq of ready) {
3461
- this.ensurePullOperationCurrent();
3462
- const item = queue.get(seq);
3463
- queue.delete(seq);
3464
- if (!item || client._pushedSeqs.get(ns)?.has(seq))
3465
- continue;
3466
- await this.publishOrderedQueueItem(ns, item.event, seq, item.payload, pullResponse);
3467
- this.ensurePullOperationCurrent();
3468
- this.markPublishedSeq(ns, seq);
3469
- delivered = true;
3716
+ const drainBatches = new Map();
3717
+ try {
3718
+ for (const seq of ready) {
3719
+ this.ensurePullOperationCurrent();
3720
+ const item = queue.get(seq);
3721
+ queue.delete(seq);
3722
+ if (!item || client._pushedSeqs.get(ns)?.has(seq))
3723
+ continue;
3724
+ const itemSource = item.source ?? source;
3725
+ let itemBatch = batch;
3726
+ if ((itemSource === 'push' || itemSource === 'inline_push')
3727
+ && (!batch || batch.trigger !== itemSource)) {
3728
+ itemBatch = drainBatches.get(itemSource);
3729
+ if (!itemBatch) {
3730
+ itemBatch = this.createDeliveryChangeBatch(itemSource);
3731
+ drainBatches.set(itemSource, itemBatch);
3732
+ }
3733
+ }
3734
+ await this.publishOrderedQueueItem(ns, item.event, seq, item.payload, pullResponse, itemSource, itemBatch);
3735
+ this.ensurePullOperationCurrent();
3736
+ this.markPublishedSeq(ns, seq);
3737
+ delivered = true;
3738
+ }
3739
+ }
3740
+ finally {
3741
+ for (const drainBatch of drainBatches.values()) {
3742
+ await this.flushRealtimeDeliveryChanges(drainBatch);
3743
+ }
3470
3744
  }
3471
3745
  if (queue.size === 0) {
3472
3746
  client._pendingOrderedMsgs.delete(ns);
@@ -3476,75 +3750,91 @@ export class MessageDeliveryEngine {
3476
3750
  }
3477
3751
  }
3478
3752
  }
3479
- async publishOrderedMessage(event, ns, seq, payload) {
3753
+ async publishOrderedMessage(event, ns, seq, payload, source = 'push', operationBatch) {
3480
3754
  const client = this.runtime.client;
3481
- const seqNum = Number(seq);
3482
- if (!Number.isFinite(seqNum) || !Number.isInteger(seqNum) || seqNum <= 0) {
3483
- await this.publishOrderedQueueItem(ns, event, seqNum, payload);
3484
- return true;
3485
- }
3486
- if (client._pushedSeqs.get(ns)?.has(seqNum)) {
3755
+ const ownsBatch = !operationBatch && (source === 'push' || source === 'inline_push');
3756
+ const batch = operationBatch ?? (ownsBatch ? this.createDeliveryChangeBatch(source) : undefined);
3757
+ try {
3758
+ const seqNum = Number(seq);
3759
+ if (!Number.isFinite(seqNum) || !Number.isInteger(seqNum) || seqNum <= 0) {
3760
+ await this.publishOrderedQueueItem(ns, event, seqNum, payload, false, source, batch);
3761
+ return true;
3762
+ }
3763
+ if (client._pushedSeqs.get(ns)?.has(seqNum)) {
3764
+ const queue = client._pendingOrderedMsgs.get(ns);
3765
+ queue?.delete(seqNum);
3766
+ if (queue && queue.size === 0)
3767
+ client._pendingOrderedMsgs.delete(ns);
3768
+ return false;
3769
+ }
3770
+ const contig = client._seqTracker.getContiguousSeq(ns);
3771
+ if (seqNum > contig) {
3772
+ this.enqueueOrderedMessage(ns, event, seqNum, payload, source);
3773
+ return false;
3774
+ }
3775
+ await this.drainOrderedMessages(ns, seqNum, true, true, batch, source);
3776
+ if (client._pushedSeqs.get(ns)?.has(seqNum))
3777
+ return false;
3487
3778
  const queue = client._pendingOrderedMsgs.get(ns);
3488
3779
  queue?.delete(seqNum);
3489
3780
  if (queue && queue.size === 0)
3490
3781
  client._pendingOrderedMsgs.delete(ns);
3491
- return false;
3782
+ await this.publishOrderedQueueItem(ns, event, seqNum, payload, false, source, batch);
3783
+ this.markPublishedSeq(ns, seqNum);
3784
+ await this.drainOrderedMessages(ns, undefined, false, true, batch, source);
3785
+ if (!client._pendingOrderedMsgs.get(ns))
3786
+ await this.saveSeqTrackerState();
3787
+ return true;
3492
3788
  }
3493
- const contig = client._seqTracker.getContiguousSeq(ns);
3494
- if (seqNum > contig) {
3495
- this.enqueueOrderedMessage(ns, event, seqNum, payload);
3496
- return false;
3789
+ finally {
3790
+ if (ownsBatch && batch)
3791
+ await this.flushRealtimeDeliveryChanges(batch);
3497
3792
  }
3498
- await this.drainOrderedMessages(ns, seqNum, true);
3499
- if (client._pushedSeqs.get(ns)?.has(seqNum))
3500
- return false;
3501
- const queue = client._pendingOrderedMsgs.get(ns);
3502
- queue?.delete(seqNum);
3503
- if (queue && queue.size === 0)
3504
- client._pendingOrderedMsgs.delete(ns);
3505
- await this.publishOrderedQueueItem(ns, event, seqNum, payload);
3506
- this.markPublishedSeq(ns, seqNum);
3507
- await this.drainOrderedMessages(ns);
3508
- if (!client._pendingOrderedMsgs.get(ns))
3509
- await this.saveSeqTrackerState();
3510
- return true;
3511
3793
  }
3512
- async publishPulledMessage(event, ns, seq, payload, persist = true) {
3794
+ async publishPulledMessage(event, ns, seq, payload, persist = true, source = 'pull', operationBatch) {
3513
3795
  const client = this.runtime.client;
3514
- this.ensurePullOperationCurrent();
3515
- const seqNum = Number(seq);
3516
- if (!Number.isFinite(seqNum) || !Number.isInteger(seqNum) || seqNum <= 0 || !ns) {
3517
- if (event === 'message.recalled') {
3518
- const published = await client._withPullResponseProcessing(ns, () => this.publishMessageRecallTombstone(seq, payload));
3796
+ const ownsBatch = !operationBatch && (source === 'push' || source === 'inline_push');
3797
+ const batch = operationBatch ?? (ownsBatch ? this.createDeliveryChangeBatch(source) : undefined);
3798
+ try {
3799
+ this.ensurePullOperationCurrent();
3800
+ const seqNum = Number(seq);
3801
+ if (!Number.isFinite(seqNum) || !Number.isInteger(seqNum) || seqNum <= 0 || !ns) {
3802
+ if (event === 'message.recalled') {
3803
+ const published = await client._withPullResponseProcessing(ns, () => this.publishMessageRecallTombstone(seq, payload, source));
3804
+ this.ensurePullOperationCurrent();
3805
+ return published;
3806
+ }
3807
+ await client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload, source, ns, batch));
3519
3808
  this.ensurePullOperationCurrent();
3520
- return published;
3809
+ return true;
3810
+ }
3811
+ const queue = client._pendingOrderedMsgs.get(ns);
3812
+ if (client._pushedSeqs.get(ns)?.has(seqNum)) {
3813
+ queue?.delete(seqNum);
3814
+ if (queue && queue.size === 0)
3815
+ client._pendingOrderedMsgs.delete(ns);
3816
+ return false;
3521
3817
  }
3522
- await client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload));
3818
+ await this.drainOrderedMessages(ns, seqNum, false, persist, batch, source);
3523
3819
  this.ensurePullOperationCurrent();
3524
- return true;
3525
- }
3526
- const queue = client._pendingOrderedMsgs.get(ns);
3527
- if (client._pushedSeqs.get(ns)?.has(seqNum)) {
3528
3820
  queue?.delete(seqNum);
3529
3821
  if (queue && queue.size === 0)
3530
3822
  client._pendingOrderedMsgs.delete(ns);
3531
- return false;
3532
- }
3533
- await this.drainOrderedMessages(ns, seqNum, false, persist);
3534
- this.ensurePullOperationCurrent();
3535
- queue?.delete(seqNum);
3536
- if (queue && queue.size === 0)
3537
- client._pendingOrderedMsgs.delete(ns);
3538
- if (event === 'message.recalled') {
3539
- const published = await client._withPullResponseProcessing(ns, () => this.publishMessageRecallTombstone(seqNum, payload));
3823
+ if (event === 'message.recalled') {
3824
+ const published = await client._withPullResponseProcessing(ns, () => this.publishMessageRecallTombstone(seqNum, payload, source));
3825
+ this.ensurePullOperationCurrent();
3826
+ this.markPublishedSeq(ns, seqNum);
3827
+ return published;
3828
+ }
3829
+ await client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload, source, ns, batch));
3540
3830
  this.ensurePullOperationCurrent();
3541
3831
  this.markPublishedSeq(ns, seqNum);
3542
- return published;
3832
+ return true;
3833
+ }
3834
+ finally {
3835
+ if (ownsBatch && batch)
3836
+ await this.flushRealtimeDeliveryChanges(batch);
3543
3837
  }
3544
- await client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload));
3545
- this.ensurePullOperationCurrent();
3546
- this.markPublishedSeq(ns, seqNum);
3547
- return true;
3548
3838
  }
3549
3839
  }
3550
3840
  //# sourceMappingURL=delivery.js.map