@agentunion/fastaun 0.5.8 → 0.5.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +64 -0
- package/_packed_docs/CHANGELOG.md +64 -0
- package/_packed_docs/agent.md/examples//347/276/244/347/273/204-/345/274/200/345/217/221/345/233/242/351/230/237.md +21 -0
- package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +6 -5
- package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +43 -8
- package/_packed_docs/sdk/09-group-rpc-manual.md +18 -3
- package/_packed_docs/sdk/09-message-rpc-manual.md +34 -10
- package/dist/agent-md-schema.js +3 -0
- package/dist/agent-md-schema.js.map +1 -1
- package/dist/agent-md.js +5 -1
- package/dist/agent-md.js.map +1 -1
- package/dist/aid-store.js +1 -3
- package/dist/aid-store.js.map +1 -1
- package/dist/auth.d.ts +5 -2
- package/dist/auth.js +64 -27
- package/dist/auth.js.map +1 -1
- package/dist/client/delivery.d.ts +25 -5
- package/dist/client/delivery.js +355 -70
- package/dist/client/delivery.js.map +1 -1
- package/dist/client/group-state.js +8 -8
- package/dist/client/group-state.js.map +1 -1
- package/dist/client/lifecycle.js +25 -22
- package/dist/client/lifecycle.js.map +1 -1
- package/dist/client/rpc-pipeline.d.ts +12 -0
- package/dist/client/rpc-pipeline.js +182 -32
- package/dist/client/rpc-pipeline.js.map +1 -1
- package/dist/client/v2-e2ee.d.ts +1 -0
- package/dist/client/v2-e2ee.js +55 -21
- package/dist/client/v2-e2ee.js.map +1 -1
- package/dist/client.js +108 -38
- package/dist/client.js.map +1 -1
- package/dist/discovery.d.ts +1 -0
- package/dist/discovery.js +3 -0
- package/dist/discovery.js.map +1 -1
- package/dist/events.d.ts +23 -8
- package/dist/events.js +120 -26
- package/dist/events.js.map +1 -1
- package/dist/facades.js +7 -3
- package/dist/facades.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js.map +1 -1
- package/dist/net.d.ts +3 -0
- package/dist/net.js +13 -0
- package/dist/net.js.map +1 -1
- package/dist/register-flow.js +15 -111
- package/dist/register-flow.js.map +1 -1
- package/dist/transport.d.ts +32 -3
- package/dist/transport.js +758 -33
- package/dist/transport.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +1 -1
- package/_packed_docs//345/217/221/345/270/203/346/212/245/345/221/212-0.5.6.md +0 -260
package/dist/client/delivery.js
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import { slotIsolationKey } from '../config.js';
|
|
2
2
|
import { ConnectionState, isJsonObject } from '../types.js';
|
|
3
|
+
import { validateGroupAIDFormat } from '../validators.js';
|
|
3
4
|
import { hasGroupMentionModeField, normalizeGroupMentionMode } from './mention-mode.js';
|
|
4
5
|
const PUSHED_SEQS_LIMIT = 50_000;
|
|
5
6
|
const PENDING_ORDERED_LIMIT = 50_000;
|
|
6
7
|
const MESSAGE_RECALL_SEEN_LIMIT = 10_000;
|
|
7
8
|
const GROUP_RECALL_SEEN_LIMIT = 10_000;
|
|
8
9
|
const SEQ_TRACKER_PERSIST_FLUSH_DELAY_MS = 200;
|
|
10
|
+
const DELIVERY_CHANGED_MESSAGE_EVENTS = new Set([
|
|
11
|
+
'message.received',
|
|
12
|
+
'group.message_created',
|
|
13
|
+
]);
|
|
9
14
|
const MAX_INLINE_PENDING_ACK_NAMESPACES = 4096;
|
|
10
15
|
const APP_MESSAGE_ENVELOPE_KEYS = [
|
|
11
16
|
'module_id', 'message_type', 'type', 'kind', 'version',
|
|
@@ -67,15 +72,33 @@ export class MessageDeliveryEngine {
|
|
|
67
72
|
realtimeTailHeads = null;
|
|
68
73
|
realtimeSyncing = null;
|
|
69
74
|
pendingGroupPullUpper = null;
|
|
75
|
+
onlineUnreadHintTargets = null;
|
|
76
|
+
onlineUnreadHintTasks = null;
|
|
70
77
|
pendingP2PInlineAcks = null;
|
|
71
78
|
pendingGroupInlineAcks = null;
|
|
72
79
|
pendingGroupInlineAckNamespaces = null;
|
|
73
80
|
inlineAckGeneration = 0;
|
|
81
|
+
pendingDeliveryChanges = null;
|
|
82
|
+
deliveryChangedGeneration = 0;
|
|
74
83
|
// 同一实例的 tracker 写入必须串行,避免旧的延迟 flush 覆盖新的 Forward 提交。
|
|
75
84
|
seqTrackerWriteChain = Promise.resolve();
|
|
76
85
|
constructor(runtime) {
|
|
77
86
|
this.runtime = runtime;
|
|
78
87
|
}
|
|
88
|
+
isPullOperationCurrent() {
|
|
89
|
+
const client = this.runtime.client;
|
|
90
|
+
const generation = client._pullOperationGeneration;
|
|
91
|
+
if (generation === undefined)
|
|
92
|
+
return true;
|
|
93
|
+
const pipeline = client._rpcPipeline;
|
|
94
|
+
return typeof pipeline?.isPullGenerationCurrent === 'function'
|
|
95
|
+
? pipeline.isPullGenerationCurrent(generation)
|
|
96
|
+
: true;
|
|
97
|
+
}
|
|
98
|
+
ensurePullOperationCurrent() {
|
|
99
|
+
if (!this.isPullOperationCurrent())
|
|
100
|
+
throw new Error('pull invalidated');
|
|
101
|
+
}
|
|
79
102
|
forwardCoordinator() {
|
|
80
103
|
const coordinator = this.runtime.client._v2E2EE;
|
|
81
104
|
if (!coordinator
|
|
@@ -91,6 +114,7 @@ export class MessageDeliveryEngine {
|
|
|
91
114
|
}
|
|
92
115
|
async confirmPlainForwardAck(ns, method, ackSeq, groupId = '') {
|
|
93
116
|
const client = this.runtime.client;
|
|
117
|
+
this.ensurePullOperationCurrent();
|
|
94
118
|
const coordinator = this.forwardCoordinator();
|
|
95
119
|
const generation = this.captureInlineAckGeneration();
|
|
96
120
|
coordinator.recordForwardAck(ns, ackSeq);
|
|
@@ -109,6 +133,7 @@ export class MessageDeliveryEngine {
|
|
|
109
133
|
_rpc_background: true,
|
|
110
134
|
};
|
|
111
135
|
const result = await client._rpcPipeline.rawCall(method, params, { background: true });
|
|
136
|
+
this.ensurePullOperationCurrent();
|
|
112
137
|
const actualAckSeq = this.resolveForwardAckSeq(result, ackSeq);
|
|
113
138
|
if (actualAckSeq < ackSeq) {
|
|
114
139
|
throw new Error(`${method} server ACK watermark ${actualAckSeq} is below requested ${ackSeq}`);
|
|
@@ -126,6 +151,7 @@ export class MessageDeliveryEngine {
|
|
|
126
151
|
throw new Error(`${method} response must be an object`);
|
|
127
152
|
}
|
|
128
153
|
const client = this.runtime.client;
|
|
154
|
+
this.ensurePullOperationCurrent();
|
|
129
155
|
const response = result;
|
|
130
156
|
const messages = deduplicatePlainForwardPageMessages(response.messages, afterSeq);
|
|
131
157
|
const coordinator = this.forwardCoordinator();
|
|
@@ -156,25 +182,30 @@ export class MessageDeliveryEngine {
|
|
|
156
182
|
let committed = false;
|
|
157
183
|
try {
|
|
158
184
|
for (const rawMessage of messages) {
|
|
185
|
+
this.ensurePullOperationCurrent();
|
|
159
186
|
const seq = positiveSafeSequenceHint(rawMessage.seq);
|
|
160
187
|
if (method === 'message.pull') {
|
|
161
188
|
const appEvent = this.p2pAppEventForMessage(rawMessage);
|
|
162
189
|
if (await this.publishPulledMessage(appEvent.event, ns, seq, appEvent.payload, false)) {
|
|
163
190
|
publishedCount += 1;
|
|
164
191
|
}
|
|
192
|
+
this.ensurePullOperationCurrent();
|
|
165
193
|
continue;
|
|
166
194
|
}
|
|
167
195
|
const message = normalizeGroupMentionMode(rawMessage);
|
|
168
196
|
if (this.recallEventFromGroupMessage(message)) {
|
|
169
197
|
if (await this.publishGroupRecallTombstone(groupId, seq, message)) {
|
|
198
|
+
this.ensurePullOperationCurrent();
|
|
170
199
|
this.markPublishedSeq(ns, seq);
|
|
171
200
|
publishedCount += 1;
|
|
172
201
|
}
|
|
173
202
|
}
|
|
174
203
|
else if (await this.publishPulledMessage('group.message_created', ns, seq, message, false)) {
|
|
204
|
+
this.ensurePullOperationCurrent();
|
|
175
205
|
publishedCount += 1;
|
|
176
206
|
}
|
|
177
207
|
}
|
|
208
|
+
this.ensurePullOperationCurrent();
|
|
178
209
|
if (messages.length > 0)
|
|
179
210
|
client._seqTracker.onPullResult(ns, messages, afterSeq);
|
|
180
211
|
const commitTarget = Math.max(client._seqTracker.getContiguousSeq(ns), retentionFloor, visibilityFloor, deferredServerCursor);
|
|
@@ -182,18 +213,21 @@ export class MessageDeliveryEngine {
|
|
|
182
213
|
client._seqTracker.forceContiguousSeq(ns, commitTarget);
|
|
183
214
|
}
|
|
184
215
|
if (client._seqTracker.getContiguousSeq(ns) !== pageContigBefore) {
|
|
185
|
-
|
|
216
|
+
this.ensurePullOperationCurrent();
|
|
217
|
+
await this.drainOrderedMessages(ns, undefined, false, false, 'pull');
|
|
218
|
+
this.ensurePullOperationCurrent();
|
|
186
219
|
await client._commitSeqTrackerState(ns);
|
|
187
220
|
}
|
|
188
221
|
committed = true;
|
|
189
222
|
}
|
|
190
223
|
catch (exc) {
|
|
191
|
-
if (!committed && typeof client._seqTracker.restoreNamespaceSnapshot === 'function') {
|
|
224
|
+
if (this.isPullOperationCurrent() && !committed && typeof client._seqTracker.restoreNamespaceSnapshot === 'function') {
|
|
192
225
|
client._seqTracker.restoreNamespaceSnapshot(ns, pageTrackerSnapshot);
|
|
193
226
|
this.dropSeqTrackerPending(ns);
|
|
194
227
|
}
|
|
195
228
|
throw exc;
|
|
196
229
|
}
|
|
230
|
+
this.ensurePullOperationCurrent();
|
|
197
231
|
const committedAck = client._seqTracker.getContiguousSeq(ns);
|
|
198
232
|
if (deferredServerCursor > 0 && committedAck >= deferredServerCursor) {
|
|
199
233
|
coordinator.clearForwardCursor(ns, committedAck);
|
|
@@ -224,19 +258,132 @@ export class MessageDeliveryEngine {
|
|
|
224
258
|
if (clampedAckSeq < pendingAckSeq) {
|
|
225
259
|
throw new Error(`${ackMethod} cannot confirm pending Forward watermark ${pendingAckSeq}`);
|
|
226
260
|
}
|
|
261
|
+
this.ensurePullOperationCurrent();
|
|
227
262
|
await this.confirmPlainForwardAck(ns, ackMethod, pendingAckSeq, groupId);
|
|
228
263
|
}
|
|
229
264
|
return { rawCount: messages.length, publishedCount };
|
|
230
265
|
}
|
|
231
266
|
resetInlineAckState() {
|
|
267
|
+
this.deliveryChangedGeneration += 1;
|
|
268
|
+
this.pendingDeliveryChanges = null;
|
|
232
269
|
this.inlineAckGeneration += 1;
|
|
270
|
+
void this.runtime.client._rpcPipeline?.invalidatePulls?.();
|
|
233
271
|
this.pendingP2PInlineAcks = null;
|
|
234
272
|
this.pendingGroupInlineAcks = null;
|
|
235
273
|
this.pendingGroupInlineAckNamespaces = null;
|
|
236
274
|
this.realtimeSyncing = null;
|
|
237
275
|
this.pendingGroupPullUpper = null;
|
|
276
|
+
this.onlineUnreadHintTargets = null;
|
|
277
|
+
this.onlineUnreadHintTasks = null;
|
|
238
278
|
this.realtimeTailHeads = null;
|
|
239
279
|
}
|
|
280
|
+
deliveryChangedNamespace(event, payload) {
|
|
281
|
+
if (event === 'group.message_created' && isJsonObject(payload)) {
|
|
282
|
+
const aid = String(this.runtime.client._aid ?? '').trim();
|
|
283
|
+
const dot = aid.indexOf('.');
|
|
284
|
+
try {
|
|
285
|
+
const groupAid = validateGroupAIDFormat(payload.group_aid ?? payload.group_id, { localIssuer: dot >= 0 ? aid.slice(dot + 1) : '' });
|
|
286
|
+
return groupAid.includes('.') ? `group:${groupAid}` : '';
|
|
287
|
+
}
|
|
288
|
+
catch {
|
|
289
|
+
return '';
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return 'p2p';
|
|
293
|
+
}
|
|
294
|
+
isDeliveryChangedMessage(event, payload) {
|
|
295
|
+
if (!DELIVERY_CHANGED_MESSAGE_EVENTS.has(event)
|
|
296
|
+
|| !isJsonObject(payload))
|
|
297
|
+
return false;
|
|
298
|
+
const messageId = payload.message_id;
|
|
299
|
+
const seq = payload.seq;
|
|
300
|
+
return (typeof messageId === 'string' && messageId.trim().length > 0)
|
|
301
|
+
|| (typeof seq === 'number' && Number.isSafeInteger(seq) && seq > 0);
|
|
302
|
+
}
|
|
303
|
+
deliveryChangedTrigger(source) {
|
|
304
|
+
if (source === 'inline-push')
|
|
305
|
+
return 'inline_push';
|
|
306
|
+
if (source === 'pull' || source === 'tail')
|
|
307
|
+
return 'pull_drained';
|
|
308
|
+
if (source === 'push' || source === 'group-push' || source === 'ordered')
|
|
309
|
+
return 'push';
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
noteDeliveryChanged(event, payload, source, context) {
|
|
313
|
+
if (!this.isDeliveryChangedMessage(event, payload))
|
|
314
|
+
return;
|
|
315
|
+
const trigger = this.deliveryChangedTrigger(source);
|
|
316
|
+
const namespace = this.deliveryChangedNamespace(event, payload);
|
|
317
|
+
if (!trigger || !namespace)
|
|
318
|
+
return;
|
|
319
|
+
const pending = trigger === 'pull_drained'
|
|
320
|
+
? this.pendingDeliveryChanges ?? new Map()
|
|
321
|
+
: context?.changes ?? this.pendingDeliveryChanges ?? new Map();
|
|
322
|
+
if (trigger === 'pull_drained' || !context)
|
|
323
|
+
this.pendingDeliveryChanges = pending;
|
|
324
|
+
pending.set(namespace, (pending.get(namespace) ?? 0) + 1);
|
|
325
|
+
if (!context && trigger !== 'pull_drained')
|
|
326
|
+
this.flushDeliveryChanged(trigger);
|
|
327
|
+
}
|
|
328
|
+
flushDeliveryChanged(trigger) {
|
|
329
|
+
const pending = this.pendingDeliveryChanges;
|
|
330
|
+
if (!pending || pending.size === 0)
|
|
331
|
+
return;
|
|
332
|
+
const changes = [...pending.entries()]
|
|
333
|
+
.filter(([, deliveredCount]) => deliveredCount > 0)
|
|
334
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
335
|
+
.map(([namespace, deliveredCount]) => ({ namespace, delivered_count: deliveredCount }));
|
|
336
|
+
pending.clear();
|
|
337
|
+
if (changes.length === 0)
|
|
338
|
+
return;
|
|
339
|
+
const payload = { trigger, changes };
|
|
340
|
+
const dispatcher = this.runtime.client._dispatcher;
|
|
341
|
+
const published = typeof dispatcher.enqueue === 'function'
|
|
342
|
+
? (dispatcher.enqueue('delivery.changed', payload), undefined)
|
|
343
|
+
: typeof dispatcher.publishSyncAware === 'function'
|
|
344
|
+
? dispatcher.publishSyncAware('delivery.changed', payload)
|
|
345
|
+
: dispatcher.publish?.('delivery.changed', payload);
|
|
346
|
+
if (isPromiseLike(published))
|
|
347
|
+
void Promise.resolve(published).catch((exc) => {
|
|
348
|
+
this.runtime.client._clientLog.warn(`delivery.changed handler failed: ${formatDeliveryError(exc)}`);
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
onPullDrained() {
|
|
352
|
+
this.flushDeliveryChanged('pull_drained');
|
|
353
|
+
}
|
|
354
|
+
flushDeliveryChangedContext(context, trigger) {
|
|
355
|
+
if (context.generation !== this.deliveryChangedGeneration || context.changes.size === 0)
|
|
356
|
+
return;
|
|
357
|
+
const pending = this.pendingDeliveryChanges;
|
|
358
|
+
if (pending) {
|
|
359
|
+
for (const [namespace, count] of pending) {
|
|
360
|
+
context.changes.set(namespace, (context.changes.get(namespace) ?? 0) + count);
|
|
361
|
+
}
|
|
362
|
+
pending.clear();
|
|
363
|
+
}
|
|
364
|
+
const changes = [...context.changes.entries()]
|
|
365
|
+
.filter(([, count]) => count > 0)
|
|
366
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
367
|
+
.map(([namespace, delivered_count]) => ({ namespace, delivered_count }));
|
|
368
|
+
context.changes.clear();
|
|
369
|
+
if (changes.length === 0)
|
|
370
|
+
return;
|
|
371
|
+
const dispatcher = this.runtime.client._dispatcher;
|
|
372
|
+
const payload = { trigger, changes };
|
|
373
|
+
const published = typeof dispatcher.enqueue === 'function'
|
|
374
|
+
? (dispatcher.enqueue('delivery.changed', payload), undefined)
|
|
375
|
+
: typeof dispatcher.publishSyncAware === 'function'
|
|
376
|
+
? dispatcher.publishSyncAware('delivery.changed', payload)
|
|
377
|
+
: dispatcher.publish?.('delivery.changed', payload);
|
|
378
|
+
if (isPromiseLike(published))
|
|
379
|
+
void Promise.resolve(published).catch((exc) => {
|
|
380
|
+
this.runtime.client._clientLog.warn(`delivery.changed handler failed: ${formatDeliveryError(exc)}`);
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
invalidateDeliveryChanges() {
|
|
384
|
+
this.deliveryChangedGeneration += 1;
|
|
385
|
+
this.pendingDeliveryChanges = null;
|
|
386
|
+
}
|
|
240
387
|
inlineGenerationIsCurrent(generation) {
|
|
241
388
|
return this.inlineAckGeneration === generation;
|
|
242
389
|
}
|
|
@@ -279,10 +426,17 @@ export class MessageDeliveryEngine {
|
|
|
279
426
|
}
|
|
280
427
|
endRealtimeSync(ns) {
|
|
281
428
|
this.realtimeSyncing?.delete(ns);
|
|
429
|
+
this.runtime.client._rpcPipeline?.requestPullDrainedCheck?.();
|
|
282
430
|
}
|
|
283
431
|
hasPendingPull(ns) {
|
|
284
432
|
return (this.pendingGroupPullUpper?.get(ns) ?? 0) > 0;
|
|
285
433
|
}
|
|
434
|
+
hasAnyPendingPull() {
|
|
435
|
+
return [...(this.pendingGroupPullUpper?.values() ?? [])].some((seq) => seq > 0)
|
|
436
|
+
|| Boolean(this.onlineUnreadHintTasks?.size)
|
|
437
|
+
|| Boolean(this.realtimeSyncing?.size)
|
|
438
|
+
|| Boolean(this.runtime.client._v2E2EE?.hasPendingSenderIKWork?.());
|
|
439
|
+
}
|
|
286
440
|
onPullGateIdle(ns) {
|
|
287
441
|
if (!ns)
|
|
288
442
|
return;
|
|
@@ -347,14 +501,14 @@ export class MessageDeliveryEngine {
|
|
|
347
501
|
client._pushedSeqs.set(ns, new Set(keep));
|
|
348
502
|
}
|
|
349
503
|
}
|
|
350
|
-
enqueueOrderedMessage(ns, event, seq, payload) {
|
|
504
|
+
enqueueOrderedMessage(ns, event, seq, payload, source = 'push') {
|
|
351
505
|
const client = this.runtime.client;
|
|
352
506
|
let queue = client._pendingOrderedMsgs.get(ns);
|
|
353
507
|
if (!queue) {
|
|
354
508
|
queue = new Map();
|
|
355
509
|
client._pendingOrderedMsgs.set(ns, queue);
|
|
356
510
|
}
|
|
357
|
-
queue.set(seq, { event, payload });
|
|
511
|
+
queue.set(seq, { event, payload, source });
|
|
358
512
|
if (queue.size > PENDING_ORDERED_LIMIT) {
|
|
359
513
|
const drop = [...queue.keys()].sort((a, b) => a - b).slice(0, queue.size - PENDING_ORDERED_LIMIT);
|
|
360
514
|
for (const oldSeq of drop)
|
|
@@ -364,26 +518,30 @@ export class MessageDeliveryEngine {
|
|
|
364
518
|
isGroupEventNamespace(ns) {
|
|
365
519
|
return ns.startsWith('group_event:');
|
|
366
520
|
}
|
|
367
|
-
async publishOrderedQueueItem(ns, event, seq, payload, source, pullResponse = false) {
|
|
521
|
+
async publishOrderedQueueItem(ns, event, seq, payload, source, pullResponse = false, deliveryContext) {
|
|
368
522
|
const client = this.runtime.client;
|
|
523
|
+
this.ensurePullOperationCurrent();
|
|
369
524
|
if (event === 'group.changed' && this.isGroupEventNamespace(ns)) {
|
|
370
525
|
await this.publishOrderedGroupChanged(payload, source);
|
|
526
|
+
this.ensurePullOperationCurrent();
|
|
371
527
|
return;
|
|
372
528
|
}
|
|
373
529
|
if (event === 'message.recalled') {
|
|
374
530
|
await this.publishMessageRecallTombstone(seq, payload);
|
|
531
|
+
this.ensurePullOperationCurrent();
|
|
375
532
|
return;
|
|
376
533
|
}
|
|
377
534
|
if (pullResponse) {
|
|
378
|
-
const published = client._withPullResponseProcessing(ns, () =>
|
|
535
|
+
const published = client._withPullResponseProcessing(ns, () => this.publishAppEvent(event, payload, source, deliveryContext));
|
|
379
536
|
if (isPromiseLike(published))
|
|
380
537
|
await published;
|
|
381
538
|
}
|
|
382
539
|
else {
|
|
383
|
-
const published =
|
|
540
|
+
const published = this.publishAppEvent(event, payload, source, deliveryContext);
|
|
384
541
|
if (isPromiseLike(published))
|
|
385
542
|
await published;
|
|
386
543
|
}
|
|
544
|
+
this.ensurePullOperationCurrent();
|
|
387
545
|
}
|
|
388
546
|
async publishOrderedGroupChanged(payload, source = 'ordered') {
|
|
389
547
|
const client = this.runtime.client;
|
|
@@ -899,7 +1057,7 @@ export class MessageDeliveryEngine {
|
|
|
899
1057
|
if (contig !== contigBefore)
|
|
900
1058
|
this.persistSeq(ns);
|
|
901
1059
|
}
|
|
902
|
-
publishAppEvent(event, payload, source = 'direct') {
|
|
1060
|
+
publishAppEvent(event, payload, source = 'direct', deliveryContext) {
|
|
903
1061
|
const client = this.runtime.client;
|
|
904
1062
|
if ((event === 'message.received' || event === 'group.message_created') && isJsonObject(payload)) {
|
|
905
1063
|
client._maybeAppendEchoTraceReceive(payload);
|
|
@@ -919,7 +1077,23 @@ export class MessageDeliveryEngine {
|
|
|
919
1077
|
client._clientLog.debug(`agent_md etag inject skipped: ${err instanceof Error ? err.message : String(err)}`);
|
|
920
1078
|
}
|
|
921
1079
|
}
|
|
922
|
-
|
|
1080
|
+
const generation = this.deliveryChangedGeneration;
|
|
1081
|
+
const normalized = this.normalizePublishedMessagePayload(event, payload);
|
|
1082
|
+
const dispatcher = client._dispatcher;
|
|
1083
|
+
const result = typeof dispatcher.enqueue === 'function'
|
|
1084
|
+
? (dispatcher.enqueue(event, normalized), undefined)
|
|
1085
|
+
: typeof dispatcher.publishSyncAware === 'function'
|
|
1086
|
+
? dispatcher.publishSyncAware(event, normalized)
|
|
1087
|
+
: dispatcher.publish?.(event, normalized);
|
|
1088
|
+
const note = () => {
|
|
1089
|
+
if (generation === this.deliveryChangedGeneration) {
|
|
1090
|
+
this.noteDeliveryChanged(event, payload, source, deliveryContext);
|
|
1091
|
+
}
|
|
1092
|
+
};
|
|
1093
|
+
if (isPromiseLike(result))
|
|
1094
|
+
return Promise.resolve(result).then(note);
|
|
1095
|
+
note();
|
|
1096
|
+
return result;
|
|
923
1097
|
}
|
|
924
1098
|
messagePayloadForDebug(message) {
|
|
925
1099
|
if (!isJsonObject(message))
|
|
@@ -1938,6 +2112,18 @@ export class MessageDeliveryEngine {
|
|
|
1938
2112
|
raw = response.effective_ack_seq;
|
|
1939
2113
|
else if (Object.prototype.hasOwnProperty.call(response, 'ack_seq'))
|
|
1940
2114
|
raw = response.ack_seq;
|
|
2115
|
+
else if (Object.prototype.hasOwnProperty.call(response, 'cursor')) {
|
|
2116
|
+
const cursor = response.cursor;
|
|
2117
|
+
const cursorObject = isJsonObject(cursor) ? cursor : null;
|
|
2118
|
+
if (cursorObject) {
|
|
2119
|
+
if (!Object.prototype.hasOwnProperty.call(cursorObject, 'current_seq'))
|
|
2120
|
+
return 0;
|
|
2121
|
+
raw = cursorObject.current_seq;
|
|
2122
|
+
}
|
|
2123
|
+
else {
|
|
2124
|
+
raw = cursor;
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
1941
2127
|
else
|
|
1942
2128
|
return requestedSeq;
|
|
1943
2129
|
return typeof raw === 'number' && Number.isSafeInteger(raw) && raw >= 0 ? raw : 0;
|
|
@@ -2020,19 +2206,20 @@ export class MessageDeliveryEngine {
|
|
|
2020
2206
|
const state = this.syncState(ns);
|
|
2021
2207
|
const needsForward = state.ack < state.tail - 1;
|
|
2022
2208
|
const backgroundEnabled = client._sessionOptions?.background_sync !== false;
|
|
2023
|
-
// Tail 期间到达的新 Push 可能只更新 maxSeen 而没有扩展 Tail
|
|
2024
|
-
|
|
2025
|
-
const tailMissedDuringPull = tailHead !== null
|
|
2026
|
-
&& Number(client._seqTracker.getMaxSeenSeq?.(ns) ?? 0) > state.head;
|
|
2209
|
+
// Tail 期间到达的新 Push 可能只更新 maxSeen 而没有扩展 Tail 窗口。
|
|
2210
|
+
const maxSeen = Number(client._seqTracker.getMaxSeenSeq?.(ns) ?? 0);
|
|
2211
|
+
const tailMissedDuringPull = tailHead !== null && maxSeen > state.head;
|
|
2027
2212
|
const tailForward = tailHead !== null && (needsForward || tailMissedDuringPull);
|
|
2028
2213
|
const backgroundWindowForward = (order.length === 1 && order[0] === 'forward' && !headForward)
|
|
2029
2214
|
|| (order.length === 1 && order[0] === 'tail' && tailHead === null && needsForward);
|
|
2030
2215
|
const foregroundForward = headForward || tailForward;
|
|
2031
2216
|
if (foregroundForward || backgroundEnabled && backgroundWindowForward) {
|
|
2217
|
+
const forwardTarget = Math.max(headForward ? pushSeq : 0, tailForward || backgroundWindowForward ? state.tail - 1 : 0, tailMissedDuringPull ? maxSeen : 0);
|
|
2218
|
+
const requiredPages = Math.max(1, Math.ceil(Math.max(0, forwardTarget - state.ack) / pageLimit));
|
|
2032
2219
|
result = await run({
|
|
2033
2220
|
after_seq: state.ack,
|
|
2034
2221
|
limit: pageLimit,
|
|
2035
|
-
...(headForward ? { max_pages: 2 } : order[0] === 'tail' ? { max_pages:
|
|
2222
|
+
...(headForward ? { max_pages: Math.max(2, requiredPages) } : order[0] === 'tail' ? { max_pages: requiredPages } : {}),
|
|
2036
2223
|
}, !foregroundForward, headForward || tailMissedDuringPull);
|
|
2037
2224
|
}
|
|
2038
2225
|
if (tailHead !== null && this.syncState(ns).ack < this.syncState(ns).tail - 1) {
|
|
@@ -2094,18 +2281,19 @@ export class MessageDeliveryEngine {
|
|
|
2094
2281
|
const state = this.syncState(ns);
|
|
2095
2282
|
const needsForward = state.ack < state.tail - 1;
|
|
2096
2283
|
const backgroundEnabled = client._sessionOptions?.background_sync !== false;
|
|
2097
|
-
|
|
2098
|
-
const tailMissedDuringPull = tailHead !== null
|
|
2099
|
-
&& Number(client._seqTracker.getMaxSeenSeq?.(ns) ?? 0) > state.head;
|
|
2284
|
+
const maxSeen = Number(client._seqTracker.getMaxSeenSeq?.(ns) ?? 0);
|
|
2285
|
+
const tailMissedDuringPull = tailHead !== null && maxSeen > state.head;
|
|
2100
2286
|
const tailForward = tailHead !== null && (needsForward || tailMissedDuringPull);
|
|
2101
2287
|
const backgroundWindowForward = (order.length === 1 && order[0] === 'forward' && !headForward)
|
|
2102
2288
|
|| (order.length === 1 && order[0] === 'tail' && tailHead === null && needsForward);
|
|
2103
2289
|
const foregroundForward = headForward || tailForward;
|
|
2104
2290
|
if (foregroundForward || backgroundEnabled && backgroundWindowForward) {
|
|
2291
|
+
const forwardTarget = Math.max(headForward ? pushSeq : 0, tailForward || backgroundWindowForward ? state.tail - 1 : 0, tailMissedDuringPull ? maxSeen : 0);
|
|
2292
|
+
const requiredPages = Math.max(1, Math.ceil(Math.max(0, forwardTarget - state.ack) / pageLimit));
|
|
2105
2293
|
result = await run({
|
|
2106
2294
|
after_seq: state.ack,
|
|
2107
2295
|
limit: pageLimit,
|
|
2108
|
-
...(headForward ? { max_pages: 2 } : order[0] === 'tail' ? { max_pages:
|
|
2296
|
+
...(headForward ? { max_pages: Math.max(2, requiredPages) } : order[0] === 'tail' ? { max_pages: requiredPages } : {}),
|
|
2109
2297
|
}, !foregroundForward, headForward || tailMissedDuringPull);
|
|
2110
2298
|
}
|
|
2111
2299
|
if (tailHead !== null && this.syncState(ns).ack < this.syncState(ns).tail - 1) {
|
|
@@ -2129,12 +2317,14 @@ export class MessageDeliveryEngine {
|
|
|
2129
2317
|
const key = client._rpcPipeline.pullGateKeyForCall('message.pull', request);
|
|
2130
2318
|
return await client._rpcPipeline.runPullSerialized(key, invoke, background);
|
|
2131
2319
|
}
|
|
2132
|
-
async runGroupForwardRecovery(groupId, ns, pageLimit, background = false) {
|
|
2320
|
+
async runGroupForwardRecovery(groupId, ns, pageLimit, background = false, throughSeq = 0) {
|
|
2133
2321
|
const client = this.runtime.client;
|
|
2134
|
-
const
|
|
2322
|
+
const ack = this.syncState(ns).ack;
|
|
2323
|
+
const maxPages = Math.max(1, Math.ceil(Math.max(0, throughSeq - ack) / pageLimit));
|
|
2324
|
+
const request = { group_id: groupId, after_seq: ack, limit: pageLimit, max_pages: maxPages };
|
|
2135
2325
|
const invoke = async () => {
|
|
2136
2326
|
const after = this.syncState(ns).ack;
|
|
2137
|
-
const messages = await client._pullGroupV2(groupId, after, pageLimit, { gateLocked: true, maxPages
|
|
2327
|
+
const messages = await client._pullGroupV2(groupId, after, pageLimit, { gateLocked: true, maxPages });
|
|
2138
2328
|
return { messages, raw_count: messages.length };
|
|
2139
2329
|
};
|
|
2140
2330
|
if (!client._rpcPipeline)
|
|
@@ -2770,9 +2960,35 @@ export class MessageDeliveryEngine {
|
|
|
2770
2960
|
if (!groupId)
|
|
2771
2961
|
return;
|
|
2772
2962
|
const ns = `group:${groupId}`;
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2963
|
+
const targets = this.onlineUnreadHintTargets ?? new Map();
|
|
2964
|
+
const tasks = this.onlineUnreadHintTasks ?? new Map();
|
|
2965
|
+
this.onlineUnreadHintTargets = targets;
|
|
2966
|
+
this.onlineUnreadHintTasks = tasks;
|
|
2967
|
+
targets.set(ns, Math.max(targets.get(ns) ?? 0, positiveSafeSequenceHint(data.seq)));
|
|
2968
|
+
if (tasks.has(ns))
|
|
2969
|
+
return;
|
|
2970
|
+
const task = Promise.resolve().then(async () => {
|
|
2971
|
+
try {
|
|
2972
|
+
while (true) {
|
|
2973
|
+
const ackBefore = this.syncState(ns).ack;
|
|
2974
|
+
await this.runGroupForwardRecovery(groupId, ns, 50, true, targets.get(ns) ?? 0);
|
|
2975
|
+
const ackAfter = this.syncState(ns).ack;
|
|
2976
|
+
if (ackAfter >= (targets.get(ns) ?? 0) || ackAfter <= ackBefore)
|
|
2977
|
+
return;
|
|
2978
|
+
}
|
|
2979
|
+
}
|
|
2980
|
+
catch (exc) {
|
|
2981
|
+
client._clientLog.debug(`online unread hint background Forward failed: ns=${ns} err=${formatDeliveryError(exc)}`);
|
|
2982
|
+
}
|
|
2983
|
+
finally {
|
|
2984
|
+
targets.delete(ns);
|
|
2985
|
+
if (tasks.get(ns) === task)
|
|
2986
|
+
tasks.delete(ns);
|
|
2987
|
+
client._rpcPipeline?.requestPullDrainedCheck?.();
|
|
2988
|
+
}
|
|
2989
|
+
});
|
|
2990
|
+
tasks.set(ns, task);
|
|
2991
|
+
client._safeAsync(task);
|
|
2776
2992
|
}
|
|
2777
2993
|
async onRawGroupV2MessageCreated(data, expectedVersion, inlineGeneration) {
|
|
2778
2994
|
const client = this.runtime.client;
|
|
@@ -2899,13 +3115,13 @@ export class MessageDeliveryEngine {
|
|
|
2899
3115
|
catch (exc) {
|
|
2900
3116
|
const error = formatDeliveryError(exc);
|
|
2901
3117
|
client._clientLog.warn(`restore SeqTracker state failed: ${error}`);
|
|
2902
|
-
client._dispatcher.
|
|
3118
|
+
client._dispatcher.enqueue('seq_tracker.persist_error', {
|
|
2903
3119
|
phase: 'restore',
|
|
2904
3120
|
aid: client._aid,
|
|
2905
3121
|
device_id: client._deviceId,
|
|
2906
3122
|
slot_id: client._slotId,
|
|
2907
3123
|
error: String(error),
|
|
2908
|
-
})
|
|
3124
|
+
});
|
|
2909
3125
|
}
|
|
2910
3126
|
}
|
|
2911
3127
|
migrateSeqStateGroupIds(state) {
|
|
@@ -3124,13 +3340,13 @@ export class MessageDeliveryEngine {
|
|
|
3124
3340
|
catch (exc) {
|
|
3125
3341
|
const error = formatDeliveryError(exc);
|
|
3126
3342
|
client._clientLog.warn(`save SeqTracker state failed: ${error}`);
|
|
3127
|
-
client._dispatcher.
|
|
3343
|
+
client._dispatcher.enqueue('seq_tracker.persist_error', {
|
|
3128
3344
|
phase: 'save',
|
|
3129
3345
|
aid,
|
|
3130
3346
|
device_id: deviceId,
|
|
3131
3347
|
slot_id: slotId,
|
|
3132
3348
|
error: String(error),
|
|
3133
|
-
})
|
|
3349
|
+
});
|
|
3134
3350
|
if (throwOnError)
|
|
3135
3351
|
throw exc;
|
|
3136
3352
|
}
|
|
@@ -3248,55 +3464,106 @@ export class MessageDeliveryEngine {
|
|
|
3248
3464
|
}
|
|
3249
3465
|
return params;
|
|
3250
3466
|
}
|
|
3251
|
-
async drainOrderedMessages(ns, beforeSeq, pullResponse = false, persist = true) {
|
|
3467
|
+
async drainOrderedMessages(ns, beforeSeq, pullResponse = false, persist = true, source = 'push', context) {
|
|
3252
3468
|
const client = this.runtime.client;
|
|
3469
|
+
this.ensurePullOperationCurrent();
|
|
3253
3470
|
const queue = client._pendingOrderedMsgs.get(ns);
|
|
3254
3471
|
if (!queue || queue.size === 0)
|
|
3255
3472
|
return;
|
|
3256
3473
|
let delivered = false;
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
.
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
if (
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3474
|
+
const rootTrigger = this.deliveryChangedTrigger(source);
|
|
3475
|
+
const drainContexts = new Map();
|
|
3476
|
+
try {
|
|
3477
|
+
while (true) {
|
|
3478
|
+
this.ensurePullOperationCurrent();
|
|
3479
|
+
const contig = client._seqTracker.getContiguousSeq(ns);
|
|
3480
|
+
const ready = [...queue.keys()]
|
|
3481
|
+
.filter((seq) => seq <= contig && (beforeSeq === undefined || seq < beforeSeq))
|
|
3482
|
+
.sort((a, b) => a - b);
|
|
3483
|
+
let seq = ready[0];
|
|
3484
|
+
if (seq === undefined) {
|
|
3485
|
+
const nextSeq = contig + 1;
|
|
3486
|
+
if (beforeSeq !== undefined && nextSeq >= beforeSeq)
|
|
3487
|
+
break;
|
|
3488
|
+
if (!queue.has(nextSeq))
|
|
3489
|
+
break;
|
|
3490
|
+
seq = nextSeq;
|
|
3491
|
+
}
|
|
3492
|
+
if (seq === undefined)
|
|
3493
|
+
continue;
|
|
3494
|
+
const item = queue.get(seq);
|
|
3495
|
+
queue.delete(seq);
|
|
3496
|
+
if (!item)
|
|
3497
|
+
continue;
|
|
3498
|
+
if (client._pushedSeqs.get(ns)?.has(seq)) {
|
|
3499
|
+
client._clientLog.debug(`publish ordered drain skipped duplicate: ns=${ns}, seq=${seq}, event=${item.event}`);
|
|
3500
|
+
client._markOrderedSeqDelivered(ns, seq);
|
|
3501
|
+
continue;
|
|
3502
|
+
}
|
|
3503
|
+
const itemSource = item.source ?? source;
|
|
3504
|
+
const itemTrigger = this.deliveryChangedTrigger(itemSource);
|
|
3505
|
+
let itemContext = itemTrigger === rootTrigger ? context : undefined;
|
|
3506
|
+
if (itemTrigger === 'push' || itemTrigger === 'inline_push') {
|
|
3507
|
+
if (!itemContext) {
|
|
3508
|
+
itemContext = drainContexts.get(itemTrigger);
|
|
3509
|
+
if (!itemContext) {
|
|
3510
|
+
itemContext = { generation: this.deliveryChangedGeneration, changes: new Map() };
|
|
3511
|
+
drainContexts.set(itemTrigger, itemContext);
|
|
3512
|
+
}
|
|
3513
|
+
}
|
|
3514
|
+
}
|
|
3515
|
+
await this.publishOrderedQueueItem(ns, item.event, seq, item.payload, itemSource, pullResponse, itemContext);
|
|
3516
|
+
this.ensurePullOperationCurrent();
|
|
3517
|
+
this.markPublishedSeq(ns, seq);
|
|
3279
3518
|
client._markOrderedSeqDelivered(ns, seq);
|
|
3280
|
-
|
|
3519
|
+
delivered = true;
|
|
3520
|
+
client._clientLog.debug(`publish ordered drain delivered: ns=${ns}, seq=${seq}, event=${item.event}`);
|
|
3521
|
+
}
|
|
3522
|
+
}
|
|
3523
|
+
finally {
|
|
3524
|
+
for (const [trigger, drainContext] of drainContexts) {
|
|
3525
|
+
if (drainContext !== context)
|
|
3526
|
+
this.flushDeliveryChangedContext(drainContext, trigger);
|
|
3281
3527
|
}
|
|
3282
|
-
await this.publishOrderedQueueItem(ns, item.event, seq, item.payload, 'ordered-drain', pullResponse);
|
|
3283
|
-
this.markPublishedSeq(ns, seq);
|
|
3284
|
-
client._markOrderedSeqDelivered(ns, seq);
|
|
3285
|
-
delivered = true;
|
|
3286
|
-
client._clientLog.debug(`publish ordered drain delivered: ns=${ns}, seq=${seq}, event=${item.event}`);
|
|
3287
3528
|
}
|
|
3288
3529
|
if (queue.size === 0) {
|
|
3289
3530
|
client._pendingOrderedMsgs.delete(ns);
|
|
3290
|
-
if (delivered && persist)
|
|
3531
|
+
if (delivered && persist) {
|
|
3532
|
+
this.ensurePullOperationCurrent();
|
|
3291
3533
|
await this.saveSeqTrackerState();
|
|
3534
|
+
}
|
|
3292
3535
|
}
|
|
3293
3536
|
}
|
|
3294
|
-
async publishOrderedMessage(event, ns, seq, payload) {
|
|
3537
|
+
async publishOrderedMessage(event, ns, seq, payload, source = 'push') {
|
|
3538
|
+
const context = {
|
|
3539
|
+
generation: this.deliveryChangedGeneration,
|
|
3540
|
+
changes: new Map(),
|
|
3541
|
+
};
|
|
3542
|
+
try {
|
|
3543
|
+
return await this.publishOrderedMessageInternal(event, ns, seq, payload, source, context);
|
|
3544
|
+
}
|
|
3545
|
+
finally {
|
|
3546
|
+
const trigger = this.deliveryChangedTrigger(source);
|
|
3547
|
+
if (trigger === 'pull_drained' && context.generation === this.deliveryChangedGeneration) {
|
|
3548
|
+
const pending = this.pendingDeliveryChanges ?? new Map();
|
|
3549
|
+
this.pendingDeliveryChanges = pending;
|
|
3550
|
+
for (const [namespace, count] of context.changes) {
|
|
3551
|
+
pending.set(namespace, (pending.get(namespace) ?? 0) + count);
|
|
3552
|
+
}
|
|
3553
|
+
}
|
|
3554
|
+
else if (trigger && trigger !== 'pull_drained') {
|
|
3555
|
+
this.flushDeliveryChangedContext(context, trigger);
|
|
3556
|
+
}
|
|
3557
|
+
}
|
|
3558
|
+
}
|
|
3559
|
+
async publishOrderedMessageInternal(event, ns, seq, payload, source, context) {
|
|
3295
3560
|
const client = this.runtime.client;
|
|
3561
|
+
this.ensurePullOperationCurrent();
|
|
3296
3562
|
const seqNum = Number(seq);
|
|
3297
3563
|
if (!Number.isFinite(seqNum) || !Number.isInteger(seqNum) || seqNum <= 0) {
|
|
3298
3564
|
client._clientLog.debug(`publish ordered direct(no-seq): event=${event}, ns=${ns || '<none>'}, seq=${String(seq)}`);
|
|
3299
|
-
await this.publishOrderedQueueItem(ns, event, seqNum, payload,
|
|
3565
|
+
await this.publishOrderedQueueItem(ns, event, seqNum, payload, source, false, context);
|
|
3566
|
+
this.ensurePullOperationCurrent();
|
|
3300
3567
|
return true;
|
|
3301
3568
|
}
|
|
3302
3569
|
if (client._pushedSeqs.get(ns)?.has(seqNum)) {
|
|
@@ -3310,10 +3577,11 @@ export class MessageDeliveryEngine {
|
|
|
3310
3577
|
const contig = client._seqTracker.getContiguousSeq(ns);
|
|
3311
3578
|
if (seqNum > contig && seqNum !== contig + 1) {
|
|
3312
3579
|
client._clientLog.debug(`publish ordered enqueue(gap): event=${event}, ns=${ns}, seq=${seqNum}, contiguous=${contig}`);
|
|
3313
|
-
this.enqueueOrderedMessage(ns, event, seqNum, payload);
|
|
3580
|
+
this.enqueueOrderedMessage(ns, event, seqNum, payload, source);
|
|
3314
3581
|
return false;
|
|
3315
3582
|
}
|
|
3316
|
-
await this.drainOrderedMessages(ns, seqNum);
|
|
3583
|
+
await this.drainOrderedMessages(ns, seqNum, false, true, source, context);
|
|
3584
|
+
this.ensurePullOperationCurrent();
|
|
3317
3585
|
if (client._pushedSeqs.get(ns)?.has(seqNum)) {
|
|
3318
3586
|
client._clientLog.debug(`publish ordered skipped after-drain duplicate: event=${event}, ns=${ns}, seq=${seqNum}`);
|
|
3319
3587
|
return false;
|
|
@@ -3322,20 +3590,25 @@ export class MessageDeliveryEngine {
|
|
|
3322
3590
|
queue?.delete(seqNum);
|
|
3323
3591
|
if (queue && queue.size === 0)
|
|
3324
3592
|
client._pendingOrderedMsgs.delete(ns);
|
|
3325
|
-
await this.publishOrderedQueueItem(ns, event, seqNum, payload,
|
|
3593
|
+
await this.publishOrderedQueueItem(ns, event, seqNum, payload, source, false, context);
|
|
3594
|
+
this.ensurePullOperationCurrent();
|
|
3326
3595
|
this.markPublishedSeq(ns, seqNum);
|
|
3327
3596
|
client._markOrderedSeqDelivered(ns, seqNum);
|
|
3328
3597
|
client._clientLog.debug(`publish ordered delivered: event=${event}, ns=${ns}, seq=${seqNum}`);
|
|
3329
|
-
await this.drainOrderedMessages(ns);
|
|
3330
|
-
if (!client._pendingOrderedMsgs.get(ns))
|
|
3598
|
+
await this.drainOrderedMessages(ns, undefined, false, true, source, context);
|
|
3599
|
+
if (!client._pendingOrderedMsgs.get(ns)) {
|
|
3600
|
+
this.ensurePullOperationCurrent();
|
|
3331
3601
|
await this.saveSeqTrackerState();
|
|
3602
|
+
}
|
|
3332
3603
|
return true;
|
|
3333
3604
|
}
|
|
3334
3605
|
async publishOrderedGroupRecall(ns, seq, message) {
|
|
3335
3606
|
const client = this.runtime.client;
|
|
3607
|
+
this.ensurePullOperationCurrent();
|
|
3336
3608
|
const seqNum = Number(seq);
|
|
3337
3609
|
if (!Number.isFinite(seqNum) || !Number.isInteger(seqNum) || seqNum <= 0) {
|
|
3338
3610
|
await this.publishGroupRecallTombstone(ns.replace(/^group:/, ''), seq, message);
|
|
3611
|
+
this.ensurePullOperationCurrent();
|
|
3339
3612
|
return true;
|
|
3340
3613
|
}
|
|
3341
3614
|
// 撤回 tombstone 也占 seq:推进 contiguous 并标记已发布,确保 ack 正常推进、不留空洞。
|
|
@@ -3345,24 +3618,31 @@ export class MessageDeliveryEngine {
|
|
|
3345
3618
|
}
|
|
3346
3619
|
client._seqTracker.onMessageSeq(ns, seqNum);
|
|
3347
3620
|
await this.publishGroupRecallTombstone(ns.replace(/^group:/, ''), seq, message);
|
|
3621
|
+
this.ensurePullOperationCurrent();
|
|
3348
3622
|
this.markPublishedSeq(ns, seqNum);
|
|
3349
3623
|
client._markOrderedSeqDelivered?.(ns, seqNum);
|
|
3350
3624
|
await this.drainOrderedMessages(ns);
|
|
3351
|
-
if (!client._pendingOrderedMsgs.get(ns))
|
|
3625
|
+
if (!client._pendingOrderedMsgs.get(ns)) {
|
|
3626
|
+
this.ensurePullOperationCurrent();
|
|
3352
3627
|
await this.saveSeqTrackerState();
|
|
3628
|
+
}
|
|
3353
3629
|
return true;
|
|
3354
3630
|
}
|
|
3355
3631
|
async publishPulledMessage(event, ns, seq, payload, persist = true) {
|
|
3356
3632
|
const client = this.runtime.client;
|
|
3633
|
+
this.ensurePullOperationCurrent();
|
|
3357
3634
|
const seqNum = Number(seq);
|
|
3358
3635
|
if (!Number.isFinite(seqNum) || !Number.isInteger(seqNum) || seqNum <= 0 || !ns) {
|
|
3359
3636
|
client._clientLog.debug(`publish pulled direct(no-seq): event=${event}, ns=${ns || '<none>'}, seq=${String(seq)}`);
|
|
3360
3637
|
if (event === 'message.recalled') {
|
|
3361
|
-
|
|
3638
|
+
const published = await this.publishMessageRecallTombstone(seq, payload);
|
|
3639
|
+
this.ensurePullOperationCurrent();
|
|
3640
|
+
return published;
|
|
3362
3641
|
}
|
|
3363
3642
|
const published = client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload, 'pull'));
|
|
3364
3643
|
if (isPromiseLike(published))
|
|
3365
3644
|
await published;
|
|
3645
|
+
this.ensurePullOperationCurrent();
|
|
3366
3646
|
return true;
|
|
3367
3647
|
}
|
|
3368
3648
|
const queue = client._pendingOrderedMsgs.get(ns);
|
|
@@ -3373,23 +3653,28 @@ export class MessageDeliveryEngine {
|
|
|
3373
3653
|
client._pendingOrderedMsgs.delete(ns);
|
|
3374
3654
|
return false;
|
|
3375
3655
|
}
|
|
3376
|
-
await this.drainOrderedMessages(ns, seqNum, true, persist);
|
|
3656
|
+
await this.drainOrderedMessages(ns, seqNum, true, persist, 'pull');
|
|
3657
|
+
this.ensurePullOperationCurrent();
|
|
3377
3658
|
queue?.delete(seqNum);
|
|
3378
3659
|
if (queue && queue.size === 0)
|
|
3379
3660
|
client._pendingOrderedMsgs.delete(ns);
|
|
3380
3661
|
if (event === 'message.recalled') {
|
|
3381
3662
|
const recallPublished = await this.publishMessageRecallTombstone(seqNum, payload);
|
|
3663
|
+
this.ensurePullOperationCurrent();
|
|
3382
3664
|
this.markPublishedSeq(ns, seqNum);
|
|
3383
3665
|
client._clientLog.debug(`publish pulled delivered: event=${event}, ns=${ns}, seq=${seqNum}`);
|
|
3384
|
-
await this.drainOrderedMessages(ns, undefined, true, persist);
|
|
3666
|
+
await this.drainOrderedMessages(ns, undefined, true, persist, 'pull');
|
|
3667
|
+
this.ensurePullOperationCurrent();
|
|
3385
3668
|
return recallPublished;
|
|
3386
3669
|
}
|
|
3387
3670
|
const published = client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload, 'pull'));
|
|
3388
3671
|
if (isPromiseLike(published))
|
|
3389
3672
|
await published;
|
|
3673
|
+
this.ensurePullOperationCurrent();
|
|
3390
3674
|
this.markPublishedSeq(ns, seqNum);
|
|
3391
3675
|
client._clientLog.debug(`publish pulled delivered: event=${event}, ns=${ns}, seq=${seqNum}`);
|
|
3392
|
-
await this.drainOrderedMessages(ns, undefined, true, persist);
|
|
3676
|
+
await this.drainOrderedMessages(ns, undefined, true, persist, 'pull');
|
|
3677
|
+
this.ensurePullOperationCurrent();
|
|
3393
3678
|
return true;
|
|
3394
3679
|
}
|
|
3395
3680
|
}
|