@agentunion/fastaun 0.5.34 → 0.5.36

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 (88) hide show
  1. package/CHANGELOG.md +56 -1
  2. package/_packed_docs/CHANGELOG.md +56 -1
  3. package/_packed_docs/INDEX.md +18 -11
  4. package/_packed_docs/KITE_DOCS_GUIDE.md +9 -9
  5. package/_packed_docs/agent.md/SCHEMA.md +1 -1
  6. package/_packed_docs/aun/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +2 -2
  7. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2112.1.md +17 -17
  8. package/_packed_docs/protocol/echo-probe-gateway.md +99 -0
  9. package/_packed_docs/protocol/echo-probe-sdk.md +91 -0
  10. package/_packed_docs/sdk/0.5.35-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +189 -0
  11. package/_packed_docs/sdk/0.5.35-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +204 -0
  12. package/_packed_docs/sdk/0.5.36-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +130 -0
  13. package/_packed_docs/sdk/0.5.36-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +176 -0
  14. package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +2 -2
  15. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +32 -6
  16. package/_packed_docs/sdk/09-group-rpc-manual.md +7 -7
  17. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +13 -13
  18. package/_packed_docs/sdk/AUN/344/272/213/344/273/266/345/217/221/345/270/203/350/256/242/351/230/205/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +73 -3
  19. package/_packed_docs/sdk/AUN/344/272/213/344/273/266/345/217/221/345/270/203/350/256/242/351/230/205/345/274/200/345/217/221/347/247/273/346/244/215/346/214/207/345/215/227.md +50 -8
  20. package/_packed_docs/sdk/INDEX.md +23 -18
  21. package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/2272.1.md +1 -1
  22. package/_packed_docs/sdk/Ownership2.1-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +5 -5
  23. package/_packed_docs/sdk/Ownership2.1-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +5 -5
  24. package/_packed_docs/sdk/Ownership2.1/350/257/246/347/273/206/350/256/276/350/256/241.md +9 -9
  25. package/_packed_docs//346/266/210/346/201/257/345/274/271/346/200/247/350/241/245/346/213/211/351/200/232/351/201/223/345/256/236/346/226/275/350/256/241/345/210/222.md +400 -0
  26. package/_packed_docs//346/266/210/346/201/257/345/274/271/346/200/247/350/241/245/346/213/211/351/200/232/351/201/223/346/226/271/346/241/210.md +485 -0
  27. package/dist/agent-md.d.ts +2 -1
  28. package/dist/agent-md.js +33 -6
  29. package/dist/agent-md.js.map +1 -1
  30. package/dist/aid-store.js +5 -1
  31. package/dist/aid-store.js.map +1 -1
  32. package/dist/auth.js +36 -45
  33. package/dist/auth.js.map +1 -1
  34. package/dist/client/delivery.d.ts +8 -1
  35. package/dist/client/delivery.js +203 -50
  36. package/dist/client/delivery.js.map +1 -1
  37. package/dist/client/lifecycle.js +10 -1
  38. package/dist/client/lifecycle.js.map +1 -1
  39. package/dist/client/rpc-pipeline.d.ts +4 -0
  40. package/dist/client/rpc-pipeline.js +93 -10
  41. package/dist/client/rpc-pipeline.js.map +1 -1
  42. package/dist/client/sync-channels.d.ts +2 -1
  43. package/dist/client/sync-channels.js +33 -14
  44. package/dist/client/sync-channels.js.map +1 -1
  45. package/dist/client/v2-e2ee.d.ts +1 -0
  46. package/dist/client/v2-e2ee.js +24 -6
  47. package/dist/client/v2-e2ee.js.map +1 -1
  48. package/dist/client.d.ts +15 -0
  49. package/dist/client.js +329 -56
  50. package/dist/client.js.map +1 -1
  51. package/dist/events.d.ts +1 -0
  52. package/dist/events.js +5 -0
  53. package/dist/events.js.map +1 -1
  54. package/dist/index.d.ts +2 -0
  55. package/dist/index.js +1 -0
  56. package/dist/index.js.map +1 -1
  57. package/dist/mail-client.d.ts +101 -0
  58. package/dist/mail-client.js +369 -0
  59. package/dist/mail-client.js.map +1 -0
  60. package/dist/ownership-v21.d.ts +2 -1
  61. package/dist/ownership-v21.js +19 -9
  62. package/dist/ownership-v21.js.map +1 -1
  63. package/dist/pubsub/binding.js +9 -2
  64. package/dist/pubsub/binding.js.map +1 -1
  65. package/dist/pubsub/channel.d.ts +11 -3
  66. package/dist/pubsub/channel.js +91 -10
  67. package/dist/pubsub/channel.js.map +1 -1
  68. package/dist/pubsub/client.d.ts +8 -2
  69. package/dist/pubsub/client.js +62 -21
  70. package/dist/pubsub/client.js.map +1 -1
  71. package/dist/pubsub/index.d.ts +2 -2
  72. package/dist/pubsub/index.js +1 -1
  73. package/dist/pubsub/index.js.map +1 -1
  74. package/dist/pubsub/service.d.ts +6 -0
  75. package/dist/pubsub/service.js +101 -10
  76. package/dist/pubsub/service.js.map +1 -1
  77. package/dist/pubsub/types.d.ts +11 -1
  78. package/dist/tools/cross-sdk-agent.d.ts +1 -1
  79. package/dist/transport.d.ts +4 -0
  80. package/dist/transport.js +39 -2
  81. package/dist/transport.js.map +1 -1
  82. package/dist/udp-standby.d.ts +88 -0
  83. package/dist/udp-standby.js +483 -0
  84. package/dist/udp-standby.js.map +1 -0
  85. package/dist/version.d.ts +1 -1
  86. package/dist/version.js +1 -1
  87. package/package.json +5 -1
  88. package/scripts/patch-imapflow-external.mjs +69 -0
@@ -104,9 +104,73 @@ function deduplicatePlainForwardPageMessages(value, afterSeq) {
104
104
  }
105
105
  export class MessageDeliveryEngine {
106
106
  runtime;
107
+ forwardAckJobs = new Map();
108
+ forwardAckTasks = new Set();
109
+ scheduleForwardAck(ns, seq, send) {
110
+ const client = this.runtime.client;
111
+ if (client._closing || seq <= 0)
112
+ return;
113
+ const scope = () => [client._aid, client._deviceId, client._slotId, this.captureInlineAckGeneration(),
114
+ client._v2RuntimeGeneration, client._transport, client._transport?._connectionGeneration,
115
+ client._transport?._workerGeneration, this.captureSeqNamespaceGeneration(ns)];
116
+ const current = scope();
117
+ const previous = this.forwardAckJobs.get(ns);
118
+ if (previous && previous.scope.every((value, i) => value === current[i])) {
119
+ if (seq > previous.seq) {
120
+ previous.seq = seq;
121
+ previous.send = send;
122
+ }
123
+ return;
124
+ }
125
+ const job = { seq, scope: current, send };
126
+ this.forwardAckJobs.set(ns, job);
127
+ const isCurrent = () => this.forwardAckJobs.get(ns) === job && job.scope.every((value, i) => value === scope()[i]);
128
+ const task = (async () => {
129
+ let failures = 0;
130
+ while (isCurrent()) {
131
+ const pending = job.seq;
132
+ try {
133
+ await job.send(pending);
134
+ failures = 0;
135
+ if (job.seq <= pending) {
136
+ if (this.forwardAckJobs.get(ns) === job)
137
+ this.forwardAckJobs.delete(ns);
138
+ return;
139
+ }
140
+ }
141
+ catch {
142
+ if (!isCurrent() || client._closing || client.state !== ConnectionState.READY)
143
+ return;
144
+ if (job.seq <= pending)
145
+ await new Promise(resolve => setTimeout(resolve, Math.min(100 * 2 ** Math.min(failures++, 5), 3000)));
146
+ }
147
+ }
148
+ })().finally(() => {
149
+ if (this.forwardAckJobs.get(ns) === job)
150
+ this.forwardAckJobs.delete(ns);
151
+ this.forwardAckTasks.delete(task);
152
+ });
153
+ this.forwardAckTasks.add(task);
154
+ }
155
+ drainForwardAcks() {
156
+ if (!this.forwardAckTasks.size)
157
+ return;
158
+ return this.waitForwardAcks();
159
+ }
160
+ async waitForwardAcks() {
161
+ let timer;
162
+ try {
163
+ await Promise.race([Promise.allSettled([...this.forwardAckTasks]), new Promise(resolve => { timer = setTimeout(resolve, 1000); })]);
164
+ }
165
+ finally {
166
+ if (timer)
167
+ clearTimeout(timer);
168
+ }
169
+ }
107
170
  realtimeTailHeads = null;
108
171
  realtimeSyncing = null;
109
172
  pendingGroupPullUpper = null;
173
+ pendingV2GroupPulls = new Set();
110
174
  onlineUnreadHintTargets = null;
111
175
  onlineUnreadHintTasks = null;
112
176
  backgroundPullNeeds = new Map();
@@ -116,6 +180,7 @@ export class MessageDeliveryEngine {
116
180
  backgroundPullTimerAt = 0;
117
181
  pendingP2PInlineAcks = null;
118
182
  pendingGroupInlineAcks = null;
183
+ pendingGroupInlineAckCount = 0;
119
184
  pendingGroupInlineAckNamespaces = null;
120
185
  inlineAckGeneration = 0;
121
186
  pendingDeliveryChanges = null;
@@ -574,6 +639,7 @@ export class MessageDeliveryEngine {
574
639
  if (!namespace.startsWith('p2p.peer.v1/') || !Number.isSafeInteger(sequence) || sequence < 0)
575
640
  return;
576
641
  this.peerServerConfirmedAcks.set(namespace, Math.max(this.peerServerConfirmedAcks.get(namespace) ?? 0, sequence));
642
+ this.runtime.client._rpcPipeline?.confirmBackgroundAck?.(namespace, sequence);
577
643
  }
578
644
  rememberCommittedSeqState(namespace) {
579
645
  this.ensurePeerContext();
@@ -935,6 +1001,8 @@ export class MessageDeliveryEngine {
935
1001
  if (this.peerRecoveryTasks.has(sender))
936
1002
  return;
937
1003
  const generation = this.captureInlineAckGeneration();
1004
+ const recoveryVersion = this.capturePeerTailVersion(sender);
1005
+ const recoveryAck = this.peerCommittedAck(namespace);
938
1006
  const task = (async () => {
939
1007
  await this.runAutomaticPull(`peer-init:${sender}`, () => this.initializePeer(sender));
940
1008
  if (!this.hasP2pApplicationSubscription())
@@ -996,7 +1064,9 @@ export class MessageDeliveryEngine {
996
1064
  const beforeAck = this.peerCommittedAck(namespace);
997
1065
  const beforeVolatile = this.peerVolatilePullCursor(sender);
998
1066
  const beforeWindow = this.syncState(namespace);
999
- const result = await client._v2E2EE.pullPeerV2(sender, { background: true, retainMessages: false, budget: { remaining: 100 } });
1067
+ const result = await client._v2E2EE.pullPeerV2(sender, { background: true, retainMessages: false, budget: { remaining: 100 },
1068
+ ...(!need.volatile && need.head > 0 && pushVersion === recoveryVersion.push && catalogVersion === recoveryVersion.catalog
1069
+ ? { autoPeerHead: need.head, autoPeerAckFloor: recoveryAck } : {}) });
1000
1070
  need.volatile ||= result.volatile_has_more === true;
1001
1071
  const afterWindow = this.syncState(namespace);
1002
1072
  if ((!result.complete || result.ack_seq < need.head) && this.peerCommittedAck(namespace) <= beforeAck && this.peerVolatilePullCursor(sender) === beforeVolatile
@@ -1106,7 +1176,7 @@ export class MessageDeliveryEngine {
1106
1176
  this.onGroupV2AckSucceeded(groupId, actualAckSeq, generation);
1107
1177
  }
1108
1178
  }
1109
- async commitPlainForwardPage(ns, afterSeq, method, result, groupId = '') {
1179
+ async commitPlainForwardPage(ns, afterSeq, method, result, groupId = '', background = false) {
1110
1180
  if (!isJsonObject(result)) {
1111
1181
  throw new Error(`${method} response must be an object`);
1112
1182
  }
@@ -1250,7 +1320,10 @@ export class MessageDeliveryEngine {
1250
1320
  throw new Error(`${ackMethod} cannot confirm pending Forward watermark ${pendingAckSeq}`);
1251
1321
  }
1252
1322
  this.ensurePullOperationCurrent(ns);
1253
- await this.confirmPlainForwardAck(ns, ackMethod, pendingAckSeq, groupId);
1323
+ if (background)
1324
+ this.scheduleForwardAck(ns, pendingAckSeq, seq => this.confirmPlainForwardAck(ns, ackMethod, seq, groupId));
1325
+ else
1326
+ await this.confirmPlainForwardAck(ns, ackMethod, pendingAckSeq, groupId);
1254
1327
  }
1255
1328
  this.syncProgress(ns, {
1256
1329
  rawCount: messages.length,
@@ -1455,9 +1528,11 @@ export class MessageDeliveryEngine {
1455
1528
  void this.runtime.client._rpcPipeline?.invalidatePulls?.();
1456
1529
  this.pendingP2PInlineAcks = null;
1457
1530
  this.pendingGroupInlineAcks = null;
1531
+ this.pendingGroupInlineAckCount = 0;
1458
1532
  this.pendingGroupInlineAckNamespaces = null;
1459
1533
  this.realtimeSyncing = null;
1460
1534
  this.pendingGroupPullUpper = null;
1535
+ this.pendingV2GroupPulls.clear();
1461
1536
  this.onlineUnreadHintTargets = null;
1462
1537
  this.onlineUnreadHintTasks = null;
1463
1538
  this.realtimeTailHeads = null;
@@ -1715,7 +1790,7 @@ export class MessageDeliveryEngine {
1715
1790
  await processing;
1716
1791
  }
1717
1792
  pendingInlineAckNamespaceCount() {
1718
- return (this.pendingP2PInlineAcks?.size ?? 0) + (this.pendingGroupInlineAcks?.size ?? 0);
1793
+ return (this.pendingP2PInlineAcks?.size ?? 0) + this.pendingGroupInlineAckCount;
1719
1794
  }
1720
1795
  markRealtimeTailCompleted(result, tailHead) {
1721
1796
  const heads = this.realtimeTailHeads ?? new WeakMap();
@@ -1937,6 +2012,11 @@ export class MessageDeliveryEngine {
1937
2012
  this.backgroundPullNeeds.delete(ns);
1938
2013
  continue;
1939
2014
  }
2015
+ if (this.pendingV2GroupPulls.has(ns)) {
2016
+ if (!client._v2Session)
2017
+ continue;
2018
+ this.pendingV2GroupPulls.delete(ns);
2019
+ }
1940
2020
  if (ns.startsWith('p2p:') && !this.hasP2pApplicationSubscription())
1941
2021
  continue;
1942
2022
  if (need.messageClassRequest && need.messageClassRequest.message_class !== 'sdk_control') {
@@ -2362,6 +2442,8 @@ export class MessageDeliveryEngine {
2362
2442
  return applicationResult({ ...result, message_class: 'sdk_control' });
2363
2443
  if (!APP_SEND_ENVELOPE_METHODS.has(method) || !isJsonObject(result))
2364
2444
  return result;
2445
+ if (result.status === 'echo_return')
2446
+ return result;
2365
2447
  let out = { ...result };
2366
2448
  const serverEnvelope = method === 'group.send' && isJsonObject(out.envelope)
2367
2449
  ? { ...out.envelope }
@@ -2686,8 +2768,10 @@ export class MessageDeliveryEngine {
2686
2768
  return this.runApplicationPage(namespace, source, async () => { await this.publishAppEvent(event, payload, source, deliveryContext); });
2687
2769
  }
2688
2770
  }
2689
- if ((event === 'message.received' || event === 'group.message_created') && isJsonObject(payload)) {
2690
- client._maybeAppendEchoTraceReceive(payload);
2771
+ if (source !== 'echo_return' && (event === 'message.received' || event === 'group.message_created') && isJsonObject(payload)) {
2772
+ if (client._maybeAppendEchoTraceReceive(payload)) {
2773
+ return client._returnReceivedEcho(payload);
2774
+ }
2691
2775
  }
2692
2776
  this.logAppMessagePublish(event, payload, source);
2693
2777
  if (isJsonObject(payload)) {
@@ -2929,7 +3013,14 @@ export class MessageDeliveryEngine {
2929
3013
  }
2930
3014
  async onRawMessageReceived(data) {
2931
3015
  const client = this.runtime.client;
2932
- if (client._p2pPeerPullEnabled) {
3016
+ if (isJsonObject(data) && !client._echoStarts.has(data))
3017
+ client._echoStarts.set(data, performance.now());
3018
+ const message = isJsonObject(data) ? data : undefined;
3019
+ const payload = message?.payload;
3020
+ const plainEcho = message && !message.encrypted && !message.encrypt && isJsonObject(payload)
3021
+ && !String(payload.type ?? '').trim().toLowerCase().startsWith('e2ee') && !payload.ciphertext
3022
+ && !client._isEncryptedPushMessage(message) && client._isEchoPayload(payload);
3023
+ if (client._p2pPeerPullEnabled && !plainEcho) {
2933
3024
  await this.onPeerPush(data);
2934
3025
  return;
2935
3026
  }
@@ -2998,6 +3089,7 @@ export class MessageDeliveryEngine {
2998
3089
  return;
2999
3090
  }
3000
3091
  const msg = { ...data };
3092
+ client._echoStarts.set(msg, client._echoStarts.get(data) ?? performance.now());
3001
3093
  if (!this.messageTargetsCurrentInstance(msg)) {
3002
3094
  client._clientLog.debug(`P2P push filtered by instance: message_id=${String(msg.message_id ?? '')}, seq=${String(msg.seq ?? '')}, target_device=${String(msg.device_id ?? '')}, target_slot=${String(msg.slot_id ?? '')}, local_device=${client._deviceId}, local_slot=${client._slotId}`);
3003
3095
  return;
@@ -3054,6 +3146,8 @@ export class MessageDeliveryEngine {
3054
3146
  }
3055
3147
  async onRawGroupMessageCreated(data) {
3056
3148
  const client = this.runtime.client;
3149
+ if (isJsonObject(data) && !client._echoStarts.has(data))
3150
+ client._echoStarts.set(data, performance.now());
3057
3151
  const tStart = Date.now();
3058
3152
  if (isJsonObject(data)) {
3059
3153
  client._logMessageDebug('server-push', '_raw.group.message_created', 'group.message_created', data);
@@ -3094,6 +3188,7 @@ export class MessageDeliveryEngine {
3094
3188
  }
3095
3189
  const msg = { ...data };
3096
3190
  const groupId = String(msg.group_id ?? '');
3191
+ client._echoStarts.set(msg, client._echoStarts.get(data) ?? performance.now());
3097
3192
  const seq = msg.seq;
3098
3193
  const payload = msg.payload;
3099
3194
  if (groupId) {
@@ -3210,7 +3305,7 @@ export class MessageDeliveryEngine {
3210
3305
  try {
3211
3306
  // Gap Fill 本身就是 Forward Pull,必须沿用协调器的完整事务:
3212
3307
  // 原始页提交成功后由协调器负责 Piggyback/终端 ACK。
3213
- const operation = () => client._pullV2(afterSeq, _INTERNAL_MESSAGE_FLOW_LIMITS.pullPageSize, { gateLocked: true });
3308
+ const operation = () => client._pullV2(afterSeq, _INTERNAL_MESSAGE_FLOW_LIMITS.pullPageSize, { gateLocked: true, background: true });
3214
3309
  const messages = await this.runAutomaticPull(ns, operation, (attempt) => client._rpcPipeline
3215
3310
  ? client._rpcPipeline.runPullSerialized(client._rpcPipeline.pullGateKeyForCall('message.pull', { after_seq: afterSeq, limit: _INTERNAL_MESSAGE_FLOW_LIMITS.pullPageSize }), attempt, true)
3216
3311
  : client._withBackgroundRpc(attempt));
@@ -3606,7 +3701,7 @@ export class MessageDeliveryEngine {
3606
3701
  return result;
3607
3702
  }
3608
3703
  reservePendingGroupInlineAck(ns, seq, row, groupAid) {
3609
- if (!ns || !Number.isSafeInteger(seq) || seq <= 0 || this.pendingGroupInlineAcks?.has(ns)
3704
+ if (!ns || !Number.isSafeInteger(seq) || seq <= 0 || this.pendingGroupInlineAcks?.get(ns)?.has(seq)
3610
3705
  || this.pendingInlineAckNamespaceCount() >= MAX_INLINE_PENDING_ACK_NAMESPACES)
3611
3706
  return false;
3612
3707
  const normalizedGroupAid = typeof groupAid === 'string' ? groupAid.trim() : '';
@@ -3616,7 +3711,9 @@ export class MessageDeliveryEngine {
3616
3711
  const namespaceMap = this.pendingGroupInlineAckNamespaces ?? new Map();
3617
3712
  this.pendingGroupInlineAcks = pendingMap;
3618
3713
  this.pendingGroupInlineAckNamespaces = namespaceMap;
3619
- pendingMap.set(ns, {
3714
+ const entries = pendingMap.get(ns) ?? new Map();
3715
+ pendingMap.set(ns, entries);
3716
+ entries.set(seq, {
3620
3717
  seq,
3621
3718
  row: this.inlinePostAckRow(row),
3622
3719
  groupAid: normalizedGroupAid,
@@ -3625,6 +3722,7 @@ export class MessageDeliveryEngine {
3625
3722
  committed: false,
3626
3723
  observedAckSeq: 0,
3627
3724
  });
3725
+ this.pendingGroupInlineAckCount++;
3628
3726
  for (const namespace of namespaces) {
3629
3727
  let candidates = namespaceMap.get(namespace);
3630
3728
  if (!candidates) {
@@ -3637,10 +3735,23 @@ export class MessageDeliveryEngine {
3637
3735
  }
3638
3736
  removePendingGroupInlineAck(ns, pending) {
3639
3737
  const pendingMap = this.pendingGroupInlineAcks;
3640
- if (pendingMap?.get(ns) !== pending)
3738
+ const entries = pendingMap?.get(ns);
3739
+ if (entries?.get(pending.seq) !== pending)
3641
3740
  return false;
3642
- pendingMap.delete(ns);
3741
+ entries.delete(pending.seq);
3742
+ this.pendingGroupInlineAckCount--;
3743
+ if (entries.size === 0)
3744
+ pendingMap?.delete(ns);
3643
3745
  for (const namespace of pending.namespaces) {
3746
+ let retained = false;
3747
+ for (const other of entries.values()) {
3748
+ if (other.namespaces.includes(namespace)) {
3749
+ retained = true;
3750
+ break;
3751
+ }
3752
+ }
3753
+ if (retained)
3754
+ continue;
3644
3755
  const aliases = this.pendingGroupInlineAckNamespaces?.get(namespace);
3645
3756
  if (!aliases)
3646
3757
  continue;
@@ -3651,8 +3762,8 @@ export class MessageDeliveryEngine {
3651
3762
  return true;
3652
3763
  }
3653
3764
  dropPendingGroupInlineAck(ns, seq) {
3654
- const pending = this.pendingGroupInlineAcks?.get(ns);
3655
- if (pending?.seq === seq)
3765
+ const pending = this.pendingGroupInlineAcks?.get(ns)?.get(seq);
3766
+ if (pending)
3656
3767
  this.removePendingGroupInlineAck(ns, pending);
3657
3768
  }
3658
3769
  resolvePendingGroupInlineAckNamespace(ns) {
@@ -3673,19 +3784,23 @@ export class MessageDeliveryEngine {
3673
3784
  const canonicalNs = candidates.values().next().value;
3674
3785
  if (typeof canonicalNs !== 'string')
3675
3786
  return;
3676
- const pending = this.pendingGroupInlineAcks?.get(canonicalNs);
3677
- if (!pending || upToSeq < pending.seq)
3678
- return;
3679
- if (!pending.committed) {
3680
- pending.observedAckSeq = Math.max(pending.observedAckSeq, upToSeq);
3787
+ const entries = this.pendingGroupInlineAcks?.get(canonicalNs);
3788
+ if (!entries)
3681
3789
  return;
3790
+ for (const pending of entries.values()) {
3791
+ if (upToSeq < pending.seq)
3792
+ continue;
3793
+ if (!pending.committed) {
3794
+ pending.observedAckSeq = Math.max(pending.observedAckSeq, upToSeq);
3795
+ continue;
3796
+ }
3797
+ this.removePendingGroupInlineAck(canonicalNs, pending);
3798
+ this.runPostInlineCommit(pending.row);
3682
3799
  }
3683
- this.removePendingGroupInlineAck(canonicalNs, pending);
3684
- this.runPostInlineCommit(pending.row);
3685
3800
  }
3686
3801
  markPendingGroupInlineAckCommitted(ns, seq) {
3687
- const pending = this.pendingGroupInlineAcks?.get(ns);
3688
- if (!pending || pending.seq !== seq)
3802
+ const pending = this.pendingGroupInlineAcks?.get(ns)?.get(seq);
3803
+ if (!pending)
3689
3804
  return false;
3690
3805
  pending.committed = true;
3691
3806
  if (pending.observedAckSeq < seq)
@@ -3695,8 +3810,8 @@ export class MessageDeliveryEngine {
3695
3810
  return true;
3696
3811
  }
3697
3812
  retryPendingGroupInlineAck(ns, seq, reason, ackThrough = seq) {
3698
- const pending = this.pendingGroupInlineAcks?.get(ns);
3699
- if (!pending || pending.seq !== seq)
3813
+ const pending = this.pendingGroupInlineAcks?.get(ns)?.get(seq);
3814
+ if (!pending)
3700
3815
  return false;
3701
3816
  if (pending.acking || !pending.committed)
3702
3817
  return true;
@@ -3715,12 +3830,12 @@ export class MessageDeliveryEngine {
3715
3830
  })).then((result) => {
3716
3831
  this.onGroupV2AckResult(groupId, result, requestedSeq, generation);
3717
3832
  }).catch((error) => {
3718
- const current = this.pendingGroupInlineAcks?.get(ns);
3833
+ const current = this.pendingGroupInlineAcks?.get(ns)?.get(seq);
3719
3834
  if (current === pending)
3720
3835
  current.acking = false;
3721
3836
  this.runtime.client._clientLog.debug(`group.v2 ${reason} ack failed: group=${groupId} ${formatDeliveryError(error)}`);
3722
3837
  }).finally(() => {
3723
- const current = this.pendingGroupInlineAcks?.get(ns);
3838
+ const current = this.pendingGroupInlineAcks?.get(ns)?.get(seq);
3724
3839
  if (current === pending)
3725
3840
  current.acking = false;
3726
3841
  });
@@ -3743,6 +3858,7 @@ export class MessageDeliveryEngine {
3743
3858
  const gid = String(groupId ?? '').trim();
3744
3859
  if (!gid || !Number.isSafeInteger(upToSeq) || upToSeq <= 0)
3745
3860
  return;
3861
+ this.runtime.client._rpcPipeline?.confirmBackgroundAck?.(this.resolvePendingGroupInlineAckNamespace(`group:${gid}`), upToSeq);
3746
3862
  this.completePendingGroupInlineAck(`group:${gid}`, upToSeq);
3747
3863
  }
3748
3864
  onGroupV2AckResult(groupId, result, requestedSeq, generation = this.inlineAckGeneration) {
@@ -3859,7 +3975,7 @@ export class MessageDeliveryEngine {
3859
3975
  tailHead = this.syncState(ns).head;
3860
3976
  return tailResult;
3861
3977
  }
3862
- const opts = { gateLocked: true };
3978
+ const opts = { gateLocked: true, background };
3863
3979
  if (request.max_pages !== undefined)
3864
3980
  opts.maxPages = request.max_pages;
3865
3981
  const messages = await client._pullV2(after, pageLimit, opts);
@@ -3985,7 +4101,7 @@ export class MessageDeliveryEngine {
3985
4101
  const client = this.runtime.client;
3986
4102
  const request = { after_seq: this.syncState(ns).ack, limit: pageLimit, max_pages: 1 };
3987
4103
  const invoke = async () => {
3988
- const messages = await client._pullV2(Number(request.after_seq), pageLimit, { gateLocked: true, maxPages: 1 });
4104
+ const messages = await client._pullV2(Number(request.after_seq), pageLimit, { gateLocked: true, maxPages: 1, background });
3989
4105
  return { messages, raw_count: messages.length };
3990
4106
  };
3991
4107
  if (!client._rpcPipeline)
@@ -4709,15 +4825,20 @@ export class MessageDeliveryEngine {
4709
4825
  client._seqTracker._restoreNamespaceSnapshotPreservingPush(ns, snapshot);
4710
4826
  throw error;
4711
4827
  }
4712
- await client._v2E2EE.ackPeerV2(sender, this.syncState(ns).ack);
4713
4828
  return true;
4714
4829
  })).catch((error) => {
4715
4830
  if (generation === this.captureInlineAckGeneration())
4716
4831
  this.schedulePeerRecovery(sender, seq);
4717
4832
  throw error;
4718
4833
  });
4719
- if (consumed?.acquired && consumed.value && !unbounded)
4720
- return;
4834
+ if (consumed?.acquired && consumed.value && generation === this.captureInlineAckGeneration()) {
4835
+ this.scheduleForwardAck(ns, this.peerCommittedAck(ns), async (ack) => {
4836
+ if (!this.isPeerHeadCovered(ns, ack))
4837
+ await client._v2E2EE.ackPeerV2(sender, ack);
4838
+ });
4839
+ if (!unbounded)
4840
+ return;
4841
+ }
4721
4842
  }
4722
4843
  if ((!unbounded && this.isPeerHeadCovered(ns, seq)) || !this.hasP2pApplicationSubscription())
4723
4844
  return;
@@ -4729,7 +4850,7 @@ export class MessageDeliveryEngine {
4729
4850
  }
4730
4851
  else {
4731
4852
  await this.runAutomaticPull(ns, () => client._v2E2EE.pullPeerV2(sender, { limit: _INTERNAL_MESSAGE_FLOW_LIMITS.pullPageSize, budget: { remaining: 1 },
4732
- ...(!unbounded ? { autoPeerHead: seq, peerPullVersion } : {}),
4853
+ ...(!unbounded ? { autoPeerHead: seq, autoPeerAckFloor: committedAck, peerPullVersion } : {}),
4733
4854
  ...(this.shouldTailPeer(ns, seq, _INTERNAL_MESSAGE_FLOW_LIMITS.pullPageSize) ? { window_mode: 'tail', autoTail: { head: seq, forward: false } } : {}) }));
4734
4855
  }
4735
4856
  }
@@ -4843,11 +4964,11 @@ export class MessageDeliveryEngine {
4843
4964
  const coordinator = client._v2E2EE;
4844
4965
  const pendingAckSeq = Number(coordinator.pendingForwardAck?.(ns, committedAck) ?? 0);
4845
4966
  if (pendingAckSeq > 0) {
4846
- client._safeAsync(coordinator.confirmForwardGroupAck(groupId, pendingAckSeq, String(data.group_aid ?? '').trim() || undefined, true));
4967
+ this.scheduleForwardAck(ns, pendingAckSeq, seq => coordinator.confirmForwardGroupAck(groupId, seq, String(data.group_aid ?? '').trim() || undefined, true));
4847
4968
  }
4848
4969
  else {
4849
4970
  const pendingNs = this.resolvePendingGroupInlineAckNamespace(ns);
4850
- const pending = this.pendingGroupInlineAcks?.get(pendingNs);
4971
+ const pending = this.pendingGroupInlineAcks?.get(pendingNs)?.values().next().value;
4851
4972
  if (pending)
4852
4973
  this.retryPendingGroupInlineAck(pendingNs, pending.seq, 'covered online hint', committedAck);
4853
4974
  }
@@ -4904,8 +5025,6 @@ export class MessageDeliveryEngine {
4904
5025
  const groupId = typeof d.group_id === 'string' ? d.group_id.trim() : '';
4905
5026
  const rawSeq = d.seq;
4906
5027
  const inlinePresent = hasNonEmptyInlineMessage(d.inline_message);
4907
- if (!client._v2Session && !(expectedVersion === 'v1' && inlinePresent))
4908
- return;
4909
5028
  const seq = this.realtimeHeadSeq(rawSeq, inlinePresent);
4910
5029
  if (!groupId) {
4911
5030
  client._clientLog.debug(`_onRawGroupV2MessageCreated skipped: group=${groupId || '<empty>'}, seq=${String(d.seq ?? '')}`);
@@ -4918,6 +5037,15 @@ export class MessageDeliveryEngine {
4918
5037
  return;
4919
5038
  }
4920
5039
  const eventKind = String(d.kind ?? '').trim();
5040
+ if (!client._v2Session && !(expectedVersion === 'v1' && inlinePresent)) {
5041
+ if (client._sessionOptions?.background_sync !== false) {
5042
+ const ns = `group:${groupId}`;
5043
+ this.recordBackgroundPullNeed(ns, seq, eventKind === 'group.online_unread_hint', true);
5044
+ if (this.backgroundPullNeeds.has(ns))
5045
+ this.pendingV2GroupPulls.add(ns);
5046
+ }
5047
+ return;
5048
+ }
4921
5049
  if (eventKind === 'group.online_unread_hint') {
4922
5050
  if (client._sessionOptions.background_sync === false)
4923
5051
  return;
@@ -4928,9 +5056,7 @@ export class MessageDeliveryEngine {
4928
5056
  if (this.automaticGroupPullStopped(ns))
4929
5057
  return;
4930
5058
  let inlineConsumed = false;
4931
- const pendingInlineAck = this.pendingGroupInlineAcks?.get(ns);
4932
- const inlineBlockedByPendingAck = Boolean(pendingInlineAck && pendingInlineAck.seq !== seq);
4933
- if (inlinePresent && !inlineBlockedByPendingAck && isJsonObject(d.inline_message)) {
5059
+ if (inlinePresent && isJsonObject(d.inline_message)) {
4934
5060
  try {
4935
5061
  inlineConsumed = await this.tryConsumeInlineGroup(d, groupId, ns, seq, expectedVersion, inlineGeneration);
4936
5062
  if (inlineGeneration !== undefined && !this.inlineGenerationIsCurrent(inlineGeneration))
@@ -4951,8 +5077,8 @@ export class MessageDeliveryEngine {
4951
5077
  client._seqTracker.updateMaxSeen(ns, seq);
4952
5078
  if (seq <= before.ack && before.ack >= before.tail - 1) {
4953
5079
  client._clientLog.debug(`V2 group duplicate push ignored: group=${groupId} seq=${seq} A/T/H=${JSON.stringify(before)}`);
4954
- const pending = this.pendingGroupInlineAcks?.get(ns);
4955
- if (inlinePresent && pending?.seq === seq) {
5080
+ const pending = this.pendingGroupInlineAcks?.get(ns)?.get(seq);
5081
+ if (inlinePresent && pending) {
4956
5082
  this.retryPendingGroupInlineAck(ns, seq, 'covered inline push');
4957
5083
  }
4958
5084
  return;
@@ -5052,18 +5178,44 @@ export class MessageDeliveryEngine {
5052
5178
  this.refreshPendingPeers();
5053
5179
  return;
5054
5180
  }
5055
- if (!client._aid || !client._v2Session)
5181
+ if (!client._aid || !client._v2Session || client._sessionOptions?.background_sync === false)
5056
5182
  return;
5057
5183
  const ns = `p2p:${client._aid}`;
5058
5184
  const generation = this.captureInlineAckGeneration();
5059
- const afterSeq = this.syncState(ns).ack;
5060
- this.recordBackgroundPullNeed(ns, positiveSafeSequenceHint(data.seq), true, true);
5185
+ const state = this.syncState(ns);
5186
+ const afterSeq = state.ack;
5187
+ const hintSeq = positiveSafeSequenceHint(data.seq);
5188
+ if (hintSeq > 0 && afterSeq >= Math.max(hintSeq, state.head, Number(client._seqTracker.getMaxSeenSeq?.(ns) ?? 0), Number(client._pendingP2pPullUpper?.get?.(ns) ?? 0))
5189
+ && afterSeq >= state.tail - 1 && !client._pendingOrderedMsgs.get(ns)?.size) {
5190
+ const coordinator = client._v2E2EE;
5191
+ const pendingAckSeq = Number(coordinator.pendingForwardAck?.(ns, afterSeq) ?? 0);
5192
+ if (pendingAckSeq > 0) {
5193
+ this.scheduleForwardAck(ns, pendingAckSeq, seq => coordinator.confirmForwardP2PAck(seq));
5194
+ }
5195
+ else {
5196
+ const pending = this.pendingP2PInlineAcks?.get(ns);
5197
+ if (pending)
5198
+ this.retryPendingP2PInlineAck(ns, pending.seq, 'covered online hint', afterSeq);
5199
+ }
5200
+ return;
5201
+ }
5202
+ this.recordBackgroundPullNeed(ns, hintSeq, true, true);
5061
5203
  const pullTrigger = this.backgroundPullNeeds.get(ns)?.trigger;
5204
+ const tasks = this.onlineUnreadHintTasks ?? new Map();
5205
+ this.onlineUnreadHintTasks = tasks;
5206
+ if (tasks.has(ns))
5207
+ return;
5062
5208
  let failed = false;
5063
- client._safeAsync(this.runP2PForwardRecovery(ns, _INTERNAL_MESSAGE_FLOW_LIMITS.pullPageSize, true).catch((exc) => {
5209
+ const task = this.runP2PForwardRecovery(ns, _INTERNAL_MESSAGE_FLOW_LIMITS.pullPageSize, true).then(() => undefined).catch((exc) => {
5064
5210
  failed = true;
5065
5211
  client._clientLog.debug(`P2P online unread hint background Forward failed: ns=${ns} err=${formatDeliveryError(exc)}`);
5066
- }).finally(() => this.finishBackgroundPull(ns, generation, afterSeq, failed, pullTrigger)));
5212
+ }).finally(() => {
5213
+ if (tasks.get(ns) === task)
5214
+ tasks.delete(ns);
5215
+ this.finishBackgroundPull(ns, generation, afterSeq, failed, pullTrigger);
5216
+ });
5217
+ tasks.set(ns, task);
5218
+ client._safeAsync(task);
5067
5219
  }
5068
5220
  async saveSeqTrackerState() {
5069
5221
  const client = this.runtime.client;
@@ -5413,9 +5565,10 @@ export class MessageDeliveryEngine {
5413
5565
  }
5414
5566
  else if (method === 'group.v2.ack' || method === 'group.ack_messages') {
5415
5567
  const canonicalNs = this.resolvePendingGroupInlineAckNamespace(ns);
5416
- const pending = this.pendingGroupInlineAcks?.get(canonicalNs);
5417
- if (pending && !pending.committed && next >= pending.seq)
5418
- next = pending.seq - 1;
5568
+ for (const pending of this.pendingGroupInlineAcks?.get(canonicalNs)?.values() ?? []) {
5569
+ if (!pending.committed && next >= pending.seq)
5570
+ next = pending.seq - 1;
5571
+ }
5419
5572
  }
5420
5573
  }
5421
5574
  return next;