@agentunion/fastaun 0.5.21 → 0.5.22
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.
- package/CHANGELOG.md +58 -1
- package/_packed_docs/CHANGELOG.md +58 -1
- package/_packed_docs/INDEX.md +465 -453
- package/_packed_docs/KITE_DOCS_GUIDE.md +168 -164
- package/_packed_docs/aun/345/210/206/345/270/203/345/274/217/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +307 -67
- package/_packed_docs/aun/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +41 -15
- package/_packed_docs/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +5 -5
- package/_packed_docs/protocol/11-Storage-/345/255/220/345/215/217/350/256/256.md +30 -8
- package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2112.1.md +669 -666
- package/_packed_docs/sdk/0.5.22-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +212 -0
- package/_packed_docs/sdk/0.5.22-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +199 -0
- package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +16 -0
- package/_packed_docs/sdk/09-group-rpc-manual.md +10 -0
- package/_packed_docs/sdk/09-storage-rpc-manual.md +64 -7
- package/_packed_docs/sdk/09-/345/207/255/350/257/201/346/234/215/345/212/241RPC/346/211/213/345/206/214.md +3 -1
- package/_packed_docs/sdk/09-/347/273/204/347/273/207/346/234/215/345/212/241RPC/346/211/213/345/206/214.md +2 -0
- package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +138 -134
- package/_packed_docs/sdk/INDEX.md +255 -239
- package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/2272.1.md +939 -930
- 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 +312 -363
- 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 +322 -553
- package/_packed_docs/sdk/Ownership2.1-TDD/345/210/206/351/230/266/346/256/265/345/256/236/346/226/275/350/256/241/345/210/222.md +4 -4
- package/_packed_docs/sdk/Ownership2.1/350/257/246/347/273/206/350/256/276/350/256/241.md +484 -480
- package/_packed_docs/sdk//345/207/255/350/257/201/344/270/216/347/273/204/347/273/207/346/234/215/345/212/241SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +190 -97
- package/_packed_docs/sdk//345/207/255/350/257/201/344/270/216/347/273/204/347/273/207/346/234/215/345/212/241SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +158 -127
- package/dist/agent-md.d.ts +1 -1
- package/dist/agent-md.js +11 -4
- package/dist/agent-md.js.map +1 -1
- package/dist/auth.d.ts +1 -0
- package/dist/auth.js +18 -2
- package/dist/auth.js.map +1 -1
- package/dist/client/delivery.d.ts +28 -0
- package/dist/client/delivery.js +399 -44
- package/dist/client/delivery.js.map +1 -1
- package/dist/client/group-state.js +3 -0
- package/dist/client/group-state.js.map +1 -1
- package/dist/client/rpc-pipeline.js +28 -2
- package/dist/client/rpc-pipeline.js.map +1 -1
- package/dist/client/v2-e2ee.js +109 -39
- package/dist/client/v2-e2ee.js.map +1 -1
- package/dist/client.js +38 -10
- package/dist/client.js.map +1 -1
- package/dist/group-fs.d.ts +1 -0
- package/dist/group-fs.js +37 -19
- package/dist/group-fs.js.map +1 -1
- package/dist/org-protocol.d.ts +1 -0
- package/dist/org-protocol.js +54 -7
- package/dist/org-protocol.js.map +1 -1
- package/dist/ownership-v21.d.ts +21 -3
- package/dist/ownership-v21.js +135 -37
- package/dist/ownership-v21.js.map +1 -1
- package/dist/ownership-workflow.d.ts +7 -2
- package/dist/ownership-workflow.js +173 -17
- package/dist/ownership-workflow.js.map +1 -1
- package/dist/storage/lowlevel.d.ts +6 -0
- package/dist/storage/lowlevel.js +24 -7
- package/dist/storage/lowlevel.js.map +1 -1
- package/dist/storage/read-cache.d.ts +70 -0
- package/dist/storage/read-cache.js +625 -0
- package/dist/storage/read-cache.js.map +1 -0
- package/dist/storage/vfs.d.ts +2 -0
- package/dist/storage/vfs.js +50 -15
- package/dist/storage/vfs.js.map +1 -1
- package/dist/tools/cross-sdk-agent.d.ts +21 -1
- package/dist/tools/cross-sdk-agent.js +301 -19
- package/dist/tools/cross-sdk-agent.js.map +1 -1
- package/dist/transport.js +6 -2
- package/dist/transport.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/client/delivery.js
CHANGED
|
@@ -2,6 +2,7 @@ import { _INTERNAL_MESSAGE_FLOW_LIMITS, slotIsolationKey } from '../config.js';
|
|
|
2
2
|
import { parseFileFrontmatter } from '../agent-md-schema.js';
|
|
3
3
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
4
4
|
import { trafficLogContext, withLogContext } from '../logger.js';
|
|
5
|
+
import { normalizeGroupAid } from '../group-id.js';
|
|
5
6
|
import { ConnectionState, isJsonObject } from '../types.js';
|
|
6
7
|
import { validateGroupAIDFormat } from '../validators.js';
|
|
7
8
|
import { hasGroupMentionModeField, normalizeGroupMentionMode } from './mention-mode.js';
|
|
@@ -12,6 +13,24 @@ const GROUP_RECALL_SEEN_LIMIT = 10_000;
|
|
|
12
13
|
const VOLATILE_SEEN_LIMIT = 10_000;
|
|
13
14
|
const SEQ_TRACKER_PERSIST_FLUSH_DELAY_MS = 200;
|
|
14
15
|
const BACKGROUND_PULL_RETRY_DELAY_MS = 250;
|
|
16
|
+
function isTerminalGroupPullError(error) {
|
|
17
|
+
if (!error || typeof error !== 'object')
|
|
18
|
+
return false;
|
|
19
|
+
const failure = error;
|
|
20
|
+
if (typeof failure.data?.retryable === 'boolean')
|
|
21
|
+
return !failure.data.retryable;
|
|
22
|
+
if (failure.retryable === true)
|
|
23
|
+
return false;
|
|
24
|
+
const message = String(failure.message ?? '').trim().toLowerCase();
|
|
25
|
+
if (['timeout', 'deadline exceeded', 'gateway service degraded', 'certificate not loaded',
|
|
26
|
+
'long_connection_already_exists', 'long connection already exists'].some((text) => message.includes(text)))
|
|
27
|
+
return false;
|
|
28
|
+
return [-32001, -32003, -32004, -32600, -32601, -32602].includes(failure.code ?? 0)
|
|
29
|
+
|| (typeof failure.code === 'number' && failure.code >= -33009 && failure.code <= -33001)
|
|
30
|
+
|| ['banned members', 'blocked members', 'member is banned', 'member is blocked',
|
|
31
|
+
'group is not active', 'group is dissolved', 'group is suspended',
|
|
32
|
+
'group not found', 'member not found', 'not a member'].some((text) => message.includes(text));
|
|
33
|
+
}
|
|
15
34
|
const DELIVERY_CHANGED_MESSAGE_EVENTS = new Set([
|
|
16
35
|
'message.received',
|
|
17
36
|
'group.message_created',
|
|
@@ -115,8 +134,12 @@ export class MessageDeliveryEngine {
|
|
|
115
134
|
seqTrackerNamespaceGenerations = new Map();
|
|
116
135
|
peerRecoveryTasks = new Map();
|
|
117
136
|
peerRecoveryNeeds = new Map();
|
|
137
|
+
peerForwardNeeds = new Map();
|
|
138
|
+
peerPushVersions = new Map();
|
|
139
|
+
peerCatalogVersion = null;
|
|
118
140
|
peerPullHints = new Map();
|
|
119
141
|
peerPullConfirmedThrough = new Map();
|
|
142
|
+
peerServerConfirmedAcks = new Map();
|
|
120
143
|
peerVolatileCursors = new Map();
|
|
121
144
|
settledDeliveries = new Map();
|
|
122
145
|
deliveryInflight = new Map();
|
|
@@ -124,9 +147,100 @@ export class MessageDeliveryEngine {
|
|
|
124
147
|
applicationClaims = new Map();
|
|
125
148
|
volatileCompletions = new Map();
|
|
126
149
|
peerPageChains = new Map();
|
|
150
|
+
automaticPullFailures = new Map();
|
|
151
|
+
terminalGroupPulls = new Map();
|
|
152
|
+
automaticPullOperations = new WeakSet();
|
|
153
|
+
automaticPullContext = new AsyncLocalStorage();
|
|
154
|
+
automaticPullAbort = new AbortController();
|
|
127
155
|
constructor(runtime) {
|
|
128
156
|
this.runtime = runtime;
|
|
129
157
|
}
|
|
158
|
+
resetAutomaticPullBackoff() {
|
|
159
|
+
this.automaticPullAbort.abort();
|
|
160
|
+
this.automaticPullAbort = new AbortController();
|
|
161
|
+
this.automaticPullFailures.clear();
|
|
162
|
+
this.terminalGroupPulls.clear();
|
|
163
|
+
}
|
|
164
|
+
groupPullNamespace(namespace) {
|
|
165
|
+
const groupId = namespace.startsWith('group_event:') ? namespace.slice('group_event:'.length)
|
|
166
|
+
: namespace.startsWith('group:') ? namespace.slice('group:'.length) : '';
|
|
167
|
+
return groupId ? `group:${normalizeGroupAid(groupId)}` : '';
|
|
168
|
+
}
|
|
169
|
+
automaticGroupPullStopped(namespace) {
|
|
170
|
+
return this.terminalGroupPulls.has(this.groupPullNamespace(namespace));
|
|
171
|
+
}
|
|
172
|
+
observeSuccessfulGroupPull(namespace) {
|
|
173
|
+
if (!this.automaticPullContext.getStore())
|
|
174
|
+
this.terminalGroupPulls.delete(this.groupPullNamespace(namespace));
|
|
175
|
+
}
|
|
176
|
+
stopAutomaticGroupPull(namespace, error) {
|
|
177
|
+
const canonical = this.groupPullNamespace(namespace);
|
|
178
|
+
if (!canonical)
|
|
179
|
+
return;
|
|
180
|
+
this.terminalGroupPulls.set(canonical, error);
|
|
181
|
+
for (const pending of [this.automaticPullFailures, this.backgroundPullNeeds, this.pendingGroupPullUpper, this.onlineUnreadHintTargets]) {
|
|
182
|
+
for (const ns of pending?.keys() ?? []) {
|
|
183
|
+
if (this.groupPullNamespace(ns) === canonical)
|
|
184
|
+
pending.delete(ns);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
automaticPullRetryAt(namespace, operation) {
|
|
189
|
+
return this.automaticPullOperations.has(operation) ? this.automaticPullFailures.get(namespace)?.retryAt ?? 0 : 0;
|
|
190
|
+
}
|
|
191
|
+
runPeerPullAttempt(namespace, operation, schedule) {
|
|
192
|
+
return this.automaticPullContext.getStore() ? this.runAutomaticPull(namespace, operation, schedule) : schedule(operation);
|
|
193
|
+
}
|
|
194
|
+
async runAutomaticPull(namespace, operation, schedule) {
|
|
195
|
+
this.ensurePeerContext();
|
|
196
|
+
if (this.automaticGroupPullStopped(namespace))
|
|
197
|
+
throw this.terminalGroupPulls.get(this.groupPullNamespace(namespace));
|
|
198
|
+
const nested = this.automaticPullContext.getStore() === namespace;
|
|
199
|
+
const generation = this.captureInlineAckGeneration();
|
|
200
|
+
const signal = this.automaticPullAbort.signal;
|
|
201
|
+
const client = this.runtime.client;
|
|
202
|
+
while ((this.automaticPullFailures.get(namespace)?.retryAt ?? 0) > Date.now()) {
|
|
203
|
+
const delay = this.automaticPullFailures.get(namespace).retryAt - Date.now();
|
|
204
|
+
await new Promise((resolve, reject) => {
|
|
205
|
+
const abort = () => {
|
|
206
|
+
clearTimeout(timer);
|
|
207
|
+
reject(new Error('pull invalidated'));
|
|
208
|
+
};
|
|
209
|
+
const timer = setTimeout(() => { signal.removeEventListener('abort', abort); resolve(); }, delay);
|
|
210
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
if (signal.aborted || generation !== this.captureInlineAckGeneration() || client._closing || client.state !== ConnectionState.READY) {
|
|
214
|
+
throw new Error('pull invalidated');
|
|
215
|
+
}
|
|
216
|
+
const attempt = async () => {
|
|
217
|
+
if (this.automaticGroupPullStopped(namespace))
|
|
218
|
+
throw this.terminalGroupPulls.get(this.groupPullNamespace(namespace));
|
|
219
|
+
const previousFailure = this.automaticPullFailures.get(namespace);
|
|
220
|
+
try {
|
|
221
|
+
const result = await this.automaticPullContext.run(namespace, operation);
|
|
222
|
+
if (!nested && !signal.aborted && generation === this.captureInlineAckGeneration())
|
|
223
|
+
this.automaticPullFailures.delete(namespace);
|
|
224
|
+
return result;
|
|
225
|
+
}
|
|
226
|
+
catch (error) {
|
|
227
|
+
if (!signal.aborted && generation === this.captureInlineAckGeneration()
|
|
228
|
+
&& this.groupPullNamespace(namespace) && isTerminalGroupPullError(error)) {
|
|
229
|
+
this.stopAutomaticGroupPull(namespace, error);
|
|
230
|
+
throw error;
|
|
231
|
+
}
|
|
232
|
+
const currentFailure = this.automaticPullFailures.get(namespace);
|
|
233
|
+
if ((!currentFailure || currentFailure === previousFailure)
|
|
234
|
+
&& !signal.aborted && generation === this.captureInlineAckGeneration() && !client._closing && client.state === ConnectionState.READY) {
|
|
235
|
+
const delay = Math.min(30_000, (this.automaticPullFailures.get(namespace)?.delay ?? 500) * 2);
|
|
236
|
+
this.automaticPullFailures.set(namespace, { delay, retryAt: Date.now() + delay });
|
|
237
|
+
}
|
|
238
|
+
throw error;
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
this.automaticPullOperations.add(attempt);
|
|
242
|
+
return await (schedule ? schedule(attempt) : attempt());
|
|
243
|
+
}
|
|
130
244
|
currentApplicationPage() {
|
|
131
245
|
const page = this.applicationPage.getStore();
|
|
132
246
|
return page?.collecting && page.batching ? page : undefined;
|
|
@@ -143,6 +257,10 @@ export class MessageDeliveryEngine {
|
|
|
143
257
|
snapshotApplicationHandlers() {
|
|
144
258
|
return this.runtime.client._dispatcher._snapshotApplicationHandlers?.(['message.batch', 'group.message_batch', 'group.event_batch']);
|
|
145
259
|
}
|
|
260
|
+
hasP2pApplicationSubscription() {
|
|
261
|
+
const handlers = this.runtime.client._dispatcher._snapshotApplicationHandlers?.(['message.received', 'message.recalled', 'message.batch']);
|
|
262
|
+
return !handlers || [...handlers.values()].some((selected) => selected.length > 0);
|
|
263
|
+
}
|
|
146
264
|
async runApplicationPage(namespace, source, operation, reservation, snapshot) {
|
|
147
265
|
const dispatcher = this.runtime.client._dispatcher;
|
|
148
266
|
if (typeof dispatcher._snapshotApplicationHandlers !== 'function')
|
|
@@ -288,6 +406,7 @@ export class MessageDeliveryEngine {
|
|
|
288
406
|
const client = this.runtime.client;
|
|
289
407
|
const context = JSON.stringify([client._aid ?? '', client._deviceId ?? '', slotIsolationKey(client._slotId)]);
|
|
290
408
|
if (context !== this.peerContext || client._seqTracker !== this.peerTracker) {
|
|
409
|
+
this.resetAutomaticPullBackoff();
|
|
291
410
|
this.peerContext = context;
|
|
292
411
|
this.peerTracker = client._seqTracker;
|
|
293
412
|
this.peerContextGeneration += 1;
|
|
@@ -297,8 +416,12 @@ export class MessageDeliveryEngine {
|
|
|
297
416
|
this.seqTrackerNamespaceGenerations.clear();
|
|
298
417
|
this.peerVolatileCursors.clear();
|
|
299
418
|
this.peerRecoveryNeeds.clear();
|
|
419
|
+
this.peerForwardNeeds.clear();
|
|
420
|
+
this.peerPushVersions.clear();
|
|
421
|
+
this.peerCatalogVersion = null;
|
|
300
422
|
this.peerPullHints.clear();
|
|
301
423
|
this.peerPullConfirmedThrough.clear();
|
|
424
|
+
this.peerServerConfirmedAcks.clear();
|
|
302
425
|
this.peerRecoveryTasks.clear();
|
|
303
426
|
this.settledDeliveries.clear();
|
|
304
427
|
this.deliveryInflight.clear();
|
|
@@ -373,7 +496,14 @@ export class MessageDeliveryEngine {
|
|
|
373
496
|
}
|
|
374
497
|
peerCommittedAck(namespace) {
|
|
375
498
|
this.ensurePeerContext();
|
|
376
|
-
|
|
499
|
+
const committed = this.committedSeqTrackerStates.get(namespace);
|
|
500
|
+
return committed ? Math.min(committed.ack, this.syncState(namespace).ack) : 0;
|
|
501
|
+
}
|
|
502
|
+
observePeerServerAck(namespace, sequence) {
|
|
503
|
+
this.ensurePeerContext();
|
|
504
|
+
if (!namespace.startsWith('p2p.peer.v1/') || !Number.isSafeInteger(sequence) || sequence < 0)
|
|
505
|
+
return;
|
|
506
|
+
this.peerServerConfirmedAcks.set(namespace, Math.max(this.peerServerConfirmedAcks.get(namespace) ?? 0, sequence));
|
|
377
507
|
}
|
|
378
508
|
rememberCommittedSeqState(namespace) {
|
|
379
509
|
this.ensurePeerContext();
|
|
@@ -461,7 +591,16 @@ export class MessageDeliveryEngine {
|
|
|
461
591
|
}
|
|
462
592
|
for (const row of rows) {
|
|
463
593
|
const ns = this.peerNamespace(String(row.sender_aid));
|
|
594
|
+
if (row.volatile_has_more === true || (nonNegativeSafeSequenceHint(row.head_seq) ?? 0) > Math.max(this.syncState(ns).head, client._seqTracker.getMaxSeenSeq(ns))) {
|
|
595
|
+
this.peerPushVersions.set(ns, {});
|
|
596
|
+
}
|
|
597
|
+
const head = nonNegativeSafeSequenceHint(row.head_seq);
|
|
598
|
+
if (head !== null)
|
|
599
|
+
client._seqTracker.updateMaxSeen(ns, head);
|
|
464
600
|
const hint = nonNegativeSafeSequenceHint(row.next_pending_seq);
|
|
601
|
+
const serverAck = nonNegativeSafeSequenceHint(row.server_ack_seq);
|
|
602
|
+
if (serverAck !== null)
|
|
603
|
+
this.observePeerServerAck(ns, serverAck);
|
|
465
604
|
const confirmed = Math.max(this.peerPullConfirmedThrough.get(ns) ?? 0, nonNegativeSafeSequenceHint(row.server_ack_seq) ?? nonNegativeSafeSequenceHint(row.initial_after_seq) ?? 0);
|
|
466
605
|
this.peerPullConfirmedThrough.set(ns, confirmed);
|
|
467
606
|
const current = this.peerPullHints.get(ns);
|
|
@@ -478,8 +617,40 @@ export class MessageDeliveryEngine {
|
|
|
478
617
|
client._dispatcher.enqueue('message.peers_snapshot', snapshot, () => context === this.ensurePeerContext() && generation === this.captureInlineAckGeneration());
|
|
479
618
|
return snapshot;
|
|
480
619
|
}
|
|
620
|
+
capturePeerTailVersion(senderAid) {
|
|
621
|
+
const context = this.ensurePeerContext();
|
|
622
|
+
return { context, push: this.peerPushVersions.get(this.peerNamespace(senderAid)), catalog: this.peerCatalogVersion };
|
|
623
|
+
}
|
|
624
|
+
isPeerTailCurrent(senderAid, version, committedAck) {
|
|
625
|
+
const namespace = this.peerNamespace(senderAid);
|
|
626
|
+
return version.context === this.ensurePeerContext() && this.peerPushVersions.get(namespace) === version.push
|
|
627
|
+
&& this.peerCatalogVersion === version.catalog && this.runtime.client._seqTracker.getMaxSeenSeq(namespace) <= committedAck
|
|
628
|
+
&& this.peerPullPriority(namespace) <= committedAck && this.syncState(namespace).head <= committedAck;
|
|
629
|
+
}
|
|
630
|
+
shouldTailPeer(namespace, head, limit, expectedForwardNeed) {
|
|
631
|
+
const state = this.syncState(namespace);
|
|
632
|
+
const knownHead = Math.max(head, state.head, this.runtime.client._seqTracker.getMaxSeenSeq(namespace), this.peerPullPriority(namespace));
|
|
633
|
+
return this.peerOwnType() === 'human' && (!this.peerForwardNeeds.has(namespace) || this.peerForwardNeeds.get(namespace) === expectedForwardNeed) && state.ack >= state.head
|
|
634
|
+
&& Number.isSafeInteger(limit) && limit > 0 && knownHead - this.peerCommittedAck(namespace) > limit;
|
|
635
|
+
}
|
|
636
|
+
canClosePeerTail(namespace, tail, pushVersion, catalogVersion, expectedNeed) {
|
|
637
|
+
const head = nonNegativeSafeSequenceHint(tail.head_seq);
|
|
638
|
+
const committedAck = this.peerCommittedAck(namespace);
|
|
639
|
+
const need = this.peerRecoveryNeeds.get(this.peerSender(namespace));
|
|
640
|
+
return tail.complete === true && tail.volatile_has_more === false && tail.pending_ack_seq === 0
|
|
641
|
+
&& head !== null && committedAck >= head && this.syncState(namespace).head <= committedAck
|
|
642
|
+
&& (this.peerServerConfirmedAcks.get(namespace) ?? 0) >= committedAck
|
|
643
|
+
&& this.runtime.client._seqTracker.getMaxSeenSeq(namespace) <= committedAck
|
|
644
|
+
&& this.peerPullPriority(namespace) <= committedAck
|
|
645
|
+
&& this.peerPushVersions.get(namespace) === pushVersion
|
|
646
|
+
&& this.peerCatalogVersion === catalogVersion
|
|
647
|
+
&& (!need || (need === expectedNeed && !need.pending && need.head <= committedAck));
|
|
648
|
+
}
|
|
481
649
|
async pullPeers(params = {}, background = false) {
|
|
482
650
|
const client = this.runtime.client;
|
|
651
|
+
const context = this.ensurePeerContext();
|
|
652
|
+
const generation = this.captureInlineAckGeneration();
|
|
653
|
+
const isCurrent = () => context === this.ensurePeerContext() && generation === this.captureInlineAckGeneration();
|
|
483
654
|
const sender = String(params.sender_aid ?? '').trim();
|
|
484
655
|
if (!sender && ('after_seq' in params || 'after_volatile_cursor' in params || 'window_mode' in params || params.force === true))
|
|
485
656
|
throw new Error('explicit peer is required for after_seq, after_volatile_cursor, window_mode or force');
|
|
@@ -488,30 +659,88 @@ export class MessageDeliveryEngine {
|
|
|
488
659
|
const budget = { remaining: Math.max(1, Math.trunc(Number(params.max_pages ?? 100) || 100)) };
|
|
489
660
|
const results = await Promise.all(rows.map(async (row) => {
|
|
490
661
|
const peer = String(row.sender_aid);
|
|
662
|
+
const peerPullVersion = this.capturePeerTailVersion(peer);
|
|
491
663
|
const ns = await this.initializePeer(peer);
|
|
492
664
|
try {
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
665
|
+
if (!isCurrent())
|
|
666
|
+
throw new Error('pull invalidated');
|
|
667
|
+
let forwardNeed = this.peerForwardNeeds.get(ns);
|
|
668
|
+
if (!params.window_mode && params.force !== true && this.shouldTailPeer(ns, Number(row.head_seq ?? 0), Number(params.limit ?? _INTERNAL_MESSAGE_FLOW_LIMITS.pullPageSize)) && budget.remaining > 0) {
|
|
669
|
+
forwardNeed = {};
|
|
670
|
+
this.peerForwardNeeds.set(ns, forwardNeed);
|
|
671
|
+
const pushVersion = this.peerPushVersions.get(ns);
|
|
672
|
+
const catalogVersion = this.peerCatalogVersion;
|
|
673
|
+
const autoTail = { head: Number(row.head_seq ?? 0), forwardNeed, forward: false };
|
|
674
|
+
const tail = await client._v2E2EE.pullPeerV2(peer, { ...params, window_mode: 'tail', budget, background, peerPullVersion, autoTail });
|
|
675
|
+
if (!isCurrent())
|
|
676
|
+
throw new Error('pull invalidated');
|
|
677
|
+
if (autoTail.forward) {
|
|
678
|
+
const notified = this.peerPushVersions.get(ns) !== pushVersion || this.peerCatalogVersion !== catalogVersion;
|
|
679
|
+
if (notified)
|
|
680
|
+
this.peerForwardNeeds.set(ns, {});
|
|
681
|
+
else if (tail.complete === true && this.peerForwardNeeds.get(ns) === forwardNeed)
|
|
682
|
+
this.peerForwardNeeds.delete(ns);
|
|
683
|
+
return { ...tail, complete: tail.complete === true && !notified };
|
|
684
|
+
}
|
|
685
|
+
if (this.canClosePeerTail(ns, tail, pushVersion, catalogVersion)) {
|
|
686
|
+
if (this.peerForwardNeeds.get(ns) === forwardNeed)
|
|
687
|
+
this.peerForwardNeeds.delete(ns);
|
|
688
|
+
return tail;
|
|
689
|
+
}
|
|
496
690
|
if (!budget.remaining) {
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
return { ...tail, complete: tail.complete && !row.volatile_has_more };
|
|
691
|
+
this.schedulePeerRecovery(peer, Number(tail.head_seq ?? 0), true);
|
|
692
|
+
return { ...tail, complete: false };
|
|
500
693
|
}
|
|
694
|
+
forwardNeed = this.peerForwardNeeds.get(ns);
|
|
695
|
+
const forwardPushVersion = this.peerPushVersions.get(ns);
|
|
696
|
+
const forwardCatalogVersion = this.peerCatalogVersion;
|
|
501
697
|
const forward = await client._v2E2EE.pullPeerV2(peer, { ...params, budget, background });
|
|
698
|
+
if (!isCurrent())
|
|
699
|
+
throw new Error('pull invalidated');
|
|
700
|
+
const notified = this.peerPushVersions.get(ns) !== forwardPushVersion || this.peerCatalogVersion !== forwardCatalogVersion;
|
|
701
|
+
if (notified)
|
|
702
|
+
this.peerForwardNeeds.set(ns, {});
|
|
703
|
+
else if (forward.complete === true && this.peerForwardNeeds.get(ns) === forwardNeed)
|
|
704
|
+
this.peerForwardNeeds.delete(ns);
|
|
502
705
|
const messages = [...new Map([...tail.messages, ...forward.messages].map((message) => [message.seq ?? message.volatile_cursor, message])).values()];
|
|
503
|
-
return { ...forward, messages };
|
|
706
|
+
return { ...forward, messages, complete: forward.complete === true && !notified };
|
|
504
707
|
}
|
|
505
|
-
|
|
708
|
+
const pushVersion = this.peerPushVersions.get(ns);
|
|
709
|
+
const catalogVersion = this.peerCatalogVersion;
|
|
710
|
+
const result = await client._v2E2EE.pullPeerV2(peer, { ...params, budget, background, peerPullVersion });
|
|
711
|
+
if (!isCurrent())
|
|
712
|
+
throw new Error('pull invalidated');
|
|
713
|
+
if (params.window_mode === 'tail') {
|
|
714
|
+
const complete = this.canClosePeerTail(ns, result, pushVersion, catalogVersion);
|
|
715
|
+
if (!complete) {
|
|
716
|
+
this.peerForwardNeeds.set(ns, {});
|
|
717
|
+
this.schedulePeerRecovery(peer, Number(result.head_seq ?? 0), true);
|
|
718
|
+
}
|
|
719
|
+
else if (this.peerForwardNeeds.get(ns) === forwardNeed)
|
|
720
|
+
this.peerForwardNeeds.delete(ns);
|
|
721
|
+
return { ...result, complete };
|
|
722
|
+
}
|
|
723
|
+
if (params.force !== true && (this.peerPushVersions.get(ns) !== pushVersion || this.peerCatalogVersion !== catalogVersion)) {
|
|
724
|
+
this.peerForwardNeeds.set(ns, {});
|
|
725
|
+
return { ...result, complete: false };
|
|
726
|
+
}
|
|
727
|
+
if (params.force !== true && result.complete === true && this.peerForwardNeeds.get(ns) === forwardNeed)
|
|
728
|
+
this.peerForwardNeeds.delete(ns);
|
|
729
|
+
return result;
|
|
506
730
|
}
|
|
507
731
|
catch (error) {
|
|
508
|
-
|
|
509
|
-
|
|
732
|
+
if (isCurrent())
|
|
733
|
+
this.schedulePeerRecovery(peer, Number(row.head_seq ?? 0), Boolean(row.volatile_has_more));
|
|
734
|
+
return { sender_aid: peer, messages: [], complete: false, ack_seq: isCurrent() ? this.syncState(ns).ack : 0, error: error instanceof Error ? error.message : String(error) };
|
|
510
735
|
}
|
|
511
736
|
}));
|
|
512
737
|
return { cursor_scope: 'peer', peers: results, messages: results.flatMap((row) => row.messages), partial: results.some((row) => !row.complete) };
|
|
513
738
|
}
|
|
514
739
|
refreshPendingPeers() {
|
|
740
|
+
this.ensurePeerContext();
|
|
741
|
+
this.peerCatalogVersion = {};
|
|
742
|
+
if (!this.hasP2pApplicationSubscription())
|
|
743
|
+
return;
|
|
515
744
|
const client = this.runtime.client;
|
|
516
745
|
if (client._sessionOptions?.background_sync === false || !client._aid || client._closing || client.state !== ConnectionState.READY)
|
|
517
746
|
return;
|
|
@@ -521,9 +750,13 @@ export class MessageDeliveryEngine {
|
|
|
521
750
|
return;
|
|
522
751
|
const generation = this.captureInlineAckGeneration();
|
|
523
752
|
const task = (async () => {
|
|
524
|
-
while (this.peerCatalogDirty && generation === this.captureInlineAckGeneration()) {
|
|
753
|
+
while (this.peerCatalogDirty && generation === this.captureInlineAckGeneration() && this.hasP2pApplicationSubscription()) {
|
|
525
754
|
this.peerCatalogDirty = false;
|
|
526
|
-
const result = await this.peers({}, true);
|
|
755
|
+
const result = await this.runAutomaticPull('message.peers', () => this.peers({}, true));
|
|
756
|
+
if (!this.hasP2pApplicationSubscription()) {
|
|
757
|
+
this.peerCatalogDirty = true;
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
527
760
|
const rows = [...result.peers];
|
|
528
761
|
const hint = (row) => this.peerPullPriority(this.peerNamespace(String(row.sender_aid)));
|
|
529
762
|
rows.sort((left, right) => hint(left) - hint(right));
|
|
@@ -543,9 +776,29 @@ export class MessageDeliveryEngine {
|
|
|
543
776
|
}
|
|
544
777
|
schedulePeerRecovery(sender, head = 0, volatile = false) {
|
|
545
778
|
const client = this.runtime.client;
|
|
546
|
-
if (
|
|
779
|
+
if (!sender || client._closing)
|
|
547
780
|
return;
|
|
548
781
|
this.ensurePeerContext();
|
|
782
|
+
if (Number.isSafeInteger(head) && head > 0 && !volatile) {
|
|
783
|
+
const namespace = this.peerNamespace(sender);
|
|
784
|
+
const committedAck = this.peerCommittedAck(namespace);
|
|
785
|
+
const serverAck = this.peerServerConfirmedAcks.get(namespace) ?? 0;
|
|
786
|
+
if (head <= committedAck && serverAck >= committedAck) {
|
|
787
|
+
const existing = this.peerRecoveryNeeds.get(sender);
|
|
788
|
+
if (!existing || this.peerRecoveryTasks.has(sender))
|
|
789
|
+
return;
|
|
790
|
+
if (Number.isSafeInteger(existing.head) && existing.head > 0 && !existing.volatile && existing.head <= committedAck) {
|
|
791
|
+
this.peerRecoveryNeeds.delete(sender);
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
const namespace = this.peerNamespace(sender);
|
|
797
|
+
this.peerPushVersions.set(namespace, {});
|
|
798
|
+
if (!this.hasP2pApplicationSubscription() || client._sessionOptions?.background_sync === false) {
|
|
799
|
+
this.peerForwardNeeds.set(namespace, {});
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
549
802
|
const need = this.peerRecoveryNeeds.get(sender) ?? { head: 0, volatile: false, pending: false };
|
|
550
803
|
need.head = Math.max(need.head, head);
|
|
551
804
|
need.volatile ||= volatile;
|
|
@@ -555,15 +808,67 @@ export class MessageDeliveryEngine {
|
|
|
555
808
|
return;
|
|
556
809
|
const generation = this.captureInlineAckGeneration();
|
|
557
810
|
const task = (async () => {
|
|
558
|
-
await this.initializePeer(sender);
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
811
|
+
await this.runAutomaticPull(`peer-init:${sender}`, () => this.initializePeer(sender));
|
|
812
|
+
if (!this.hasP2pApplicationSubscription())
|
|
813
|
+
return;
|
|
814
|
+
const namespace = this.peerNamespace(sender);
|
|
815
|
+
if (this.shouldTailPeer(namespace, need.head, _INTERNAL_MESSAGE_FLOW_LIMITS.pullPageSize)) {
|
|
816
|
+
const forwardNeed = {};
|
|
817
|
+
this.peerForwardNeeds.set(namespace, forwardNeed);
|
|
818
|
+
need.pending = false;
|
|
819
|
+
const pushVersion = this.peerPushVersions.get(namespace);
|
|
820
|
+
const catalogVersion = this.peerCatalogVersion;
|
|
821
|
+
const autoTail = { head: need.head, forwardNeed, forward: false };
|
|
822
|
+
const tail = await this.runAutomaticPull(namespace, async () => {
|
|
823
|
+
const beforeAck = this.peerCommittedAck(namespace);
|
|
824
|
+
const beforeVolatile = this.peerVolatilePullCursor(sender);
|
|
825
|
+
const result = await client._v2E2EE.pullPeerV2(sender, { window_mode: 'tail', background: true, retainMessages: false, budget: { remaining: 1 }, autoTail });
|
|
826
|
+
if (autoTail.forward && !result.complete && this.peerCommittedAck(namespace) <= beforeAck && this.peerVolatilePullCursor(sender) === beforeVolatile) {
|
|
827
|
+
throw new Error('peer recovery made no progress');
|
|
828
|
+
}
|
|
829
|
+
return result;
|
|
830
|
+
});
|
|
831
|
+
if (generation !== this.captureInlineAckGeneration() || this.peerRecoveryNeeds.get(sender) !== need)
|
|
832
|
+
return;
|
|
833
|
+
if (autoTail.forward) {
|
|
834
|
+
if (tail.complete && this.peerForwardNeeds.get(namespace) === forwardNeed
|
|
835
|
+
&& this.peerPushVersions.get(namespace) === pushVersion && this.peerCatalogVersion === catalogVersion)
|
|
836
|
+
this.peerForwardNeeds.delete(namespace);
|
|
837
|
+
if (tail.complete && Number(tail.ack_seq) >= need.head && !need.pending
|
|
838
|
+
&& this.peerPushVersions.get(namespace) === pushVersion && this.peerCatalogVersion === catalogVersion) {
|
|
839
|
+
this.peerRecoveryNeeds.delete(sender);
|
|
840
|
+
return;
|
|
841
|
+
}
|
|
842
|
+
await new Promise((resolve) => setTimeout(resolve, BACKGROUND_PULL_RETRY_DELAY_MS));
|
|
843
|
+
}
|
|
844
|
+
else if (this.canClosePeerTail(namespace, tail, pushVersion, catalogVersion, need)) {
|
|
845
|
+
this.peerRecoveryNeeds.delete(sender);
|
|
846
|
+
if (this.peerForwardNeeds.get(namespace) === forwardNeed)
|
|
847
|
+
this.peerForwardNeeds.delete(namespace);
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
562
850
|
}
|
|
563
|
-
while (generation === this.captureInlineAckGeneration() && this.peerRecoveryNeeds.has(sender)) {
|
|
851
|
+
while (generation === this.captureInlineAckGeneration() && this.peerRecoveryNeeds.has(sender) && this.hasP2pApplicationSubscription()) {
|
|
564
852
|
need.pending = false;
|
|
565
|
-
const
|
|
566
|
-
|
|
853
|
+
const forwardNeed = this.peerForwardNeeds.get(namespace);
|
|
854
|
+
const pushVersion = this.peerPushVersions.get(namespace);
|
|
855
|
+
const catalogVersion = this.peerCatalogVersion;
|
|
856
|
+
const result = await this.runAutomaticPull(namespace, async () => {
|
|
857
|
+
const beforeAck = this.peerCommittedAck(namespace);
|
|
858
|
+
const beforeVolatile = this.peerVolatilePullCursor(sender);
|
|
859
|
+
const result = await client._v2E2EE.pullPeerV2(sender, { background: true, retainMessages: false, budget: { remaining: 100 } });
|
|
860
|
+
if (!result.complete && this.peerCommittedAck(namespace) <= beforeAck && this.peerVolatilePullCursor(sender) === beforeVolatile) {
|
|
861
|
+
throw new Error('peer recovery made no progress');
|
|
862
|
+
}
|
|
863
|
+
return result;
|
|
864
|
+
});
|
|
865
|
+
if (generation !== this.captureInlineAckGeneration() || this.peerRecoveryNeeds.get(sender) !== need)
|
|
866
|
+
return;
|
|
867
|
+
if (result.complete && this.peerForwardNeeds.get(namespace) === forwardNeed
|
|
868
|
+
&& this.peerPushVersions.get(namespace) === pushVersion && this.peerCatalogVersion === catalogVersion)
|
|
869
|
+
this.peerForwardNeeds.delete(namespace);
|
|
870
|
+
if (result.complete && result.ack_seq >= need.head && !need.pending
|
|
871
|
+
&& this.peerPushVersions.get(namespace) === pushVersion && this.peerCatalogVersion === catalogVersion) {
|
|
567
872
|
this.peerRecoveryNeeds.delete(sender);
|
|
568
873
|
break;
|
|
569
874
|
}
|
|
@@ -979,6 +1284,7 @@ export class MessageDeliveryEngine {
|
|
|
979
1284
|
return processed.map((message) => this.publicVolatileMessage(message));
|
|
980
1285
|
}
|
|
981
1286
|
resetInlineAckState() {
|
|
1287
|
+
this.resetAutomaticPullBackoff();
|
|
982
1288
|
this.syncStopped('aborted');
|
|
983
1289
|
this.deliveryChangedGeneration += 1;
|
|
984
1290
|
this.pendingDeliveryChanges = null;
|
|
@@ -1003,8 +1309,12 @@ export class MessageDeliveryEngine {
|
|
|
1003
1309
|
this.peerCatalogTask = null;
|
|
1004
1310
|
this.peerCatalogDirty = false;
|
|
1005
1311
|
this.peerRecoveryNeeds.clear();
|
|
1312
|
+
this.peerForwardNeeds.clear();
|
|
1313
|
+
this.peerPushVersions.clear();
|
|
1314
|
+
this.peerCatalogVersion = null;
|
|
1006
1315
|
this.peerPullHints.clear();
|
|
1007
1316
|
this.peerPullConfirmedThrough.clear();
|
|
1317
|
+
this.peerServerConfirmedAcks.clear();
|
|
1008
1318
|
this.peerRecoveryTasks.clear();
|
|
1009
1319
|
}
|
|
1010
1320
|
deliveryChangedNamespace(event, payload) {
|
|
@@ -1295,6 +1605,8 @@ export class MessageDeliveryEngine {
|
|
|
1295
1605
|
this.schedulePendingGroupPullIfNeeded(ns, 'pull-gate-idle');
|
|
1296
1606
|
}
|
|
1297
1607
|
recordBackgroundPullNeed(ns, throughSeq = 0, probe = false) {
|
|
1608
|
+
if (this.automaticGroupPullStopped(ns))
|
|
1609
|
+
return;
|
|
1298
1610
|
const client = this.runtime.client;
|
|
1299
1611
|
if (client._sessionOptions?.background_sync === false || !client._aid
|
|
1300
1612
|
|| !(ns === `p2p:${client._aid}` || ns.startsWith('group:') || ns.startsWith('group_event:')))
|
|
@@ -1329,6 +1641,8 @@ export class MessageDeliveryEngine {
|
|
|
1329
1641
|
finishBackgroundPull(ns, generation, afterSeq, failed) {
|
|
1330
1642
|
if (generation !== this.captureInlineAckGeneration())
|
|
1331
1643
|
return;
|
|
1644
|
+
if (this.automaticGroupPullStopped(ns))
|
|
1645
|
+
return;
|
|
1332
1646
|
const need = this.backgroundPullNeeds.get(ns);
|
|
1333
1647
|
if (need) {
|
|
1334
1648
|
if (failed)
|
|
@@ -1362,6 +1676,12 @@ export class MessageDeliveryEngine {
|
|
|
1362
1676
|
}
|
|
1363
1677
|
let retryAt = Infinity;
|
|
1364
1678
|
for (const [ns, need] of this.backgroundPullNeeds) {
|
|
1679
|
+
if (this.automaticGroupPullStopped(ns)) {
|
|
1680
|
+
this.backgroundPullNeeds.delete(ns);
|
|
1681
|
+
continue;
|
|
1682
|
+
}
|
|
1683
|
+
if (ns.startsWith('p2p:') && !this.hasP2pApplicationSubscription())
|
|
1684
|
+
continue;
|
|
1365
1685
|
if (this.backgroundPullTasks.has(ns) || this.realtimeSyncing?.has(ns) || this.onlineUnreadHintTasks?.has(ns))
|
|
1366
1686
|
continue;
|
|
1367
1687
|
const eventScope = ns.startsWith('group_event:');
|
|
@@ -1428,6 +1748,8 @@ export class MessageDeliveryEngine {
|
|
|
1428
1748
|
client._safeAsync(this.fillP2pGap());
|
|
1429
1749
|
}
|
|
1430
1750
|
recordPendingGroupPull(ns, seq) {
|
|
1751
|
+
if (this.automaticGroupPullStopped(ns))
|
|
1752
|
+
return;
|
|
1431
1753
|
if (!ns || !Number.isSafeInteger(seq) || seq <= 0)
|
|
1432
1754
|
return;
|
|
1433
1755
|
const pending = this.pendingGroupPullUpper ?? new Map();
|
|
@@ -1435,6 +1757,8 @@ export class MessageDeliveryEngine {
|
|
|
1435
1757
|
pending.set(ns, Math.max(pending.get(ns) ?? 0, seq));
|
|
1436
1758
|
}
|
|
1437
1759
|
schedulePendingGroupPullIfNeeded(ns, reason) {
|
|
1760
|
+
if (this.automaticGroupPullStopped(ns))
|
|
1761
|
+
return false;
|
|
1438
1762
|
const pending = this.pendingGroupPullUpper;
|
|
1439
1763
|
if (!pending)
|
|
1440
1764
|
return false;
|
|
@@ -2580,13 +2904,15 @@ export class MessageDeliveryEngine {
|
|
|
2580
2904
|
};
|
|
2581
2905
|
if (client._rpcPipeline) {
|
|
2582
2906
|
const key = client._rpcPipeline.pullGateKeyForCall('group.pull', { group_id: groupId, after_seq: afterSeq, limit: 50 });
|
|
2583
|
-
await client._rpcPipeline.runPullNamespaceSerialized(key,
|
|
2907
|
+
await this.runAutomaticPull(ns, operation, (attempt) => client._rpcPipeline.runPullNamespaceSerialized(key, attempt, true));
|
|
2584
2908
|
}
|
|
2585
2909
|
else {
|
|
2586
|
-
await client._withBackgroundRpc(
|
|
2910
|
+
await this.runAutomaticPull(ns, operation, (attempt) => client._withBackgroundRpc(attempt));
|
|
2587
2911
|
}
|
|
2588
2912
|
}
|
|
2589
2913
|
async fillP2pGap() {
|
|
2914
|
+
if (!this.hasP2pApplicationSubscription())
|
|
2915
|
+
return;
|
|
2590
2916
|
const client = this.runtime.client;
|
|
2591
2917
|
if (client._p2pPeerPullEnabled) {
|
|
2592
2918
|
this.refreshPendingPeers();
|
|
@@ -2611,9 +2937,9 @@ export class MessageDeliveryEngine {
|
|
|
2611
2937
|
// Gap Fill 本身就是 Forward Pull,必须沿用协调器的完整事务:
|
|
2612
2938
|
// 原始页提交成功后由协调器负责 Piggyback/终端 ACK。
|
|
2613
2939
|
const operation = () => client._pullV2(afterSeq, 50, { gateLocked: true });
|
|
2614
|
-
const messages = client._rpcPipeline
|
|
2615
|
-
?
|
|
2616
|
-
:
|
|
2940
|
+
const messages = await this.runAutomaticPull(ns, operation, (attempt) => client._rpcPipeline
|
|
2941
|
+
? client._rpcPipeline.runPullSerialized(client._rpcPipeline.pullGateKeyForCall('message.pull', { after_seq: afterSeq, limit: 50 }), attempt, true)
|
|
2942
|
+
: client._withBackgroundRpc(attempt));
|
|
2617
2943
|
filled = messages.length;
|
|
2618
2944
|
this.prunePushedSeqs(ns);
|
|
2619
2945
|
client._clientLog.debug(`P2P message gap fill done: after_seq=${afterSeq}, filled=${filled}`);
|
|
@@ -2638,6 +2964,8 @@ export class MessageDeliveryEngine {
|
|
|
2638
2964
|
if (!groupId)
|
|
2639
2965
|
return;
|
|
2640
2966
|
const ns = `group:${groupId}`;
|
|
2967
|
+
if (this.automaticGroupPullStopped(ns))
|
|
2968
|
+
return;
|
|
2641
2969
|
const afterSeq = client._seqTracker.getContiguousSeq(ns);
|
|
2642
2970
|
const generation = this.captureInlineAckGeneration();
|
|
2643
2971
|
const dedupKey = `group_msg:${groupId}:${afterSeq}`;
|
|
@@ -2650,9 +2978,9 @@ export class MessageDeliveryEngine {
|
|
|
2650
2978
|
let failed = false;
|
|
2651
2979
|
try {
|
|
2652
2980
|
const operation = () => client._pullGroupV2(groupId, afterSeq, 50, { gateLocked: true, background: true });
|
|
2653
|
-
const messages = client._rpcPipeline
|
|
2654
|
-
?
|
|
2655
|
-
:
|
|
2981
|
+
const messages = await this.runAutomaticPull(ns, operation, (attempt) => client._rpcPipeline
|
|
2982
|
+
? client._rpcPipeline.runPullNamespaceSerialized(client._rpcPipeline.pullGateKeyForCall('group.pull', { group_id: groupId, after_seq: afterSeq, limit: 50 }), attempt, true)
|
|
2983
|
+
: client._withBackgroundRpc(attempt));
|
|
2656
2984
|
filled = messages.length;
|
|
2657
2985
|
this.prunePushedSeqs(ns);
|
|
2658
2986
|
client._clientLog.debug(`group message gap fill done: group=${groupId}, after_seq=${afterSeq}, filled=${filled}`);
|
|
@@ -2708,6 +3036,8 @@ export class MessageDeliveryEngine {
|
|
|
2708
3036
|
if (!groupId)
|
|
2709
3037
|
return;
|
|
2710
3038
|
const ns = `group_event:${groupId}`;
|
|
3039
|
+
if (this.automaticGroupPullStopped(ns))
|
|
3040
|
+
return;
|
|
2711
3041
|
const generation = this.captureInlineAckGeneration();
|
|
2712
3042
|
const requestedAfterSeq = Number(options.afterSeq ?? 0);
|
|
2713
3043
|
const afterSeq = Number.isSafeInteger(requestedAfterSeq) && requestedAfterSeq > 0
|
|
@@ -2751,10 +3081,10 @@ export class MessageDeliveryEngine {
|
|
|
2751
3081
|
let result;
|
|
2752
3082
|
if (client._rpcPipeline) {
|
|
2753
3083
|
const pullKey = client._rpcPipeline.pullGateKeyForCall('group.pull_events', request);
|
|
2754
|
-
result = await
|
|
3084
|
+
result = await this.runAutomaticPull(ns, () => client.call('group.pull_events', request), (attempt) => client._rpcPipeline.runPullSerialized(pullKey, attempt, true));
|
|
2755
3085
|
}
|
|
2756
3086
|
else {
|
|
2757
|
-
result = await client.call('group.pull_events', request);
|
|
3087
|
+
result = await this.runAutomaticPull(ns, () => client.call('group.pull_events', request));
|
|
2758
3088
|
}
|
|
2759
3089
|
if (!isJsonObject(result))
|
|
2760
3090
|
return;
|
|
@@ -2923,6 +3253,8 @@ export class MessageDeliveryEngine {
|
|
|
2923
3253
|
enqueueOnlineUnreadEventHint(data) {
|
|
2924
3254
|
const client = this.runtime.client;
|
|
2925
3255
|
const groupId = String(data.group_id ?? '').trim();
|
|
3256
|
+
if (this.automaticGroupPullStopped(`group_event:${groupId}`))
|
|
3257
|
+
return;
|
|
2926
3258
|
if (!groupId || client._sessionOptions?.background_sync === false)
|
|
2927
3259
|
return;
|
|
2928
3260
|
this.recordBackgroundPullNeed(`group_event:${groupId}`, positiveSafeSequenceHint(data.event_seq), true);
|
|
@@ -3252,9 +3584,9 @@ export class MessageDeliveryEngine {
|
|
|
3252
3584
|
return { messages, raw_count: messages.length };
|
|
3253
3585
|
};
|
|
3254
3586
|
if (!client._rpcPipeline)
|
|
3255
|
-
return await invoke
|
|
3587
|
+
return await this.runAutomaticPull(ns, invoke);
|
|
3256
3588
|
const key = client._rpcPipeline.pullGateKeyForCall('message.pull', request);
|
|
3257
|
-
return await client._rpcPipeline.runPullSerialized(key,
|
|
3589
|
+
return await this.runAutomaticPull(ns, invoke, (attempt) => client._rpcPipeline.runPullSerialized(key, attempt, background));
|
|
3258
3590
|
};
|
|
3259
3591
|
if (order.length === 1 && order[0] === 'tail') {
|
|
3260
3592
|
result = await run({ window_mode: 'tail', after_seq: this.syncState(ns).ack, limit: pageLimit }, false);
|
|
@@ -3330,9 +3662,9 @@ export class MessageDeliveryEngine {
|
|
|
3330
3662
|
return { messages, raw_count: messages.length };
|
|
3331
3663
|
};
|
|
3332
3664
|
if (!client._rpcPipeline)
|
|
3333
|
-
return await invoke
|
|
3665
|
+
return await this.runAutomaticPull(ns, invoke);
|
|
3334
3666
|
const key = client._rpcPipeline.pullGateKeyForCall('group.pull', { group_id: groupId, ...request });
|
|
3335
|
-
return await client._rpcPipeline.runPullNamespaceSerialized(key,
|
|
3667
|
+
return await this.runAutomaticPull(ns, invoke, (attempt) => client._rpcPipeline.runPullNamespaceSerialized(key, attempt, background));
|
|
3336
3668
|
};
|
|
3337
3669
|
if (order.length === 1 && order[0] === 'tail') {
|
|
3338
3670
|
result = await run({ window_mode: 'tail', after_seq: this.syncState(ns).ack, limit: pageLimit }, false);
|
|
@@ -3373,9 +3705,9 @@ export class MessageDeliveryEngine {
|
|
|
3373
3705
|
return { messages, raw_count: messages.length };
|
|
3374
3706
|
};
|
|
3375
3707
|
if (!client._rpcPipeline)
|
|
3376
|
-
return await invoke
|
|
3708
|
+
return await this.runAutomaticPull(ns, invoke);
|
|
3377
3709
|
const key = client._rpcPipeline.pullGateKeyForCall('message.pull', request);
|
|
3378
|
-
return await client._rpcPipeline.runPullSerialized(key,
|
|
3710
|
+
return await this.runAutomaticPull(ns, invoke, (attempt) => client._rpcPipeline.runPullSerialized(key, attempt, background));
|
|
3379
3711
|
}
|
|
3380
3712
|
async runGroupForwardRecovery(groupId, ns, pageLimit, background = false, throughSeq = 0) {
|
|
3381
3713
|
const client = this.runtime.client;
|
|
@@ -3387,9 +3719,9 @@ export class MessageDeliveryEngine {
|
|
|
3387
3719
|
return { messages, raw_count: messages.length };
|
|
3388
3720
|
};
|
|
3389
3721
|
if (!client._rpcPipeline)
|
|
3390
|
-
return await invoke
|
|
3722
|
+
return await this.runAutomaticPull(ns, invoke);
|
|
3391
3723
|
const key = client._rpcPipeline.pullGateKeyForCall('group.pull', request);
|
|
3392
|
-
return await client._rpcPipeline.runPullNamespaceSerialized(key,
|
|
3724
|
+
return await this.runAutomaticPull(ns, invoke, (attempt) => client._rpcPipeline.runPullNamespaceSerialized(key, attempt, background));
|
|
3393
3725
|
}
|
|
3394
3726
|
syncState(ns) {
|
|
3395
3727
|
const tracker = this.runtime.client._seqTracker;
|
|
@@ -4018,15 +4350,31 @@ export class MessageDeliveryEngine {
|
|
|
4018
4350
|
return;
|
|
4019
4351
|
}
|
|
4020
4352
|
const sender = String(head.sender_aid);
|
|
4021
|
-
|
|
4353
|
+
this.ensurePeerContext();
|
|
4354
|
+
this.peerPushVersions.set(this.peerNamespace(sender), {});
|
|
4355
|
+
if (!hasNonEmptyInlineMessage(head.inline_message) && !this.isVolatileMessage(head) && !this.hasP2pApplicationSubscription())
|
|
4356
|
+
return;
|
|
4022
4357
|
const generation = this.captureInlineAckGeneration();
|
|
4358
|
+
let ns;
|
|
4359
|
+
try {
|
|
4360
|
+
ns = await this.runAutomaticPull(`peer-init:${sender}`, () => this.initializePeer(sender));
|
|
4361
|
+
}
|
|
4362
|
+
catch (error) {
|
|
4363
|
+
if (generation === this.captureInlineAckGeneration())
|
|
4364
|
+
this.schedulePeerRecovery(sender, positiveSafeSequenceHint(head.seq) ?? 0, this.isVolatileMessage(head));
|
|
4365
|
+
throw error;
|
|
4366
|
+
}
|
|
4023
4367
|
const seq = positiveSafeSequenceHint(head.seq);
|
|
4024
4368
|
if (this.isVolatileMessage(head)) {
|
|
4025
4369
|
await client._v2E2EE.publishV2VolatileMessages([head], 'push', false);
|
|
4026
4370
|
return;
|
|
4027
4371
|
}
|
|
4028
4372
|
if (!seq) {
|
|
4029
|
-
this.
|
|
4373
|
+
if (client._sessionOptions?.background_sync === false && this.hasP2pApplicationSubscription()) {
|
|
4374
|
+
await this.runAutomaticPull(ns, () => client._v2E2EE.pullPeerV2(sender, { limit: _INTERNAL_MESSAGE_FLOW_LIMITS.pullPageSize, budget: { remaining: 1 } }));
|
|
4375
|
+
}
|
|
4376
|
+
else
|
|
4377
|
+
this.refreshPendingPeers();
|
|
4030
4378
|
return;
|
|
4031
4379
|
}
|
|
4032
4380
|
client._seqTracker.updateMaxSeen(ns, seq);
|
|
@@ -4065,14 +4413,14 @@ export class MessageDeliveryEngine {
|
|
|
4065
4413
|
if (consumed?.acquired && consumed.value)
|
|
4066
4414
|
return;
|
|
4067
4415
|
}
|
|
4068
|
-
if (seq <= this.syncState(ns).ack)
|
|
4416
|
+
if (seq <= this.syncState(ns).ack || !this.hasP2pApplicationSubscription())
|
|
4069
4417
|
return;
|
|
4070
4418
|
if (client._sessionOptions?.background_sync !== false) {
|
|
4071
4419
|
this.schedulePeerRecovery(sender, seq);
|
|
4072
4420
|
}
|
|
4073
4421
|
else {
|
|
4074
|
-
await client._v2E2EE.pullPeerV2(sender, { limit: _INTERNAL_MESSAGE_FLOW_LIMITS.pullPageSize, budget: { remaining: 1 },
|
|
4075
|
-
...(this.
|
|
4422
|
+
await this.runAutomaticPull(ns, () => client._v2E2EE.pullPeerV2(sender, { limit: _INTERNAL_MESSAGE_FLOW_LIMITS.pullPageSize, budget: { remaining: 1 },
|
|
4423
|
+
...(this.shouldTailPeer(ns, seq, _INTERNAL_MESSAGE_FLOW_LIMITS.pullPageSize) ? { window_mode: 'tail', autoTail: { head: seq, forward: false } } : {}) }));
|
|
4076
4424
|
}
|
|
4077
4425
|
}
|
|
4078
4426
|
async onV2PushNotification(data, expectedVersion, inlineGeneration) {
|
|
@@ -4136,6 +4484,8 @@ export class MessageDeliveryEngine {
|
|
|
4136
4484
|
this.retryPendingP2PInlineAck(ns, pushSeq, 'covered inline push');
|
|
4137
4485
|
return;
|
|
4138
4486
|
}
|
|
4487
|
+
if (!this.hasP2pApplicationSubscription())
|
|
4488
|
+
return;
|
|
4139
4489
|
this.recordBackgroundPullNeed(ns, pushSeq);
|
|
4140
4490
|
const pullActive = client._rpcPipeline?.hasPullActivity?.(ns) === true;
|
|
4141
4491
|
if (pullActive || !this.beginRealtimeSync(ns)) {
|
|
@@ -4167,6 +4517,8 @@ export class MessageDeliveryEngine {
|
|
|
4167
4517
|
if (!groupId)
|
|
4168
4518
|
return;
|
|
4169
4519
|
const ns = `group:${groupId}`;
|
|
4520
|
+
if (this.automaticGroupPullStopped(ns))
|
|
4521
|
+
return;
|
|
4170
4522
|
const generation = this.captureInlineAckGeneration();
|
|
4171
4523
|
const afterSeq = this.syncState(ns).ack;
|
|
4172
4524
|
this.recordBackgroundPullNeed(ns, positiveSafeSequenceHint(data.seq));
|
|
@@ -4240,6 +4592,8 @@ export class MessageDeliveryEngine {
|
|
|
4240
4592
|
return;
|
|
4241
4593
|
}
|
|
4242
4594
|
const ns = `group:${groupId}`;
|
|
4595
|
+
if (this.automaticGroupPullStopped(ns))
|
|
4596
|
+
return;
|
|
4243
4597
|
let inlineConsumed = false;
|
|
4244
4598
|
const pendingInlineAck = this.pendingGroupInlineAcks?.get(ns);
|
|
4245
4599
|
const inlineBlockedByPendingAck = Boolean(pendingInlineAck && pendingInlineAck.seq !== seq);
|
|
@@ -4649,6 +5003,7 @@ export class MessageDeliveryEngine {
|
|
|
4649
5003
|
this.seqTrackerNamespaceGenerations.set(ns, (this.seqTrackerNamespaceGenerations.get(ns) ?? 0) + 1);
|
|
4650
5004
|
this.committedSeqTrackerStates.delete(ns);
|
|
4651
5005
|
this.initializedPeers.delete(ns);
|
|
5006
|
+
this.peerServerConfirmedAcks.delete(ns);
|
|
4652
5007
|
}
|
|
4653
5008
|
this.dropSeqTrackerPending(ns);
|
|
4654
5009
|
const saveSeq = client._tokenStore.saveSeq;
|