@agentunion/fastaun 0.5.10 → 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.
@@ -80,6 +80,8 @@ export class MessageDeliveryEngine {
80
80
  inlineAckGeneration = 0;
81
81
  pendingDeliveryChanges = null;
82
82
  deliveryChangedGeneration = 0;
83
+ syncRun = 0;
84
+ syncSessions = null;
83
85
  // 同一实例的 tracker 写入必须串行,避免旧的延迟 flush 覆盖新的 Forward 提交。
84
86
  seqTrackerWriteChain = Promise.resolve();
85
87
  constructor(runtime) {
@@ -152,6 +154,7 @@ export class MessageDeliveryEngine {
152
154
  }
153
155
  const client = this.runtime.client;
154
156
  this.ensurePullOperationCurrent();
157
+ this.syncStarted(ns);
155
158
  const response = result;
156
159
  const messages = deduplicatePlainForwardPageMessages(response.messages, afterSeq);
157
160
  const coordinator = this.forwardCoordinator();
@@ -194,7 +197,7 @@ export class MessageDeliveryEngine {
194
197
  }
195
198
  const message = normalizeGroupMentionMode(rawMessage);
196
199
  if (this.recallEventFromGroupMessage(message)) {
197
- if (await this.publishGroupRecallTombstone(groupId, seq, message)) {
200
+ if (await this.publishGroupRecallTombstone(groupId, seq, message, 'pull')) {
198
201
  this.ensurePullOperationCurrent();
199
202
  this.markPublishedSeq(ns, seq);
200
203
  publishedCount += 1;
@@ -261,9 +264,17 @@ export class MessageDeliveryEngine {
261
264
  this.ensurePullOperationCurrent();
262
265
  await this.confirmPlainForwardAck(ns, ackMethod, pendingAckSeq, groupId);
263
266
  }
267
+ this.syncProgress(ns, {
268
+ rawCount: messages.length,
269
+ pulledCount: publishedCount,
270
+ remaining: response.remaining,
271
+ hasMore: typeof response.has_more === 'boolean' ? response.has_more : undefined,
272
+ currentSeq: client._seqTracker.getContiguousSeq(ns),
273
+ });
264
274
  return { rawCount: messages.length, publishedCount };
265
275
  }
266
276
  resetInlineAckState() {
277
+ this.syncStopped('aborted');
267
278
  this.deliveryChangedGeneration += 1;
268
279
  this.pendingDeliveryChanges = null;
269
280
  this.inlineAckGeneration += 1;
@@ -276,6 +287,7 @@ export class MessageDeliveryEngine {
276
287
  this.onlineUnreadHintTargets = null;
277
288
  this.onlineUnreadHintTasks = null;
278
289
  this.realtimeTailHeads = null;
290
+ this.syncSessions = null;
279
291
  }
280
292
  deliveryChangedNamespace(event, payload) {
281
293
  if (event === 'group.message_created' && isJsonObject(payload)) {
@@ -301,9 +313,9 @@ export class MessageDeliveryEngine {
301
313
  || (typeof seq === 'number' && Number.isSafeInteger(seq) && seq > 0);
302
314
  }
303
315
  deliveryChangedTrigger(source) {
304
- if (source === 'inline-push')
316
+ if (source === 'inline-push' || source === 'inline_push')
305
317
  return 'inline_push';
306
- if (source === 'pull' || source === 'tail')
318
+ if (source === 'pull' || source === 'tail' || source === 'pending-retry' || source === 'pending_retry')
307
319
  return 'pull_drained';
308
320
  if (source === 'push' || source === 'group-push' || source === 'ordered')
309
321
  return 'push';
@@ -336,7 +348,7 @@ export class MessageDeliveryEngine {
336
348
  pending.clear();
337
349
  if (changes.length === 0)
338
350
  return;
339
- const payload = { trigger, changes };
351
+ const payload = { source: trigger === 'pull_drained' ? 'pull' : trigger, trigger, changes };
340
352
  const dispatcher = this.runtime.client._dispatcher;
341
353
  const published = typeof dispatcher.enqueue === 'function'
342
354
  ? (dispatcher.enqueue('delivery.changed', payload), undefined)
@@ -348,8 +360,119 @@ export class MessageDeliveryEngine {
348
360
  this.runtime.client._clientLog.warn(`delivery.changed handler failed: ${formatDeliveryError(exc)}`);
349
361
  });
350
362
  }
363
+ publishSyncLifecycle(event, payload) {
364
+ const dispatcher = this.runtime.client._dispatcher;
365
+ const published = typeof dispatcher.enqueue === 'function'
366
+ ? (dispatcher.enqueue(event, payload), undefined)
367
+ : typeof dispatcher.publishSyncAware === 'function'
368
+ ? dispatcher.publishSyncAware(event, payload)
369
+ : dispatcher.publish?.(event, payload);
370
+ if (isPromiseLike(published))
371
+ void Promise.resolve(published).catch((exc) => {
372
+ this.runtime.client._clientLog.warn(`${event} handler failed: ${formatDeliveryError(exc)}`);
373
+ });
374
+ }
375
+ syncStarted(ns) {
376
+ const namespace = String(ns ?? '').trim();
377
+ if (!namespace)
378
+ return;
379
+ const sessions = this.syncSessions ?? new Map();
380
+ this.syncSessions = sessions;
381
+ if (sessions.has(namespace))
382
+ return;
383
+ if (sessions.size === 0) {
384
+ this.syncRun += 1;
385
+ this.publishSyncLifecycle('sync.started', {
386
+ run_id: this.syncRun,
387
+ source: 'pull',
388
+ syncing: true,
389
+ namespaces_pending: 1,
390
+ received_total: 0,
391
+ namespace,
392
+ started_at: Date.now(),
393
+ });
394
+ }
395
+ const session = { pages: 0, pulled: 0, startedAt: Date.now() };
396
+ sessions.set(namespace, session);
397
+ }
398
+ syncProgress(ns, progress = {}) {
399
+ const namespace = String(ns ?? '').trim();
400
+ if (!namespace)
401
+ return;
402
+ this.syncStarted(namespace);
403
+ const session = this.syncSessions?.get(namespace);
404
+ if (!session)
405
+ return;
406
+ const pulledCount = Math.max(0, Number(progress.pulledCount ?? progress.rawCount ?? 0) || 0);
407
+ const rawCount = Math.max(0, Number(progress.rawCount ?? pulledCount) || 0);
408
+ session.pages += 1;
409
+ session.pulled += pulledCount;
410
+ const payload = {
411
+ run_id: this.syncRun,
412
+ source: 'pull',
413
+ namespace,
414
+ page: session.pages,
415
+ page_pulled_count: pulledCount,
416
+ page_raw_count: rawCount,
417
+ pulled_count: session.pulled,
418
+ received_total: session.pulled,
419
+ batch_size: pulledCount,
420
+ };
421
+ if (typeof progress.remaining === 'number'
422
+ && Number.isSafeInteger(progress.remaining) && progress.remaining >= 0) {
423
+ payload.remaining = progress.remaining;
424
+ payload.estimated_remaining = payload.remaining;
425
+ }
426
+ if (progress.hasMore !== undefined) {
427
+ payload.has_more = Boolean(progress.hasMore);
428
+ if (progress.remaining === undefined || progress.remaining === null) {
429
+ payload.estimated_remaining = progress.hasMore ? undefined : 0;
430
+ }
431
+ }
432
+ if (progress.currentSeq !== undefined && Number.isFinite(Number(progress.currentSeq))) {
433
+ payload.current_seq = Math.max(0, Number(progress.currentSeq));
434
+ }
435
+ if (progress.targetSeq !== undefined && Number.isFinite(Number(progress.targetSeq))) {
436
+ payload.target_seq = Math.max(0, Number(progress.targetSeq));
437
+ }
438
+ this.publishSyncLifecycle('sync.progress', payload);
439
+ }
351
440
  onPullDrained() {
352
441
  this.flushDeliveryChanged('pull_drained');
442
+ this.syncStopped('pull_drained');
443
+ }
444
+ onPullAborted() {
445
+ this.syncStopped('aborted');
446
+ }
447
+ syncStopped(reason = 'completed') {
448
+ const sessions = this.syncSessions;
449
+ if (!sessions || sessions.size === 0)
450
+ return;
451
+ const entries = [...sessions.entries()];
452
+ const receivedTotal = entries.reduce((total, [, session]) => total + session.pulled, 0);
453
+ const payload = {
454
+ run_id: this.syncRun,
455
+ source: 'pull',
456
+ syncing: false,
457
+ namespaces_pending: 0,
458
+ received_total: receivedTotal,
459
+ reason,
460
+ stopped_at: Date.now(),
461
+ };
462
+ if (entries.length === 1) {
463
+ const [namespace, session] = entries[0];
464
+ payload.namespace = namespace;
465
+ payload.pages = session.pages;
466
+ payload.pulled_count = session.pulled;
467
+ }
468
+ else {
469
+ payload.namespaces = entries.map(([namespace]) => namespace);
470
+ payload.pages = entries.reduce((total, [, session]) => total + session.pages, 0);
471
+ payload.pulled_count = receivedTotal;
472
+ }
473
+ sessions.clear();
474
+ this.syncSessions = null;
475
+ this.publishSyncLifecycle('sync.stopped', payload);
353
476
  }
354
477
  flushDeliveryChangedContext(context, trigger) {
355
478
  if (context.generation !== this.deliveryChangedGeneration || context.changes.size === 0)
@@ -369,7 +492,7 @@ export class MessageDeliveryEngine {
369
492
  if (changes.length === 0)
370
493
  return;
371
494
  const dispatcher = this.runtime.client._dispatcher;
372
- const payload = { trigger, changes };
495
+ const payload = { trigger, source: trigger, changes };
373
496
  const published = typeof dispatcher.enqueue === 'function'
374
497
  ? (dispatcher.enqueue('delivery.changed', payload), undefined)
375
498
  : typeof dispatcher.publishSyncAware === 'function'
@@ -527,7 +650,7 @@ export class MessageDeliveryEngine {
527
650
  return;
528
651
  }
529
652
  if (event === 'message.recalled') {
530
- await this.publishMessageRecallTombstone(seq, payload);
653
+ await this.publishMessageRecallTombstone(seq, payload, source);
531
654
  this.ensurePullOperationCurrent();
532
655
  return;
533
656
  }
@@ -580,17 +703,27 @@ export class MessageDeliveryEngine {
580
703
  }
581
704
  return result;
582
705
  }
583
- normalizePublishedMessagePayload(event, payload) {
706
+ normalizePublishedMessagePayload(event, payload, source = 'direct') {
584
707
  payload = this.stripInlineInternalFields(payload);
708
+ let normalized;
585
709
  if (this.isInstanceScopedMessageEvent(event)) {
586
710
  if (event === 'group.message_created')
587
711
  payload = normalizeGroupMentionMode(payload);
588
- return this.attachAppMessageEnvelope(this.stripInternalSenderDeviceFields(this.attachCurrentInstanceContext(payload)));
712
+ normalized = this.attachAppMessageEnvelope(this.stripInternalSenderDeviceFields(this.attachCurrentInstanceContext(payload)));
713
+ }
714
+ else if (this.isGroupScopedEvent(event)) {
715
+ normalized = this.attachAppGroupEventEnvelope(this.attachCurrentInstanceContext(payload));
589
716
  }
590
- if (this.isGroupScopedEvent(event)) {
591
- return this.attachAppGroupEventEnvelope(this.attachCurrentInstanceContext(payload));
717
+ else {
718
+ normalized = payload;
592
719
  }
593
- return payload;
720
+ if (source === 'direct' || !isJsonObject(normalized))
721
+ return normalized;
722
+ const canonicalSource = source === 'group-push' || source === 'ordered' || source === 'legacy' || source === 'tail'
723
+ ? (source === 'tail' ? 'pull' : 'push')
724
+ : source === 'inline-push' || source === 'inline_push' ? 'inline_push'
725
+ : source === 'pending-retry' || source === 'pending_retry' ? 'pending_retry' : source;
726
+ return { ...normalized, source: canonicalSource };
594
727
  }
595
728
  stripInlineInternalFields(payload) {
596
729
  if (!isJsonObject(payload))
@@ -860,7 +993,7 @@ export class MessageDeliveryEngine {
860
993
  return `p2p|tombstone:${tombstoneId}`;
861
994
  return `p2p|unknown:${Date.now()}:${Math.random()}`;
862
995
  }
863
- async publishMessageRecallTombstone(seq, message) {
996
+ async publishMessageRecallTombstone(seq, message, source = 'push') {
864
997
  const client = this.runtime.client;
865
998
  const eventPayload = this.recallEventFromMessage(message);
866
999
  if (!eventPayload)
@@ -881,7 +1014,7 @@ export class MessageDeliveryEngine {
881
1014
  for (const [oldKey] of drop)
882
1015
  seen.delete(oldKey);
883
1016
  }
884
- await client._publishAppEvent('message.recalled', eventPayload, 'message-recall');
1017
+ await client._publishAppEvent('message.recalled', eventPayload, source);
885
1018
  client._clientLog.debug(`message.recalled published: seq=${String(seq)} ids=${JSON.stringify(eventPayload.message_ids)}`);
886
1019
  return true;
887
1020
  }
@@ -964,7 +1097,7 @@ export class MessageDeliveryEngine {
964
1097
  return `${normalizedGroupId}|tombstone:${tombstoneId}`;
965
1098
  return `${normalizedGroupId}|unknown:${Date.now()}:${Math.random()}`;
966
1099
  }
967
- async publishGroupRecallTombstone(groupId, seq, message) {
1100
+ async publishGroupRecallTombstone(groupId, seq, message, source = 'push') {
968
1101
  const client = this.runtime.client;
969
1102
  const eventPayload = this.recallEventFromGroupMessage(message);
970
1103
  if (!eventPayload)
@@ -987,7 +1120,7 @@ export class MessageDeliveryEngine {
987
1120
  for (const [oldKey] of drop)
988
1121
  seen.delete(oldKey);
989
1122
  }
990
- await client._publishAppEvent('group.message_recalled', eventPayload, 'group-recall');
1123
+ await client._publishAppEvent('group.message_recalled', eventPayload, source);
991
1124
  client._clientLog.debug(`group.message_recalled published: group=${groupId} seq=${String(seq)} ids=${JSON.stringify(eventPayload.message_ids)}`);
992
1125
  return true;
993
1126
  }
@@ -1078,7 +1211,7 @@ export class MessageDeliveryEngine {
1078
1211
  }
1079
1212
  }
1080
1213
  const generation = this.deliveryChangedGeneration;
1081
- const normalized = this.normalizePublishedMessagePayload(event, payload);
1214
+ const normalized = this.normalizePublishedMessagePayload(event, payload, source);
1082
1215
  const dispatcher = client._dispatcher;
1083
1216
  const result = typeof dispatcher.enqueue === 'function'
1084
1217
  ? (dispatcher.enqueue(event, normalized), undefined)
@@ -1279,7 +1412,7 @@ export class MessageDeliveryEngine {
1279
1412
  _decrypt_error: String(exc),
1280
1413
  };
1281
1414
  client._attachV2EnvelopeMetadataFromSource(safeEvent, data);
1282
- Promise.resolve(client._publishAppEvent('message.undecryptable', safeEvent)).catch(() => { });
1415
+ Promise.resolve(client._publishAppEvent('message.undecryptable', safeEvent, 'push')).catch(() => { });
1283
1416
  }
1284
1417
  });
1285
1418
  client._clientLog.debug(`_onRawMessageReceived exit: elapsed=${Date.now() - tStart}ms (handler dispatched)`);
@@ -1411,7 +1544,7 @@ export class MessageDeliveryEngine {
1411
1544
  _decrypt_error: String(exc),
1412
1545
  };
1413
1546
  client._attachV2EnvelopeMetadataFromSource(safeEvent, data);
1414
- Promise.resolve(client._publishAppEvent('group.message_undecryptable', safeEvent)).catch(() => { });
1547
+ Promise.resolve(client._publishAppEvent('group.message_undecryptable', safeEvent, 'push')).catch(() => { });
1415
1548
  }
1416
1549
  });
1417
1550
  client._clientLog.debug(`_onRawGroupMessageCreated exit: elapsed=${Date.now() - tStart}ms (handler dispatched)`);
@@ -1507,7 +1640,7 @@ export class MessageDeliveryEngine {
1507
1640
  const client = this.runtime.client;
1508
1641
  let groupId = String(notification.group_id ?? '').trim();
1509
1642
  if (!groupId) {
1510
- await client._publishAppEvent('group.message_created', notification);
1643
+ await client._publishAppEvent('group.message_created', notification, 'push');
1511
1644
  return;
1512
1645
  }
1513
1646
  if (client._sessionOptions?.background_sync === false) {
@@ -1657,8 +1790,10 @@ export class MessageDeliveryEngine {
1657
1790
  }
1658
1791
  }
1659
1792
  client._gapFillDone.set(dedupKey, Date.now());
1793
+ this.syncStarted(ns);
1660
1794
  let filled = 0;
1661
1795
  let continuationAfterSeq = 0;
1796
+ let failed = false;
1662
1797
  try {
1663
1798
  let nextAfterSeq = afterSeq;
1664
1799
  const maxPages = singlePage ? 1 : 100;
@@ -1710,6 +1845,7 @@ export class MessageDeliveryEngine {
1710
1845
  }
1711
1846
  const eventSeqs = [];
1712
1847
  let hasDissolvedEvent = false;
1848
+ let publishedEventCount = 0;
1713
1849
  for (const evt of eventObjects) {
1714
1850
  const eventSeq = Number(evt.event_seq ?? 0);
1715
1851
  if (Number.isFinite(eventSeq) && eventSeq > 0)
@@ -1729,10 +1865,12 @@ export class MessageDeliveryEngine {
1729
1865
  }
1730
1866
  }
1731
1867
  if (Number.isFinite(eventSeq) && eventSeq > 0 && !client._pushedSeqs.get(ns)?.has(eventSeq)) {
1732
- this.enqueueOrderedMessage(ns, 'group.changed', eventSeq, evt);
1868
+ this.enqueueOrderedMessage(ns, 'group.changed', eventSeq, evt, 'pull');
1869
+ publishedEventCount += 1;
1733
1870
  }
1734
1871
  }
1735
- filled += 1;
1872
+ if (et !== 'group.message_created')
1873
+ filled += 1;
1736
1874
  }
1737
1875
  const ackContig = client._seqTracker.getContiguousSeq(ns);
1738
1876
  await this.drainOrderedMessages(ns);
@@ -1759,6 +1897,15 @@ export class MessageDeliveryEngine {
1759
1897
  client._clientLog.debug(`group event auto-ack failed: group=${groupId} ${formatDeliveryError(e)}`);
1760
1898
  }
1761
1899
  }
1900
+ this.syncProgress(ns, {
1901
+ rawCount: eventObjects.length,
1902
+ pulledCount: publishedEventCount,
1903
+ remaining: result.remaining,
1904
+ hasMore: typeof result.has_more === 'boolean'
1905
+ ? Boolean(result.has_more) : undefined,
1906
+ currentSeq: ackContig,
1907
+ targetSeq: Math.max(Number(cursor?.latest_seq ?? 0), Number(result.latest_event_seq ?? 0)),
1908
+ });
1762
1909
  const nextAfter = Math.max(eventSeqs.length > 0 ? Math.max(...eventSeqs) : nextAfterSeq, nextAfterSeq);
1763
1910
  if (singlePage && result.has_more === true && nextAfter > nextAfterSeq) {
1764
1911
  continuationAfterSeq = nextAfter;
@@ -1773,6 +1920,7 @@ export class MessageDeliveryEngine {
1773
1920
  client._clientLog.debug(`group event gap fill done: group=${groupId}, after_seq=${afterSeq}, filled=${filled}`);
1774
1921
  }
1775
1922
  catch (exc) {
1923
+ failed = true;
1776
1924
  client._clientLog.warn(`group event gap fill failed: ${formatDeliveryError(exc)}`);
1777
1925
  }
1778
1926
  finally {
@@ -1789,6 +1937,8 @@ export class MessageDeliveryEngine {
1789
1937
  else if (!singlePage && filled > 0 && client._seqTracker.getContiguousSeq(ns) > afterSeq) {
1790
1938
  void this.fillGroupEventGap(groupId);
1791
1939
  }
1940
+ if (!client._rpcPipeline)
1941
+ this.syncStopped(failed ? 'aborted' : 'pull_drained');
1792
1942
  }
1793
1943
  }
1794
1944
  enqueueOnlineUnreadEventHint(data) {
@@ -3628,18 +3778,18 @@ export class MessageDeliveryEngine {
3628
3778
  }
3629
3779
  return true;
3630
3780
  }
3631
- async publishPulledMessage(event, ns, seq, payload, persist = true) {
3781
+ async publishPulledMessage(event, ns, seq, payload, persist = true, source = 'pull') {
3632
3782
  const client = this.runtime.client;
3633
3783
  this.ensurePullOperationCurrent();
3634
3784
  const seqNum = Number(seq);
3635
3785
  if (!Number.isFinite(seqNum) || !Number.isInteger(seqNum) || seqNum <= 0 || !ns) {
3636
3786
  client._clientLog.debug(`publish pulled direct(no-seq): event=${event}, ns=${ns || '<none>'}, seq=${String(seq)}`);
3637
3787
  if (event === 'message.recalled') {
3638
- const published = await this.publishMessageRecallTombstone(seq, payload);
3788
+ const published = await this.publishMessageRecallTombstone(seq, payload, source);
3639
3789
  this.ensurePullOperationCurrent();
3640
3790
  return published;
3641
3791
  }
3642
- const published = client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload, 'pull'));
3792
+ const published = client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload, source));
3643
3793
  if (isPromiseLike(published))
3644
3794
  await published;
3645
3795
  this.ensurePullOperationCurrent();
@@ -3653,27 +3803,27 @@ export class MessageDeliveryEngine {
3653
3803
  client._pendingOrderedMsgs.delete(ns);
3654
3804
  return false;
3655
3805
  }
3656
- await this.drainOrderedMessages(ns, seqNum, true, persist, 'pull');
3806
+ await this.drainOrderedMessages(ns, seqNum, true, persist, source);
3657
3807
  this.ensurePullOperationCurrent();
3658
3808
  queue?.delete(seqNum);
3659
3809
  if (queue && queue.size === 0)
3660
3810
  client._pendingOrderedMsgs.delete(ns);
3661
3811
  if (event === 'message.recalled') {
3662
- const recallPublished = await this.publishMessageRecallTombstone(seqNum, payload);
3812
+ const recallPublished = await this.publishMessageRecallTombstone(seqNum, payload, source);
3663
3813
  this.ensurePullOperationCurrent();
3664
3814
  this.markPublishedSeq(ns, seqNum);
3665
3815
  client._clientLog.debug(`publish pulled delivered: event=${event}, ns=${ns}, seq=${seqNum}`);
3666
- await this.drainOrderedMessages(ns, undefined, true, persist, 'pull');
3816
+ await this.drainOrderedMessages(ns, undefined, true, persist, source);
3667
3817
  this.ensurePullOperationCurrent();
3668
3818
  return recallPublished;
3669
3819
  }
3670
- const published = client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload, 'pull'));
3820
+ const published = client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload, source));
3671
3821
  if (isPromiseLike(published))
3672
3822
  await published;
3673
3823
  this.ensurePullOperationCurrent();
3674
3824
  this.markPublishedSeq(ns, seqNum);
3675
3825
  client._clientLog.debug(`publish pulled delivered: event=${event}, ns=${ns}, seq=${seqNum}`);
3676
- await this.drainOrderedMessages(ns, undefined, true, persist, 'pull');
3826
+ await this.drainOrderedMessages(ns, undefined, true, persist, source);
3677
3827
  this.ensurePullOperationCurrent();
3678
3828
  return true;
3679
3829
  }