@agentunion/fastaun 0.5.11 → 0.5.13
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 +46 -0
- package/_packed_docs/CHANGELOG.md +46 -0
- package/_packed_docs/INDEX.md +3 -3
- package/_packed_docs/KITE_DOCS_GUIDE.md +1 -1
- package/_packed_docs/agent.md/SCHEMA.md +84 -84
- 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 +8 -6
- package/_packed_docs/aun/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +29 -15
- package/_packed_docs/protocol/10-Group-/345/255/220/345/215/217/350/256/256.md +21 -3
- package/_packed_docs/sdk/09-group-rpc-manual.md +29 -2
- package/dist/agent-md-schema.js +8 -4
- package/dist/agent-md-schema.js.map +1 -1
- package/dist/client/delivery.d.ts +2 -3
- package/dist/client/delivery.js +69 -222
- package/dist/client/delivery.js.map +1 -1
- package/dist/client/group-state.js +1 -0
- package/dist/client/group-state.js.map +1 -1
- package/dist/client/rpc-pipeline.js +18 -8
- package/dist/client/rpc-pipeline.js.map +1 -1
- package/dist/client/v2-e2ee.d.ts +0 -1
- package/dist/client/v2-e2ee.js +43 -62
- package/dist/client/v2-e2ee.js.map +1 -1
- package/dist/client.d.ts +17 -0
- package/dist/client.js +269 -16
- package/dist/client.js.map +1 -1
- package/dist/facades.d.ts +6 -0
- package/dist/facades.js +66 -0
- package/dist/facades.js.map +1 -1
- package/dist/seq-tracker.d.ts +2 -2
- package/dist/seq-tracker.js +11 -17
- package/dist/seq-tracker.js.map +1 -1
- package/dist/tools/cross-sdk-agent.js +50 -20
- package/dist/tools/cross-sdk-agent.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
|
@@ -50,6 +50,10 @@ function positiveSafeSequenceHint(value) {
|
|
|
50
50
|
const parsed = Number(value);
|
|
51
51
|
return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : 0;
|
|
52
52
|
}
|
|
53
|
+
function hasNonEmptyInlineMessage(value) {
|
|
54
|
+
return isJsonObject(value)
|
|
55
|
+
&& Object.keys(value).length > 0;
|
|
56
|
+
}
|
|
53
57
|
function nonNegativeSafeSequenceHint(value) {
|
|
54
58
|
return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0 ? value : null;
|
|
55
59
|
}
|
|
@@ -203,14 +207,18 @@ export class MessageDeliveryEngine {
|
|
|
203
207
|
publishedCount += 1;
|
|
204
208
|
}
|
|
205
209
|
}
|
|
210
|
+
else if (this.isSelfSentGroupMessage(message)) {
|
|
211
|
+
this.markPublishedSeq(ns, seq);
|
|
212
|
+
}
|
|
206
213
|
else if (await this.publishPulledMessage('group.message_created', ns, seq, message, false)) {
|
|
207
214
|
this.ensurePullOperationCurrent();
|
|
208
215
|
publishedCount += 1;
|
|
209
216
|
}
|
|
210
217
|
}
|
|
211
218
|
this.ensurePullOperationCurrent();
|
|
212
|
-
|
|
213
|
-
|
|
219
|
+
client._seqTracker.onPullResult(ns, messages, afterSeq, Number.isSafeInteger(result.contiguous_seq)
|
|
220
|
+
? result.contiguous_seq
|
|
221
|
+
: undefined);
|
|
214
222
|
const commitTarget = Math.max(client._seqTracker.getContiguousSeq(ns), retentionFloor, visibilityFloor, deferredServerCursor);
|
|
215
223
|
if (commitTarget > client._seqTracker.getContiguousSeq(ns)) {
|
|
216
224
|
client._seqTracker.forceContiguousSeq(ns, commitTarget);
|
|
@@ -554,6 +562,9 @@ export class MessageDeliveryEngine {
|
|
|
554
562
|
hasPendingPull(ns) {
|
|
555
563
|
return (this.pendingGroupPullUpper?.get(ns) ?? 0) > 0;
|
|
556
564
|
}
|
|
565
|
+
pendingPullUpper(ns) {
|
|
566
|
+
return this.pendingGroupPullUpper?.get(ns) ?? 0;
|
|
567
|
+
}
|
|
557
568
|
hasAnyPendingPull() {
|
|
558
569
|
return [...(this.pendingGroupPullUpper?.values() ?? [])].some((seq) => seq > 0)
|
|
559
570
|
|| Boolean(this.onlineUnreadHintTasks?.size)
|
|
@@ -1160,35 +1171,11 @@ export class MessageDeliveryEngine {
|
|
|
1160
1171
|
return;
|
|
1161
1172
|
}
|
|
1162
1173
|
const ns = `group:${groupId}`;
|
|
1163
|
-
if (seqNum > 0)
|
|
1174
|
+
if (seqNum > 0)
|
|
1164
1175
|
client._seqTracker.updateMaxSeen(ns, seqNum);
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
await this.publishGroupRecallTombstone(groupId, seq, wrapped);
|
|
1168
|
-
return;
|
|
1169
|
-
}
|
|
1170
|
-
client._repairPushContiguousBound(ns, seqNum, true, '_raw.group.message_recalled');
|
|
1171
|
-
}
|
|
1172
|
-
// 该 notice_seq 已由 pull 路径处理过(已发布或挂起待发布)时,去重发布兜底后返回。
|
|
1173
|
-
const pushed = client._pushedSeqs.get(ns);
|
|
1174
|
-
const pending = client._pendingOrderedMsgs.get(ns);
|
|
1175
|
-
if (pushed?.has(seqNum) || pending?.has(seqNum)) {
|
|
1176
|
-
await this.publishGroupRecallTombstone(groupId, seq, wrapped);
|
|
1177
|
-
return;
|
|
1176
|
+
if (seqNum > client._seqTracker.getContiguousSeq(ns) && client._sessionOptions?.background_sync !== false) {
|
|
1177
|
+
this.fillGroupGap(groupId).catch((exc) => client._clientLog.warn(`background recall gap fill failed: ${formatDeliveryError(exc)}`));
|
|
1178
1178
|
}
|
|
1179
|
-
const contigBefore = client._seqTracker.getContiguousSeq(ns);
|
|
1180
|
-
client._seqTracker.onMessageSeq(ns, seqNum);
|
|
1181
|
-
await this.publishGroupRecallTombstone(groupId, seq, wrapped);
|
|
1182
|
-
this.markPublishedSeq(ns, seqNum);
|
|
1183
|
-
client._markOrderedSeqDelivered?.(ns, seqNum);
|
|
1184
|
-
const contig = client._seqTracker.getContiguousSeq(ns);
|
|
1185
|
-
if (contig > 0) {
|
|
1186
|
-
const ackSeq = this.clampAckSeq('group.ack_messages', 'msg_seq', ns, contig);
|
|
1187
|
-
client._withBackgroundRpc(() => client._ackGroupV2(groupId, ackSeq))
|
|
1188
|
-
.catch((e) => { client._clientLog.debug(`group recall auto-ack failed: group=${groupId} ${formatDeliveryError(e)}`); });
|
|
1189
|
-
}
|
|
1190
|
-
if (contig !== contigBefore)
|
|
1191
|
-
this.persistSeq(ns);
|
|
1192
1179
|
}
|
|
1193
1180
|
publishAppEvent(event, payload, source = 'direct', deliveryContext) {
|
|
1194
1181
|
const client = this.runtime.client;
|
|
@@ -1330,6 +1317,21 @@ export class MessageDeliveryEngine {
|
|
|
1330
1317
|
}
|
|
1331
1318
|
return true;
|
|
1332
1319
|
}
|
|
1320
|
+
isSelfSentGroupMessage(message) {
|
|
1321
|
+
if (!isJsonObject(message))
|
|
1322
|
+
return false;
|
|
1323
|
+
const client = this.runtime.client;
|
|
1324
|
+
const selfAid = String(client._aid ?? '').trim().toLowerCase();
|
|
1325
|
+
const senderAid = String(message.sender_aid ?? message.from_aid ?? message.from ?? '').trim().toLowerCase();
|
|
1326
|
+
if (!selfAid || senderAid !== selfAid)
|
|
1327
|
+
return false;
|
|
1328
|
+
const senderDevice = Object.prototype.hasOwnProperty.call(message, 'sender_device_id')
|
|
1329
|
+
? message.sender_device_id
|
|
1330
|
+
: message.from_device_id;
|
|
1331
|
+
if (typeof senderDevice !== 'string' || !senderDevice.trim() || !String(client._deviceId ?? '').trim())
|
|
1332
|
+
return false;
|
|
1333
|
+
return senderDevice.trim() === String(client._deviceId).trim();
|
|
1334
|
+
}
|
|
1333
1335
|
async runPushProcessSerialized(ns, operation, inlineGeneration) {
|
|
1334
1336
|
const client = this.runtime.client;
|
|
1335
1337
|
if (inlineGeneration === undefined) {
|
|
@@ -1397,7 +1399,7 @@ export class MessageDeliveryEngine {
|
|
|
1397
1399
|
? `p2p:${client._aid}`
|
|
1398
1400
|
: '';
|
|
1399
1401
|
const inlineGeneration = isJsonObject(data)
|
|
1400
|
-
&&
|
|
1402
|
+
&& hasNonEmptyInlineMessage(data.inline_message)
|
|
1401
1403
|
? this.captureInlineAckGeneration() : undefined;
|
|
1402
1404
|
this.runPushProcessSerialized(ns, () => this.processAndPublishMessage(data, inlineGeneration), inlineGeneration).catch((exc) => {
|
|
1403
1405
|
client._clientLog.warn(`P2P message decrypt failed: ${formatDeliveryError(exc)}`);
|
|
@@ -1432,20 +1434,9 @@ export class MessageDeliveryEngine {
|
|
|
1432
1434
|
const seqNum = Number(seq);
|
|
1433
1435
|
if (Number.isFinite(seqNum) && seqNum > 0)
|
|
1434
1436
|
client._seqTracker.updateMaxSeen(ns, seqNum);
|
|
1435
|
-
|
|
1436
|
-
const published = await this.publishOrderedMessage('message.recalled', ns, seq, msg);
|
|
1437
|
-
const contigAfter = client._seqTracker.getContiguousSeq(ns);
|
|
1438
|
-
if (Number(seq) > contigAfter && !published && client._sessionOptions?.background_sync !== false) {
|
|
1437
|
+
if (Number(seq) > client._seqTracker.getContiguousSeq(ns) && client._sessionOptions?.background_sync !== false) {
|
|
1439
1438
|
this.fillP2pGap().catch((exc) => client._clientLog.warn(`background recall gap fill failed: ${formatDeliveryError(exc)}`));
|
|
1440
1439
|
}
|
|
1441
|
-
const contig = client._seqTracker.getContiguousSeq(ns);
|
|
1442
|
-
if (contig > 0) {
|
|
1443
|
-
const ackSeq = this.clampAckSeq('message.ack', 'seq', ns, contig);
|
|
1444
|
-
client._withBackgroundRpc(() => client._ackV2(ackSeq))
|
|
1445
|
-
.catch((e) => { client._clientLog.debug(`P2P recall auto-ack failed: ${formatDeliveryError(e)}`); });
|
|
1446
|
-
}
|
|
1447
|
-
if (contigAfter !== contigBefore)
|
|
1448
|
-
this.persistSeq(ns);
|
|
1449
1440
|
return;
|
|
1450
1441
|
}
|
|
1451
1442
|
await this.publishMessageRecallTombstone(seq, msg);
|
|
@@ -1464,7 +1455,7 @@ export class MessageDeliveryEngine {
|
|
|
1464
1455
|
// 新 SDK 的所有带合法 seq P2P Push 都只作为 Head 通知处理。
|
|
1465
1456
|
// 新服务端为兼容旧 SDK 仍可能发送 message.received;V2 会话下必须
|
|
1466
1457
|
// 在旧 seq tracking / ordered publish 之前转入前台 Tail 协调器。
|
|
1467
|
-
const inlinePresent =
|
|
1458
|
+
const inlinePresent = hasNonEmptyInlineMessage(msg.inline_message);
|
|
1468
1459
|
const legacyPushSeq = this.realtimeHeadSeq(msg.seq, inlinePresent);
|
|
1469
1460
|
if (client._v2Session && client._aid && (msg.seq !== undefined && msg.seq !== null || inlinePresent)) {
|
|
1470
1461
|
if (legacyPushSeq === null) {
|
|
@@ -1483,29 +1474,11 @@ export class MessageDeliveryEngine {
|
|
|
1483
1474
|
const ns = `p2p:${client._aid}`;
|
|
1484
1475
|
if (seq > 0)
|
|
1485
1476
|
client._seqTracker.updateMaxSeen(ns, seq);
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
? await client._publishEncryptedPushMessage('message.received', 'message.undecryptable', ns, seq, msg, false)
|
|
1489
|
-
: await this.publishOrderedMessage('message.received', ns, seq, msg);
|
|
1490
|
-
const contigAfter = client._seqTracker.getContiguousSeq(ns);
|
|
1491
|
-
const needPull = Number(seq) > contigAfter && !published;
|
|
1492
|
-
if (needPull && client._sessionOptions?.background_sync !== false) {
|
|
1493
|
-
client._clientLog.debug(`P2P seq gap detected: ns=${ns}, seq=${seq}, contiguous=${contigAfter}`);
|
|
1477
|
+
if (Number(seq) > client._seqTracker.getContiguousSeq(ns) && client._sessionOptions?.background_sync !== false) {
|
|
1478
|
+
client._clientLog.debug(`P2P push triggering Forward: ns=${ns}, seq=${seq}, contiguous=${client._seqTracker.getContiguousSeq(ns)}`);
|
|
1494
1479
|
this.fillP2pGap().catch((exc) => client._clientLog.warn(`background gap fill trigger failed: ${formatDeliveryError(exc)}`));
|
|
1495
1480
|
}
|
|
1496
|
-
|
|
1497
|
-
if (contig > 0) {
|
|
1498
|
-
const maxSeen = client._seqTracker.getMaxSeenSeq(ns);
|
|
1499
|
-
const ackSeq = this.clampAckSeq('message.ack', 'seq', ns, contig);
|
|
1500
|
-
client._clientLog.debug(`P2P push auto-ack send: ns=${ns}, seq=${ackSeq}, contiguous=${contig}, max_seen=${maxSeen}`);
|
|
1501
|
-
client._withBackgroundRpc(() => client._ackV2(ackSeq))
|
|
1502
|
-
.then(() => { client._clientLog.debug(`P2P push auto-ack ok: ns=${ns}, seq=${ackSeq}`); })
|
|
1503
|
-
.catch((e) => { client._clientLog.debug(`P2P auto-ack failed: ${formatDeliveryError(e)}`); });
|
|
1504
|
-
}
|
|
1505
|
-
if (contigAfter !== contigBefore)
|
|
1506
|
-
this.persistSeq(ns);
|
|
1507
|
-
if (encryptedPush)
|
|
1508
|
-
return;
|
|
1481
|
+
return;
|
|
1509
1482
|
}
|
|
1510
1483
|
else {
|
|
1511
1484
|
if (encryptedPush) {
|
|
@@ -1529,7 +1502,7 @@ export class MessageDeliveryEngine {
|
|
|
1529
1502
|
? String(data.group_id ?? '').trim()
|
|
1530
1503
|
: '';
|
|
1531
1504
|
const inlineGeneration = isJsonObject(data)
|
|
1532
|
-
&&
|
|
1505
|
+
&& hasNonEmptyInlineMessage(data.inline_message)
|
|
1533
1506
|
? this.captureInlineAckGeneration() : undefined;
|
|
1534
1507
|
this.runPushProcessSerialized(groupNs ? `group:${groupNs}` : '', () => this.processAndPublishGroupMessage(data, inlineGeneration), inlineGeneration).catch((exc) => {
|
|
1535
1508
|
client._clientLog.warn(`group message decrypt failed: ${formatDeliveryError(exc)}`);
|
|
@@ -1564,7 +1537,7 @@ export class MessageDeliveryEngine {
|
|
|
1564
1537
|
}
|
|
1565
1538
|
// V2 会话中旧 group.message_created 也只是 Head 通知;必须先 Tail,
|
|
1566
1539
|
// 不能让 payload 路径直接推进旧 ordered publish 状态。
|
|
1567
|
-
const inlinePresent =
|
|
1540
|
+
const inlinePresent = hasNonEmptyInlineMessage(msg.inline_message);
|
|
1568
1541
|
const legacyPushSeq = this.realtimeHeadSeq(seq, inlinePresent);
|
|
1569
1542
|
if (client._v2Session && groupId && (seq !== undefined && seq !== null || inlinePresent)) {
|
|
1570
1543
|
if (legacyPushSeq === null) {
|
|
@@ -1589,46 +1562,15 @@ export class MessageDeliveryEngine {
|
|
|
1589
1562
|
const ns = `group:${groupId}`;
|
|
1590
1563
|
if (seq > 0)
|
|
1591
1564
|
client._seqTracker.updateMaxSeen(ns, seq);
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
if (!encryptedPush && this.recallEventFromGroupMessage(msg)) {
|
|
1595
|
-
const published = await this.publishOrderedGroupRecall(ns, seq, msg);
|
|
1596
|
-
const contigAfter = client._seqTracker.getContiguousSeq(ns);
|
|
1597
|
-
const contig = client._seqTracker.getContiguousSeq(ns);
|
|
1598
|
-
if (contig > 0) {
|
|
1599
|
-
const ackSeq = this.clampAckSeq('group.ack_messages', 'msg_seq', ns, contig);
|
|
1600
|
-
client._withBackgroundRpc(() => client._ackGroupV2(groupId, ackSeq))
|
|
1601
|
-
.catch((e) => { client._clientLog.debug(`group recall auto-ack failed: group=${groupId} ${formatDeliveryError(e)}`); });
|
|
1602
|
-
}
|
|
1603
|
-
if (contigAfter !== contigBefore)
|
|
1604
|
-
this.persistSeq(ns);
|
|
1605
|
-
void published;
|
|
1606
|
-
return;
|
|
1607
|
-
}
|
|
1608
|
-
const published = encryptedPush
|
|
1609
|
-
? await client._publishEncryptedPushMessage('group.message_created', 'group.message_undecryptable', ns, seq, msg, true)
|
|
1610
|
-
: await this.publishOrderedMessage('group.message_created', ns, seq, msg);
|
|
1611
|
-
const contigAfter = client._seqTracker.getContiguousSeq(ns);
|
|
1612
|
-
const needPull = Number(seq) > contigAfter && !published;
|
|
1613
|
-
if (needPull && client._sessionOptions?.background_sync !== false) {
|
|
1614
|
-
client._clientLog.debug(`group message seq gap detected: group=${groupId}, seq=${seq}, contiguous=${contigAfter}`);
|
|
1565
|
+
if (Number(seq) > client._seqTracker.getContiguousSeq(ns) && client._sessionOptions?.background_sync !== false) {
|
|
1566
|
+
client._clientLog.debug(`group push triggering Forward: group=${groupId}, seq=${seq}, contiguous=${client._seqTracker.getContiguousSeq(ns)}`);
|
|
1615
1567
|
this.fillGroupGap(groupId).catch((exc) => client._clientLog.warn(`background gap fill trigger failed: ${formatDeliveryError(exc)}`));
|
|
1616
1568
|
}
|
|
1617
|
-
|
|
1618
|
-
if (contig > 0) {
|
|
1619
|
-
const maxSeen = client._seqTracker.getMaxSeenSeq(ns);
|
|
1620
|
-
const ackSeq = this.clampAckSeq('group.ack_messages', 'msg_seq', ns, contig);
|
|
1621
|
-
client._clientLog.debug(`group push auto-ack send: group=${groupId}, ns=${ns}, seq=${ackSeq}, contiguous=${contig}, max_seen=${maxSeen}`);
|
|
1622
|
-
client._withBackgroundRpc(() => client._ackGroupV2(groupId, ackSeq))
|
|
1623
|
-
.then(() => { client._clientLog.debug(`group push auto-ack ok: group=${groupId}, seq=${ackSeq}`); })
|
|
1624
|
-
.catch((e) => { client._clientLog.debug(`group message auto-ack failed: group=${groupId} ${formatDeliveryError(e)}`); });
|
|
1625
|
-
}
|
|
1626
|
-
if (contigAfter !== contigBefore)
|
|
1627
|
-
this.persistSeq(ns);
|
|
1628
|
-
if (encryptedPush)
|
|
1629
|
-
return;
|
|
1569
|
+
return;
|
|
1630
1570
|
}
|
|
1631
1571
|
else {
|
|
1572
|
+
if (this.isSelfSentGroupMessage(msg))
|
|
1573
|
+
return;
|
|
1632
1574
|
if (encryptedPush) {
|
|
1633
1575
|
await client._publishEncryptedPushMessage('group.message_created', 'group.message_undecryptable', '', seq ?? 0, msg, true);
|
|
1634
1576
|
return;
|
|
@@ -1824,9 +1766,9 @@ export class MessageDeliveryEngine {
|
|
|
1824
1766
|
return;
|
|
1825
1767
|
const pageContigBefore = client._seqTracker.getContiguousSeq(ns);
|
|
1826
1768
|
const eventObjects = events.filter((evt) => isJsonObject(evt));
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1769
|
+
client._seqTracker.onPullResult(ns, eventObjects, nextAfterSeq, Number.isSafeInteger(result.contiguous_seq)
|
|
1770
|
+
? result.contiguous_seq
|
|
1771
|
+
: undefined);
|
|
1830
1772
|
const cursor = isJsonObject(result.cursor)
|
|
1831
1773
|
? result.cursor
|
|
1832
1774
|
: null;
|
|
@@ -1907,10 +1849,13 @@ export class MessageDeliveryEngine {
|
|
|
1907
1849
|
targetSeq: Math.max(Number(cursor?.latest_seq ?? 0), Number(result.latest_event_seq ?? 0)),
|
|
1908
1850
|
});
|
|
1909
1851
|
const nextAfter = Math.max(eventSeqs.length > 0 ? Math.max(...eventSeqs) : nextAfterSeq, nextAfterSeq);
|
|
1910
|
-
|
|
1852
|
+
const pageAdvanced = eventObjects.length > 0 && nextAfter > nextAfterSeq;
|
|
1853
|
+
const needsMore = result.has_more === true
|
|
1854
|
+
|| client._seqTracker.getMaxSeenSeq(ns) > nextAfter;
|
|
1855
|
+
if (singlePage && pageAdvanced && needsMore) {
|
|
1911
1856
|
continuationAfterSeq = nextAfter;
|
|
1912
1857
|
}
|
|
1913
|
-
if (
|
|
1858
|
+
if (!pageAdvanced || !needsMore)
|
|
1914
1859
|
break;
|
|
1915
1860
|
nextAfterSeq = nextAfter;
|
|
1916
1861
|
}
|
|
@@ -1973,93 +1918,19 @@ export class MessageDeliveryEngine {
|
|
|
1973
1918
|
this.enqueueOnlineUnreadEventHint(data);
|
|
1974
1919
|
return;
|
|
1975
1920
|
}
|
|
1976
|
-
if (this.isSelfJoinGroupChanged(data)) {
|
|
1977
|
-
const contig = client._seqTracker.getContiguousSeq(ns);
|
|
1978
|
-
const maxSeen = client._seqTracker.getMaxSeenSeq(ns);
|
|
1979
|
-
if (contig === 0 && maxSeen === 0 && eventSeq > 1) {
|
|
1980
|
-
client._clientLog.debug(`group.changed self-join baseline: group=${groupId}, event_seq=${eventSeq}, baseline=${eventSeq - 1}`);
|
|
1981
|
-
client._seqTracker.forceContiguousSeq(ns, eventSeq - 1);
|
|
1982
|
-
}
|
|
1983
|
-
}
|
|
1984
1921
|
const contigBefore = client._seqTracker.getContiguousSeq(ns);
|
|
1985
1922
|
if (eventSeq <= contigBefore || client._pushedSeqs.get(ns)?.has(eventSeq)) {
|
|
1986
1923
|
client._clientLog.debug(`group.changed skipped duplicate/stale: group=${groupId}, event_seq=${eventSeq}, contiguous=${contigBefore}`);
|
|
1987
|
-
this.fireGroupEventAck(groupId, Math.min(eventSeq, contigBefore > 0 ? contigBefore : eventSeq), 'covered push');
|
|
1988
1924
|
return;
|
|
1989
1925
|
}
|
|
1990
|
-
|
|
1991
|
-
needPull =
|
|
1992
|
-
|
|
1993
|
-
await this.drainOrderedMessages(ns);
|
|
1994
|
-
if (ackContig > 0 && ackContig !== contigBefore) {
|
|
1995
|
-
if (data.action !== 'dissolved')
|
|
1996
|
-
this.persistSeq(ns);
|
|
1997
|
-
client._transport.call('group.ack_events', {
|
|
1998
|
-
group_id: groupId,
|
|
1999
|
-
event_seq: ackContig,
|
|
2000
|
-
device_id: client._deviceId,
|
|
2001
|
-
slot_id: client._slotId,
|
|
2002
|
-
}, undefined, undefined, true).catch((e) => {
|
|
2003
|
-
client._clientLog.debug(`group event push auto-ack failed: group=${groupId} ${formatDeliveryError(e)}`);
|
|
2004
|
-
});
|
|
2005
|
-
}
|
|
2006
|
-
if (needPull && groupId && !data._from_gap_fill && client._sessionOptions?.background_sync !== false) {
|
|
1926
|
+
client._seqTracker.updateMaxSeen(ns, eventSeq);
|
|
1927
|
+
needPull = eventSeq > contigBefore;
|
|
1928
|
+
if (needPull && !data._from_gap_fill && client._sessionOptions?.background_sync !== false) {
|
|
2007
1929
|
this.fillGroupEventGap(groupId).catch((exc) => {
|
|
2008
1930
|
client._clientLog.warn(`background gap fill trigger failed: ${formatDeliveryError(exc)}`);
|
|
2009
1931
|
});
|
|
2010
1932
|
}
|
|
2011
1933
|
}
|
|
2012
|
-
fireGroupEventAck(groupId, eventSeq, reason) {
|
|
2013
|
-
const client = this.runtime.client;
|
|
2014
|
-
const gid = String(groupId ?? '').trim();
|
|
2015
|
-
if (!gid)
|
|
2016
|
-
return;
|
|
2017
|
-
const ns = `group_event:${gid}`;
|
|
2018
|
-
const ackSeq = this.clampAckSeq('group.ack_events', 'event_seq', ns, Number(eventSeq) || 0);
|
|
2019
|
-
if (ackSeq <= 0)
|
|
2020
|
-
return;
|
|
2021
|
-
const params = {
|
|
2022
|
-
group_id: gid,
|
|
2023
|
-
event_seq: ackSeq,
|
|
2024
|
-
device_id: client._deviceId,
|
|
2025
|
-
slot_id: client._slotId,
|
|
2026
|
-
_rpc_background: true,
|
|
2027
|
-
};
|
|
2028
|
-
try {
|
|
2029
|
-
Promise.resolve(client.call('group.ack_events', params)).catch((e) => {
|
|
2030
|
-
client._clientLog.debug(`group event ${reason} ack failed: group=${gid} ${formatDeliveryError(e)}`);
|
|
2031
|
-
});
|
|
2032
|
-
}
|
|
2033
|
-
catch (e) {
|
|
2034
|
-
client._clientLog.debug(`group event ${reason} ack failed: group=${gid} ${formatDeliveryError(e)}`);
|
|
2035
|
-
}
|
|
2036
|
-
}
|
|
2037
|
-
fireGroupV2Ack(groupId, upToSeq, reason, groupAid = '') {
|
|
2038
|
-
const client = this.runtime.client;
|
|
2039
|
-
const gid = String(groupId ?? '').trim();
|
|
2040
|
-
if (!gid)
|
|
2041
|
-
return;
|
|
2042
|
-
const ns = `group:${gid}`;
|
|
2043
|
-
const ackSeq = this.clampAckSeq('group.v2.ack', 'up_to_seq', ns, Number(upToSeq) || 0);
|
|
2044
|
-
if (ackSeq <= 0)
|
|
2045
|
-
return;
|
|
2046
|
-
const generation = this.inlineAckGeneration;
|
|
2047
|
-
try {
|
|
2048
|
-
Promise.resolve(client.call('group.v2.ack', {
|
|
2049
|
-
group_id: gid,
|
|
2050
|
-
...(groupAid ? { group_aid: groupAid } : {}),
|
|
2051
|
-
up_to_seq: ackSeq,
|
|
2052
|
-
_rpc_background: true,
|
|
2053
|
-
})).then((result) => {
|
|
2054
|
-
this.onGroupV2AckResult(gid, result, ackSeq, generation);
|
|
2055
|
-
}).catch((e) => {
|
|
2056
|
-
client._clientLog.debug(`group.v2 ${reason} ack failed: group=${gid} ${formatDeliveryError(e)}`);
|
|
2057
|
-
});
|
|
2058
|
-
}
|
|
2059
|
-
catch (e) {
|
|
2060
|
-
client._clientLog.debug(`group.v2 ${reason} ack failed: group=${gid} ${formatDeliveryError(e)}`);
|
|
2061
|
-
}
|
|
2062
|
-
}
|
|
2063
1934
|
isSelfJoinGroupChanged(data) {
|
|
2064
1935
|
const action = String(data.action ?? '').trim();
|
|
2065
1936
|
if (!['member_added', 'joined', 'join_approved', 'invite_code_used'].includes(action))
|
|
@@ -2356,15 +2227,17 @@ export class MessageDeliveryEngine {
|
|
|
2356
2227
|
const state = this.syncState(ns);
|
|
2357
2228
|
const needsForward = state.ack < state.tail - 1;
|
|
2358
2229
|
const backgroundEnabled = client._sessionOptions?.background_sync !== false;
|
|
2359
|
-
// Tail 期间到达的新 Push
|
|
2230
|
+
// Tail 期间到达的新 Push 只记录在 pending Pull 上界,不写入 SeqTracker。
|
|
2360
2231
|
const maxSeen = Number(client._seqTracker.getMaxSeenSeq?.(ns) ?? 0);
|
|
2361
|
-
const
|
|
2232
|
+
const pendingUpper = Number(client._pendingP2pPullUpper?.get?.(ns) ?? 0);
|
|
2233
|
+
const observedUpper = Math.max(maxSeen, pendingUpper);
|
|
2234
|
+
const tailMissedDuringPull = tailHead !== null && observedUpper > state.head;
|
|
2362
2235
|
const tailForward = tailHead !== null && (needsForward || tailMissedDuringPull);
|
|
2363
2236
|
const backgroundWindowForward = (order.length === 1 && order[0] === 'forward' && !headForward)
|
|
2364
2237
|
|| (order.length === 1 && order[0] === 'tail' && tailHead === null && needsForward);
|
|
2365
2238
|
const foregroundForward = headForward || tailForward;
|
|
2366
2239
|
if (foregroundForward || backgroundEnabled && backgroundWindowForward) {
|
|
2367
|
-
const forwardTarget = Math.max(headForward ? pushSeq : 0, tailForward || backgroundWindowForward ? state.tail - 1 : 0, tailMissedDuringPull ?
|
|
2240
|
+
const forwardTarget = Math.max(headForward ? pushSeq : 0, tailForward || backgroundWindowForward ? state.tail - 1 : 0, tailMissedDuringPull ? observedUpper : 0);
|
|
2368
2241
|
const requiredPages = Math.max(1, Math.ceil(Math.max(0, forwardTarget - state.ack) / pageLimit));
|
|
2369
2242
|
result = await run({
|
|
2370
2243
|
after_seq: state.ack,
|
|
@@ -2432,13 +2305,15 @@ export class MessageDeliveryEngine {
|
|
|
2432
2305
|
const needsForward = state.ack < state.tail - 1;
|
|
2433
2306
|
const backgroundEnabled = client._sessionOptions?.background_sync !== false;
|
|
2434
2307
|
const maxSeen = Number(client._seqTracker.getMaxSeenSeq?.(ns) ?? 0);
|
|
2435
|
-
const
|
|
2308
|
+
const pendingUpper = this.pendingPullUpper(ns);
|
|
2309
|
+
const observedUpper = Math.max(maxSeen, pendingUpper);
|
|
2310
|
+
const tailMissedDuringPull = tailHead !== null && observedUpper > state.head;
|
|
2436
2311
|
const tailForward = tailHead !== null && (needsForward || tailMissedDuringPull);
|
|
2437
2312
|
const backgroundWindowForward = (order.length === 1 && order[0] === 'forward' && !headForward)
|
|
2438
2313
|
|| (order.length === 1 && order[0] === 'tail' && tailHead === null && needsForward);
|
|
2439
2314
|
const foregroundForward = headForward || tailForward;
|
|
2440
2315
|
if (foregroundForward || backgroundEnabled && backgroundWindowForward) {
|
|
2441
|
-
const forwardTarget = Math.max(headForward ? pushSeq : 0, tailForward || backgroundWindowForward ? state.tail - 1 : 0, tailMissedDuringPull ?
|
|
2316
|
+
const forwardTarget = Math.max(headForward ? pushSeq : 0, tailForward || backgroundWindowForward ? state.tail - 1 : 0, tailMissedDuringPull ? observedUpper : 0);
|
|
2442
2317
|
const requiredPages = Math.max(1, Math.ceil(Math.max(0, forwardTarget - state.ack) / pageLimit));
|
|
2443
2318
|
result = await run({
|
|
2444
2319
|
after_seq: state.ack,
|
|
@@ -3048,7 +2923,7 @@ export class MessageDeliveryEngine {
|
|
|
3048
2923
|
this.enqueueOnlineUnreadP2pHint(pushData);
|
|
3049
2924
|
return;
|
|
3050
2925
|
}
|
|
3051
|
-
const inlinePresent =
|
|
2926
|
+
const inlinePresent = hasNonEmptyInlineMessage(pushData.inline_message);
|
|
3052
2927
|
const pushSeq = this.realtimeHeadSeq(rawPushSeq, inlinePresent);
|
|
3053
2928
|
if (pushSeq === null) {
|
|
3054
2929
|
if (inlinePresent) {
|
|
@@ -3079,7 +2954,8 @@ export class MessageDeliveryEngine {
|
|
|
3079
2954
|
client._seqTracker.updateMaxSeen(ns, pushSeq);
|
|
3080
2955
|
if (pushSeq <= before.ack && before.ack >= before.tail - 1) {
|
|
3081
2956
|
client._clientLog.debug(`V2 P2P duplicate push ignored: seq=${pushSeq} A/T/H=${JSON.stringify(before)}`);
|
|
3082
|
-
|
|
2957
|
+
if (inlinePresent)
|
|
2958
|
+
this.retryPendingP2PInlineAck(ns, pushSeq, 'covered inline push');
|
|
3083
2959
|
return;
|
|
3084
2960
|
}
|
|
3085
2961
|
const pullActive = client._rpcPipeline?.hasPullActivity?.(ns) === true;
|
|
@@ -3152,7 +3028,7 @@ export class MessageDeliveryEngine {
|
|
|
3152
3028
|
client._logMessageDebug('server-push', '_raw.group.v2.message_created', 'group.message_created', d);
|
|
3153
3029
|
const groupId = typeof d.group_id === 'string' ? d.group_id.trim() : '';
|
|
3154
3030
|
const rawSeq = d.seq;
|
|
3155
|
-
const inlinePresent =
|
|
3031
|
+
const inlinePresent = hasNonEmptyInlineMessage(d.inline_message);
|
|
3156
3032
|
const seq = this.realtimeHeadSeq(rawSeq, inlinePresent);
|
|
3157
3033
|
if (!groupId) {
|
|
3158
3034
|
client._clientLog.debug(`_onRawGroupV2MessageCreated skipped: group=${groupId || '<empty>'}, seq=${String(d.seq ?? '')}`);
|
|
@@ -3197,12 +3073,9 @@ export class MessageDeliveryEngine {
|
|
|
3197
3073
|
if (seq <= before.ack && before.ack >= before.tail - 1) {
|
|
3198
3074
|
client._clientLog.debug(`V2 group duplicate push ignored: group=${groupId} seq=${seq} A/T/H=${JSON.stringify(before)}`);
|
|
3199
3075
|
const pending = this.pendingGroupInlineAcks?.get(ns);
|
|
3200
|
-
if (pending?.seq === seq) {
|
|
3076
|
+
if (inlinePresent && pending?.seq === seq) {
|
|
3201
3077
|
this.retryPendingGroupInlineAck(ns, seq, 'covered inline push');
|
|
3202
3078
|
}
|
|
3203
|
-
else if (!inlinePresent) {
|
|
3204
|
-
this.fireGroupV2Ack(groupId, seq, 'covered push');
|
|
3205
|
-
}
|
|
3206
3079
|
return;
|
|
3207
3080
|
}
|
|
3208
3081
|
const pullActive = client._rpcPipeline?.hasPullActivity?.(ns) === true;
|
|
@@ -3752,32 +3625,6 @@ export class MessageDeliveryEngine {
|
|
|
3752
3625
|
}
|
|
3753
3626
|
return true;
|
|
3754
3627
|
}
|
|
3755
|
-
async publishOrderedGroupRecall(ns, seq, message) {
|
|
3756
|
-
const client = this.runtime.client;
|
|
3757
|
-
this.ensurePullOperationCurrent();
|
|
3758
|
-
const seqNum = Number(seq);
|
|
3759
|
-
if (!Number.isFinite(seqNum) || !Number.isInteger(seqNum) || seqNum <= 0) {
|
|
3760
|
-
await this.publishGroupRecallTombstone(ns.replace(/^group:/, ''), seq, message);
|
|
3761
|
-
this.ensurePullOperationCurrent();
|
|
3762
|
-
return true;
|
|
3763
|
-
}
|
|
3764
|
-
// 撤回 tombstone 也占 seq:推进 contiguous 并标记已发布,确保 ack 正常推进、不留空洞。
|
|
3765
|
-
if (client._pushedSeqs.get(ns)?.has(seqNum)) {
|
|
3766
|
-
await this.publishGroupRecallTombstone(ns.replace(/^group:/, ''), seq, message);
|
|
3767
|
-
return false;
|
|
3768
|
-
}
|
|
3769
|
-
client._seqTracker.onMessageSeq(ns, seqNum);
|
|
3770
|
-
await this.publishGroupRecallTombstone(ns.replace(/^group:/, ''), seq, message);
|
|
3771
|
-
this.ensurePullOperationCurrent();
|
|
3772
|
-
this.markPublishedSeq(ns, seqNum);
|
|
3773
|
-
client._markOrderedSeqDelivered?.(ns, seqNum);
|
|
3774
|
-
await this.drainOrderedMessages(ns);
|
|
3775
|
-
if (!client._pendingOrderedMsgs.get(ns)) {
|
|
3776
|
-
this.ensurePullOperationCurrent();
|
|
3777
|
-
await this.saveSeqTrackerState();
|
|
3778
|
-
}
|
|
3779
|
-
return true;
|
|
3780
|
-
}
|
|
3781
3628
|
async publishPulledMessage(event, ns, seq, payload, persist = true, source = 'pull') {
|
|
3782
3629
|
const client = this.runtime.client;
|
|
3783
3630
|
this.ensurePullOperationCurrent();
|