@agentunion/fastaun-browser 0.5.11 → 0.5.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/_packed_docs/CHANGELOG.md +30 -0
  3. package/_packed_docs/INDEX.md +2 -2
  4. package/_packed_docs/KITE_DOCS_GUIDE.md +1 -1
  5. package/_packed_docs/protocol/10-Group-/345/255/220/345/215/217/350/256/256.md +21 -3
  6. package/_packed_docs/sdk/09-group-rpc-manual.md +29 -2
  7. package/dist/bundle.js +559 -295
  8. package/dist/client/delivery.d.ts +1 -0
  9. package/dist/client/delivery.d.ts.map +1 -1
  10. package/dist/client/delivery.js +161 -242
  11. package/dist/client/delivery.js.map +1 -1
  12. package/dist/client/group-state.d.ts.map +1 -1
  13. package/dist/client/group-state.js +1 -0
  14. package/dist/client/group-state.js.map +1 -1
  15. package/dist/client/rpc-pipeline.d.ts.map +1 -1
  16. package/dist/client/rpc-pipeline.js +24 -11
  17. package/dist/client/rpc-pipeline.js.map +1 -1
  18. package/dist/client/v2-e2ee.d.ts +0 -1
  19. package/dist/client/v2-e2ee.d.ts.map +1 -1
  20. package/dist/client/v2-e2ee.js +65 -44
  21. package/dist/client/v2-e2ee.js.map +1 -1
  22. package/dist/client.d.ts +14 -0
  23. package/dist/client.d.ts.map +1 -1
  24. package/dist/client.js +341 -18
  25. package/dist/client.js.map +1 -1
  26. package/dist/facades.d.ts +7 -0
  27. package/dist/facades.d.ts.map +1 -1
  28. package/dist/facades.js +44 -0
  29. package/dist/facades.js.map +1 -1
  30. package/dist/seq-tracker.d.ts +2 -2
  31. package/dist/seq-tracker.d.ts.map +1 -1
  32. package/dist/seq-tracker.js +11 -7
  33. package/dist/seq-tracker.js.map +1 -1
  34. package/dist/tools/cross-sdk-agent.js +50 -12
  35. package/dist/tools/cross-sdk-agent.js.map +1 -1
  36. package/dist/transport.d.ts.map +1 -1
  37. package/dist/transport.js +3 -0
  38. package/dist/transport.js.map +1 -1
  39. package/dist/version.d.ts +1 -1
  40. package/dist/version.js +1 -1
  41. package/package.json +1 -1
package/dist/bundle.js CHANGED
@@ -460,7 +460,7 @@ var init_indexeddb_store = __esm({
460
460
  });
461
461
 
462
462
  // src/version.ts
463
- var VERSION = "0.5.11";
463
+ var VERSION = "0.5.12";
464
464
 
465
465
  // src/types.ts
466
466
  var ConnectionState = /* @__PURE__ */ ((ConnectionState2) => {
@@ -1747,6 +1747,9 @@ var EVENT_NAME_MAP = {
1747
1747
  "message.recalled": "message.recalled",
1748
1748
  "message.ack": "message.ack",
1749
1749
  "group.changed": "group.changed",
1750
+ "group.invite_created": "group.invite_created",
1751
+ "group.invite_received": "group.invite_received",
1752
+ "group.invite_finalized": "group.invite_finalized",
1750
1753
  "group.message_created": "group.message_created",
1751
1754
  "group.message_recalled": "group.message_recalled",
1752
1755
  "group.state_committed": "group.state_committed",
@@ -4203,7 +4206,6 @@ var SeqTracker = class {
4203
4206
  if (headSeq === t.headSeq) {
4204
4207
  if (windowStartSeq <= t.tailSeq) return false;
4205
4208
  t.tailSeq = windowStartSeq;
4206
- this._normalizeWindow(t);
4207
4209
  return true;
4208
4210
  }
4209
4211
  if (t.headSeq + pageLimit < headSeq && windowStartSeq <= t.headSeq + 1) {
@@ -4212,7 +4214,6 @@ var SeqTracker = class {
4212
4214
  if (windowStartSeq > t.headSeq + 1) t.tailSeq = windowStartSeq;
4213
4215
  t.headSeq = headSeq;
4214
4216
  t.maxSeenSeq = Math.max(t.maxSeenSeq, headSeq);
4215
- this._normalizeWindow(t);
4216
4217
  return true;
4217
4218
  }
4218
4219
  /** Push 专用:只扩展上界 maxSeenSeq,不动 contiguousSeq。 */
@@ -4287,8 +4288,8 @@ var SeqTracker = class {
4287
4288
  }
4288
4289
  return true;
4289
4290
  }
4290
- /** 提交已校验的 Forward 原始页;页内缺号视为服务端永久空号。 */
4291
- onPullResult(ns, messages, afterSeq) {
4291
+ /** 提交已校验的 Forward 原始页;缺号只有服务端明确证明时才能跳过。 */
4292
+ onPullResult(ns, messages, afterSeq, contiguousSeq) {
4292
4293
  const t = this._get(ns);
4293
4294
  const pulledSeqs = /* @__PURE__ */ new Set();
4294
4295
  for (const m of messages) {
@@ -4318,6 +4319,7 @@ var SeqTracker = class {
4318
4319
  if (maxPulled > 0) {
4319
4320
  t.maxSeenSeq = Math.max(t.maxSeenSeq, maxPulled);
4320
4321
  }
4322
+ this._tryAdvance(t);
4321
4323
  if (maxPulled > t.contiguousSeq) {
4322
4324
  for (const [key, probe] of t.pendingGaps) {
4323
4325
  if (probe.gapEnd <= maxPulled) t.pendingGaps.delete(key);
@@ -4327,7 +4329,11 @@ var SeqTracker = class {
4327
4329
  }
4328
4330
  t.contiguousSeq = maxPulled;
4329
4331
  }
4330
- this._tryAdvance(t);
4332
+ let proof = Number.isSafeInteger(contiguousSeq) && contiguousSeq > 0 ? contiguousSeq : 0;
4333
+ if (proof > 0) {
4334
+ if (proof > t.contiguousSeq) this.forceContiguousSeq(ns, proof);
4335
+ }
4336
+ this._normalizeWindow(t);
4331
4337
  const remaining = [...t.receivedSeqs].filter((seq2) => seq2 > t.contiguousSeq);
4332
4338
  if (remaining.length > 0) {
4333
4339
  const firstPending = Math.min(...remaining);
@@ -4506,7 +4512,6 @@ var SeqTracker = class {
4506
4512
  restored.tailSeq = Number(tailRaw);
4507
4513
  restored.headSeq = Number(headRaw);
4508
4514
  restored.maxSeenSeq = Math.max(ack, restored.headSeq);
4509
- this._normalizeWindow(restored);
4510
4515
  this._trackers.set(ns, restored);
4511
4516
  }
4512
4517
  return issues;
@@ -5006,6 +5011,9 @@ function positiveSafeSequenceHint(value) {
5006
5011
  const parsed = Number(value);
5007
5012
  return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : 0;
5008
5013
  }
5014
+ function hasNonEmptyInlineMessage(value) {
5015
+ return isJsonObject(value) && Object.keys(value).length > 0;
5016
+ }
5009
5017
  function nonNegativeSafeSequenceHint(value) {
5010
5018
  if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) return null;
5011
5019
  return value;
@@ -5794,13 +5802,17 @@ var MessageDeliveryEngine = class {
5794
5802
  this.markPublishedSeq(ns, seq2);
5795
5803
  publishedCount += 1;
5796
5804
  }
5805
+ } else if (this.isSelfSentGroupMessage(message)) {
5806
+ this.markPublishedSeq(ns, seq2);
5797
5807
  } else if (await this.publishPulledMessage("group.message_created", ns, seq2, message, false)) {
5798
5808
  this.ensurePullOperationCurrent();
5799
5809
  publishedCount += 1;
5800
5810
  }
5801
5811
  }
5802
5812
  this.ensurePullOperationCurrent();
5803
- if (messages.length > 0) client._seqTracker.onPullResult(ns, messages, afterSeq);
5813
+ const contiguousSeq = Number.isSafeInteger(result.contiguous_seq) ? result.contiguous_seq : void 0;
5814
+ if (contiguousSeq === void 0) client._seqTracker.onPullResult(ns, messages, afterSeq);
5815
+ else client._seqTracker.onPullResult(ns, messages, afterSeq, contiguousSeq);
5804
5816
  const commitTarget = Math.max(
5805
5817
  client._seqTracker.getContiguousSeq(ns),
5806
5818
  retentionFloor,
@@ -6296,38 +6308,9 @@ var MessageDeliveryEngine = class {
6296
6308
  return;
6297
6309
  }
6298
6310
  const ns = `group:${groupId}`;
6299
- if (seqNum > 0) {
6300
- client._seqTracker.updateMaxSeen(ns, seqNum);
6301
- if (client._seqTracker.getContiguousSeq(ns) === seqNum) {
6302
- await this.publishGroupRecallTombstone(groupId, seq2, wrapped);
6303
- return;
6304
- }
6305
- client._repairPushContiguousBound(ns, seqNum, true, "_raw.group.message_recalled");
6306
- }
6307
- const pushed = client._pushedSeqs.get(ns);
6308
- const pending = client._pendingOrderedMsgs.get(ns);
6309
- if (pushed?.has(seqNum) || pending?.has(seqNum)) {
6310
- await this.publishGroupRecallTombstone(groupId, seq2, wrapped);
6311
- return;
6312
- }
6313
- const contigBefore = client._seqTracker.getContiguousSeq(ns);
6314
- client._seqTracker.onMessageSeq(ns, seqNum);
6311
+ client._seqTracker.updateMaxSeen(ns, seqNum);
6312
+ if (seqNum > client._seqTracker.getContiguousSeq(ns) && client._sessionOptions?.background_sync !== false) client._safeAsync(this.fillGroupGap(groupId));
6315
6313
  await this.publishGroupRecallTombstone(groupId, seq2, wrapped);
6316
- this.markPublishedSeq(ns, seqNum);
6317
- const contig = client._seqTracker.getContiguousSeq(ns);
6318
- if (contig > 0) {
6319
- const ackSeq = this.clampAckSeq("group.ack_messages", "msg_seq", ns, contig);
6320
- client._transport.call("group.ack_messages", {
6321
- group_id: groupId,
6322
- msg_seq: ackSeq,
6323
- device_id: client._deviceId,
6324
- slot_id: client._slotId,
6325
- _rpc_background: true
6326
- }).catch((e) => {
6327
- client._clientLog.warn("group recall auto-ack failed: group=" + groupId, e);
6328
- });
6329
- }
6330
- if (contig !== contigBefore) this.persistSeq(ns);
6331
6314
  }
6332
6315
  async publishAppEvent(event, payload, source = "direct", ns = "", batch) {
6333
6316
  const client = this.runtime.client;
@@ -6392,6 +6375,16 @@ var MessageDeliveryEngine = class {
6392
6375
  }
6393
6376
  return true;
6394
6377
  }
6378
+ isSelfSentGroupMessage(message) {
6379
+ if (!isJsonObject(message)) return false;
6380
+ const client = this.runtime.client;
6381
+ const selfAid = String(client._aid ?? "").trim().toLowerCase();
6382
+ const senderAid = String(message.sender_aid ?? message.from_aid ?? message.from ?? "").trim().toLowerCase();
6383
+ if (!selfAid || senderAid !== selfAid) return false;
6384
+ const senderDevice = Object.prototype.hasOwnProperty.call(message, "sender_device_id") ? message.sender_device_id : message.from_device_id;
6385
+ if (typeof senderDevice !== "string" || !senderDevice.trim() || !String(client._deviceId ?? "").trim()) return false;
6386
+ return senderDevice.trim() === String(client._deviceId).trim();
6387
+ }
6395
6388
  strictTargetString(source, key) {
6396
6389
  if (!Object.prototype.hasOwnProperty.call(source, key)) {
6397
6390
  return { present: false, valid: false, value: "" };
@@ -6452,11 +6445,11 @@ var MessageDeliveryEngine = class {
6452
6445
  const client = this.runtime.client;
6453
6446
  client._clientLog.debug(`_onRawMessageReceived enter: from=${data?.from ?? "-"} mid=${data?.message_id ?? "-"} seq=${data?.seq ?? "-"}`);
6454
6447
  const ns = isJsonObject(data) && client._aid && data.seq !== void 0 ? `p2p:${client._aid}` : "";
6455
- const seq2 = isJsonObject(data) && typeof data.seq === "number" ? data.seq : 0;
6456
- if (client._v2Session && ns && Number.isSafeInteger(seq2) && seq2 > 0 && this.messageTargetsCurrentInstance(data)) {
6448
+ const seq2 = isJsonObject(data) ? positiveSafeSequenceHint(data.seq) : 0;
6449
+ if (client._v2Session && ns && seq2 > 0 && this.messageTargetsCurrentInstance(data)) {
6457
6450
  client._seqTracker.updateMaxSeen(ns, seq2);
6458
6451
  }
6459
- const hasInlineObject = isJsonObject(data) && isJsonObject(data.inline_message);
6452
+ const hasInlineObject = isJsonObject(data) && hasNonEmptyInlineMessage(data.inline_message);
6460
6453
  const inlineGeneration = hasInlineObject ? this.captureInlineGeneration() : void 0;
6461
6454
  const operation = () => this.processAndPublishMessage(data, inlineGeneration);
6462
6455
  client._safeAsync(client._v2Session && ns && !hasInlineObject ? operation() : this.runPushProcessSerialized(ns, operation, inlineGeneration));
@@ -6468,33 +6461,13 @@ var MessageDeliveryEngine = class {
6468
6461
  const msg = { ...data };
6469
6462
  if (!("type" in msg)) msg.type = "message.recalled";
6470
6463
  if (!this.messageTargetsCurrentInstance(msg)) return;
6471
- const seq2 = msg.seq;
6472
- if (seq2 !== void 0 && seq2 !== null && client._aid) {
6464
+ const seq2 = positiveSafeSequenceHint(msg.seq);
6465
+ if (client._aid && seq2 > 0) {
6473
6466
  const ns = `p2p:${client._aid}`;
6474
- if (seq2 > 0) client._seqTracker.updateMaxSeen(ns, seq2);
6475
- const contigBefore = client._seqTracker.getContiguousSeq(ns);
6476
- const seqNeedsPull = client._seqTracker.onMessageSeq(ns, seq2);
6477
- const published = await this.publishOrderedMessage("message.recalled", ns, seq2, msg);
6478
- const contigAfter = client._seqTracker.getContiguousSeq(ns);
6479
- if (seqNeedsPull && !published && client._sessionOptions?.background_sync !== false) {
6480
- client._safeAsync(this.fillP2pGap());
6481
- }
6482
- const contig = client._seqTracker.getContiguousSeq(ns);
6483
- if (contig > 0) {
6484
- const ackSeq = this.clampAckSeq("message.ack", "seq", ns, contig);
6485
- client._transport.call("message.ack", {
6486
- seq: ackSeq,
6487
- device_id: client._deviceId,
6488
- slot_id: client._slotId,
6489
- _rpc_background: true
6490
- }).catch((e) => {
6491
- client._clientLog.warn(`P2P recall auto-ack failed:${String(e)}`);
6492
- });
6493
- }
6494
- if (contigAfter !== contigBefore) this.persistSeq(ns);
6495
- return;
6467
+ client._seqTracker.updateMaxSeen(ns, seq2);
6468
+ if (seq2 > client._seqTracker.getContiguousSeq(ns) && client._sessionOptions?.background_sync !== false) client._safeAsync(this.fillP2pGap());
6496
6469
  }
6497
- await this.publishMessageRecallTombstone(seq2, msg);
6470
+ await this.publishMessageRecallTombstone(msg.seq, msg);
6498
6471
  }
6499
6472
  async processAndPublishMessage(data, inlineGeneration) {
6500
6473
  const client = this.runtime.client;
@@ -6508,43 +6481,32 @@ var MessageDeliveryEngine = class {
6508
6481
  return;
6509
6482
  }
6510
6483
  const legacyPushSeq = positiveSafeSequenceHint(msg.seq);
6511
- const hasInlineField = Object.prototype.hasOwnProperty.call(msg, "inline_message");
6484
+ const hasInlineField = hasNonEmptyInlineMessage(msg.inline_message);
6512
6485
  if (client._v2Session && client._aid && (legacyPushSeq > 0 || hasInlineField)) {
6513
6486
  await this.processV2P2PNotification(msg, "v1", inlineGeneration);
6514
6487
  return;
6515
6488
  }
6489
+ if (client._aid && legacyPushSeq > 0) {
6490
+ const ns2 = `p2p:${client._aid}`;
6491
+ client._seqTracker.updateMaxSeen(ns2, legacyPushSeq);
6492
+ if (client._sessionOptions?.background_sync !== false) client._safeAsync(this.fillP2pGap());
6493
+ return;
6494
+ }
6516
6495
  const seq2 = msg.seq;
6517
6496
  const encryptedPush = client._isEncryptedPushMessage(msg);
6518
- if (seq2 !== void 0 && seq2 !== null && client._aid) {
6519
- const ns = `p2p:${client._aid}`;
6520
- if (seq2 > 0) client._seqTracker.updateMaxSeen(ns, seq2);
6521
- const contigBefore = client._seqTracker.getContiguousSeq(ns);
6522
- const seqNeedsPull = client._seqTracker.onMessageSeq(ns, seq2);
6523
- const published = encryptedPush ? await client._publishEncryptedPushMessage("message.received", "message.undecryptable", ns, seq2, msg, false) : await this.publishOrderedMessage("message.received", ns, seq2, msg);
6524
- const contigAfter = client._seqTracker.getContiguousSeq(ns);
6525
- const needPull = seqNeedsPull && !published;
6526
- if (needPull && client._sessionOptions?.background_sync !== false) {
6527
- client._safeAsync(this.fillP2pGap());
6528
- }
6529
- const contig = client._seqTracker.getContiguousSeq(ns);
6530
- if (contig > 0) {
6531
- const ackSeq = this.clampAckSeq("message.ack", "seq", ns, contig);
6532
- client._transport.call("message.ack", {
6533
- seq: ackSeq,
6534
- device_id: client._deviceId,
6535
- slot_id: client._slotId,
6536
- _rpc_background: true
6537
- }).catch((e) => {
6538
- client._clientLog.warn(`P2P auto-ack failed:${String(e)}`);
6539
- });
6540
- }
6541
- if (contigAfter !== contigBefore) this.persistSeq(ns);
6542
- if (encryptedPush) return;
6497
+ const ns = client._aid ? `p2p:${client._aid}` : "";
6498
+ const seqNum = positiveSafeSequenceHint(seq2);
6499
+ if (ns && seqNum > 0) {
6500
+ client._seqTracker.updateMaxSeen(ns, seqNum);
6501
+ if (seqNum > client._seqTracker.getContiguousSeq(ns) && client._sessionOptions?.background_sync !== false) client._safeAsync(this.fillP2pGap());
6502
+ }
6503
+ if (encryptedPush) {
6504
+ await client._publishEncryptedPushMessage("message.received", "message.undecryptable", ns, seq2 ?? 0, msg, false);
6505
+ return;
6506
+ }
6507
+ if (ns && seq2 !== void 0 && seq2 !== null) {
6508
+ await this.publishOrderedMessage("message.received", ns, seq2, msg);
6543
6509
  } else {
6544
- if (encryptedPush) {
6545
- await client._publishEncryptedPushMessage("message.received", "message.undecryptable", "", seq2 ?? 0, msg, false);
6546
- return;
6547
- }
6548
6510
  await client._publishAppEvent("message.received", msg, "push");
6549
6511
  }
6550
6512
  } catch (exc) {
@@ -6568,13 +6530,13 @@ var MessageDeliveryEngine = class {
6568
6530
  const client = this.runtime.client;
6569
6531
  client._clientLog.debug(`_onRawGroupMessageCreated enter: group_id=${data?.group_id ?? "-"} from=${data?.from ?? "-"} seq=${data?.seq ?? "-"}`);
6570
6532
  const groupId = isJsonObject(data) && typeof data.group_id === "string" ? data.group_id.trim() : "";
6571
- const seq2 = isJsonObject(data) && typeof data.seq === "number" ? data.seq : 0;
6572
6533
  const kind = isJsonObject(data) ? String(data.kind ?? "").trim() : "";
6573
- if (client._v2Session && groupId && kind !== "group.online_unread_hint" && Number.isSafeInteger(seq2) && seq2 > 0) {
6574
- client._seqTracker.updateMaxSeen(`group:${groupId}`, seq2);
6575
- }
6576
6534
  const ns = groupId ? `group:${groupId}` : "";
6577
- const hasInlineObject = isJsonObject(data) && isJsonObject(data.inline_message);
6535
+ const seq2 = isJsonObject(data) ? positiveSafeSequenceHint(data.seq) : 0;
6536
+ if (client._v2Session && ns && kind !== "group.online_unread_hint" && seq2 > 0) {
6537
+ client._seqTracker.updateMaxSeen(ns, seq2);
6538
+ }
6539
+ const hasInlineObject = isJsonObject(data) && hasNonEmptyInlineMessage(data.inline_message);
6578
6540
  const inlineGeneration = hasInlineObject ? this.captureInlineGeneration() : void 0;
6579
6541
  const operation = () => this.processAndPublishGroupMessage(data, inlineGeneration);
6580
6542
  client._safeAsync(client._v2Session && ns && !hasInlineObject ? operation() : this.runPushProcessSerialized(ns, operation, inlineGeneration));
@@ -6595,69 +6557,56 @@ var MessageDeliveryEngine = class {
6595
6557
  client._groupSynced.add(groupId);
6596
6558
  }
6597
6559
  const legacyPushSeq = positiveSafeSequenceHint(seq2);
6598
- const hasInlineField = Object.prototype.hasOwnProperty.call(msg, "inline_message");
6560
+ const hasInlineField = hasNonEmptyInlineMessage(msg.inline_message);
6599
6561
  if (client._v2Session && groupId && (legacyPushSeq > 0 || hasInlineField)) {
6600
6562
  await this.processGroupV2MessageCreated(msg, "v1", inlineGeneration);
6601
6563
  return;
6602
6564
  }
6565
+ if (groupId && legacyPushSeq > 0) {
6566
+ const ns2 = `group:${groupId}`;
6567
+ client._seqTracker.updateMaxSeen(ns2, legacyPushSeq);
6568
+ if (client._sessionOptions?.background_sync !== false) client._safeAsync(this.fillGroupGap(groupId));
6569
+ return;
6570
+ }
6603
6571
  if (payload === void 0 || payload === null || typeof payload === "object" && Object.keys(payload).length === 0) {
6604
- await this.autoPullGroupMessages(msg);
6572
+ if (client._v2Session) await this.autoPullGroupMessages(msg);
6573
+ else if (groupId && seq2 !== void 0 && seq2 !== null) {
6574
+ const ns2 = `group:${groupId}`;
6575
+ const seqNum2 = positiveSafeSequenceHint(seq2);
6576
+ if (seqNum2 > 0) {
6577
+ client._seqTracker.updateMaxSeen(ns2, seqNum2);
6578
+ if (seqNum2 > client._seqTracker.getContiguousSeq(ns2) && client._sessionOptions?.background_sync !== false) client._safeAsync(this.fillGroupGap(groupId));
6579
+ }
6580
+ await this.publishOrderedMessage("group.message_created", ns2, seq2, msg);
6581
+ } else await client._publishAppEvent("group.message_created", msg, "push");
6605
6582
  return;
6606
6583
  }
6607
6584
  const encryptedPush = client._isEncryptedPushMessage(msg);
6608
- if (groupId && seq2 !== void 0 && seq2 !== null) {
6609
- const ns = `group:${groupId}`;
6610
- if (seq2 > 0) client._seqTracker.updateMaxSeen(ns, seq2);
6611
- const contigBefore = client._seqTracker.getContiguousSeq(ns);
6612
- const seqNeedsPull = client._seqTracker.onMessageSeq(ns, seq2);
6613
- if (!encryptedPush && this.recallEventFromGroupMessage(msg)) {
6614
- await this.publishGroupRecallTombstone(groupId, seq2, msg);
6615
- this.markPublishedSeq(ns, Number(seq2));
6616
- const contigAfter2 = client._seqTracker.getContiguousSeq(ns);
6617
- const contig2 = client._seqTracker.getContiguousSeq(ns);
6618
- if (contig2 > 0) {
6619
- const ackSeq = this.clampAckSeq("group.ack_messages", "msg_seq", ns, contig2);
6620
- client._transport.call("group.ack_messages", {
6621
- group_id: groupId,
6622
- msg_seq: ackSeq,
6623
- device_id: client._deviceId,
6624
- slot_id: client._slotId,
6625
- _rpc_background: true
6626
- }).catch((e) => {
6627
- client._clientLog.warn("group recall auto-ack failed: group=" + groupId, e);
6628
- });
6629
- }
6630
- if (contigAfter2 !== contigBefore) this.persistSeq(ns);
6631
- return;
6632
- }
6633
- const published = encryptedPush ? await client._publishEncryptedPushMessage("group.message_created", "group.message_undecryptable", ns, seq2, msg, true) : await this.publishOrderedMessage("group.message_created", ns, seq2, msg);
6634
- const contigAfter = client._seqTracker.getContiguousSeq(ns);
6635
- const needPull = seqNeedsPull && !published;
6636
- if (needPull && client._sessionOptions?.background_sync !== false) {
6637
- client._safeAsync(this.fillGroupGap(groupId));
6638
- }
6639
- const contig = client._seqTracker.getContiguousSeq(ns);
6640
- if (contig > 0) {
6641
- const ackSeq = this.clampAckSeq("group.ack_messages", "msg_seq", ns, contig);
6642
- client._transport.call("group.ack_messages", {
6643
- group_id: groupId,
6644
- msg_seq: ackSeq,
6645
- device_id: client._deviceId,
6646
- slot_id: client._slotId,
6647
- _rpc_background: true
6648
- }).catch((e) => {
6649
- client._clientLog.warn("group message auto-ack failed: group=" + groupId, e);
6650
- });
6651
- }
6652
- if (contigAfter !== contigBefore) this.persistSeq(ns);
6653
- if (encryptedPush) return;
6654
- } else {
6655
- if (encryptedPush) {
6656
- await client._publishEncryptedPushMessage("group.message_created", "group.message_undecryptable", "", seq2 ?? 0, msg, true);
6657
- return;
6658
- }
6659
- await client._publishAppEvent("group.message_created", msg, "push");
6585
+ const ns = groupId ? `group:${groupId}` : "";
6586
+ const seqNum = positiveSafeSequenceHint(seq2);
6587
+ if (ns && seqNum > 0) {
6588
+ client._seqTracker.updateMaxSeen(ns, seqNum);
6589
+ if (seqNum > client._seqTracker.getContiguousSeq(ns) && client._sessionOptions?.background_sync !== false) client._safeAsync(this.fillGroupGap(groupId));
6590
+ }
6591
+ if (this.isSelfSentGroupMessage(msg)) return;
6592
+ if (!encryptedPush && this.recallEventFromGroupMessage(msg)) {
6593
+ await this.publishGroupRecallTombstone(groupId, seq2, msg);
6594
+ return;
6660
6595
  }
6596
+ if (encryptedPush) {
6597
+ await client._publishEncryptedPushMessage(
6598
+ "group.message_created",
6599
+ "group.message_undecryptable",
6600
+ ns,
6601
+ seq2 ?? 0,
6602
+ msg,
6603
+ true
6604
+ );
6605
+ return;
6606
+ }
6607
+ if (ns && seq2 !== void 0 && seq2 !== null) {
6608
+ await this.publishOrderedMessage("group.message_created", ns, seq2, msg);
6609
+ } else await client._publishAppEvent("group.message_created", msg, "push");
6661
6610
  } catch (exc) {
6662
6611
  client._clientLog.warn(`group push processing failed:${String(exc)}`);
6663
6612
  if (isJsonObject(data)) {
@@ -6854,6 +6803,7 @@ var MessageDeliveryEngine = class {
6854
6803
  client._gapFillDone.add(dedupKey);
6855
6804
  this.runtime.delivery.setGapFillActive(true);
6856
6805
  let continuationAfterSeq = 0;
6806
+ let pendingAckSeq = 0;
6857
6807
  let failed = false;
6858
6808
  try {
6859
6809
  let nextAfterSeq = afterSeq;
@@ -6874,9 +6824,9 @@ var MessageDeliveryEngine = class {
6874
6824
  if (!Array.isArray(events)) return;
6875
6825
  const pageContigBefore = client._seqTracker.getContiguousSeq(ns);
6876
6826
  const eventObjects = events.filter((evt) => isJsonObject(evt));
6877
- if (eventObjects.length > 0) {
6878
- client._seqTracker.onPullResult(ns, eventObjects, nextAfterSeq);
6879
- }
6827
+ const contiguousSeq = Number.isSafeInteger(result.contiguous_seq) ? result.contiguous_seq : void 0;
6828
+ if (contiguousSeq === void 0) client._seqTracker.onPullResult(ns, eventObjects, nextAfterSeq);
6829
+ else client._seqTracker.onPullResult(ns, eventObjects, nextAfterSeq, contiguousSeq);
6880
6830
  const cursor = isJsonObject(result.cursor) ? result.cursor : null;
6881
6831
  const retentionFloor = Math.max(
6882
6832
  0,
@@ -6930,21 +6880,7 @@ var MessageDeliveryEngine = class {
6930
6880
  this.persistSeq(ns);
6931
6881
  }
6932
6882
  if (eventObjects.length > 0 && ackContig > 0 && ackContig !== pageContigBefore) {
6933
- try {
6934
- const ackPromise = client._transport.call("group.ack_events", {
6935
- group_id: groupId,
6936
- event_seq: ackContig,
6937
- device_id: client._deviceId,
6938
- slot_id: client._slotId,
6939
- _rpc_background: true
6940
- });
6941
- if (singlePage) await ackPromise;
6942
- else ackPromise.catch((e) => {
6943
- client._clientLog.warn("group event auto-ack failed: group=" + groupId, e);
6944
- });
6945
- } catch (e) {
6946
- client._clientLog.warn("group event auto-ack failed: group=" + groupId, e);
6947
- }
6883
+ pendingAckSeq = Math.max(pendingAckSeq, ackContig);
6948
6884
  }
6949
6885
  this.onPullPage(
6950
6886
  ns,
@@ -6957,15 +6893,29 @@ var MessageDeliveryEngine = class {
6957
6893
  eventObjects.length > 0 ? Number(eventObjects[eventObjects.length - 1].event_seq ?? 0) : void 0
6958
6894
  );
6959
6895
  const nextAfter = Math.max(eventSeqs.length > 0 ? Math.max(...eventSeqs) : nextAfterSeq, nextAfterSeq);
6960
- if (singlePage && result.has_more === true && nextAfter > nextAfterSeq) {
6896
+ const needsMore = result.has_more === true || client._seqTracker.getMaxSeenSeq(ns) > nextAfter;
6897
+ if (singlePage && needsMore && nextAfter > nextAfterSeq) {
6961
6898
  continuationAfterSeq = nextAfter;
6962
6899
  }
6963
- if (eventObjects.length === 0 || nextAfter <= nextAfterSeq || result.has_more !== true) break;
6900
+ if (eventObjects.length === 0 || nextAfter <= nextAfterSeq || !needsMore) break;
6964
6901
  nextAfterSeq = nextAfter;
6965
6902
  }
6966
6903
  if (pageCount >= maxPages) {
6967
6904
  client._clientLog.warn(`group event gap fill reached max_pages=${maxPages} group=${groupId} after_seq=${nextAfterSeq}`);
6968
6905
  }
6906
+ if (pendingAckSeq > 0) {
6907
+ try {
6908
+ await client._transport.call("group.ack_events", {
6909
+ group_id: groupId,
6910
+ event_seq: pendingAckSeq,
6911
+ device_id: client._deviceId,
6912
+ slot_id: client._slotId,
6913
+ _rpc_background: true
6914
+ });
6915
+ } catch (e) {
6916
+ client._clientLog.warn("group event auto-ack failed: group=" + groupId, e);
6917
+ }
6918
+ }
6969
6919
  } catch (exc) {
6970
6920
  failed = true;
6971
6921
  client._clientLog.warn(`group event gap-fill failed:${String(exc)}`);
@@ -7001,41 +6951,15 @@ var MessageDeliveryEngine = class {
7001
6951
  this.enqueueOnlineUnreadEventHint(data);
7002
6952
  return;
7003
6953
  }
7004
- if (this.isSelfJoinGroupChanged(data)) {
7005
- const contig = client._seqTracker.getContiguousSeq(ns);
7006
- const maxSeen = client._seqTracker.getMaxSeenSeq(ns);
7007
- if (contig === 0 && maxSeen === 0 && eventSeq > 1) {
7008
- client._clientLog.debug(`group.changed self-join baseline: group=${groupId}, event_seq=${eventSeq}, baseline=${eventSeq - 1}`);
7009
- client._seqTracker.forceContiguousSeq(ns, eventSeq - 1);
7010
- }
7011
- }
7012
6954
  const contigBefore = client._seqTracker.getContiguousSeq(ns);
6955
+ client._seqTracker.updateMaxSeen(ns, eventSeq);
7013
6956
  const publishedDuplicate = client._pushedSeqs.get(ns)?.has(eventSeq) === true;
7014
6957
  if (eventSeq <= contigBefore || publishedDuplicate) {
7015
6958
  client._clientLog.debug(`group.changed skipped duplicate/stale: group=${groupId}, event_seq=${eventSeq}, contiguous=${contigBefore}`);
7016
- if (eventSeq <= contigBefore) {
7017
- this.fireGroupEventAck(groupId, ns, eventSeq, "group event covered push ack");
7018
- } else if (contigBefore > 0) {
7019
- this.fireGroupEventAck(groupId, ns, contigBefore, "group event covered push ack");
7020
- }
7021
6959
  return;
7022
6960
  }
7023
6961
  this.enqueueOrderedMessage(ns, "group.changed", eventSeq, data);
7024
- needPull = client._seqTracker.onMessageSeq(ns, eventSeq);
7025
- const ackContig = client._seqTracker.getContiguousSeq(ns);
7026
- await this.drainOrderedMessages(ns);
7027
- if (ackContig > 0 && ackContig !== contigBefore) {
7028
- if (data.action !== "dissolved") this.persistSeq(ns);
7029
- client._transport.call("group.ack_events", {
7030
- group_id: groupId,
7031
- event_seq: ackContig,
7032
- device_id: client._deviceId,
7033
- slot_id: client._slotId,
7034
- _rpc_background: true
7035
- }).catch((e) => {
7036
- client._clientLog.warn("group event push auto-ack failed: group=" + groupId, e);
7037
- });
7038
- }
6962
+ needPull = eventSeq > contigBefore;
7039
6963
  if (needPull && groupId && !data._from_gap_fill && client._sessionOptions?.background_sync !== false) {
7040
6964
  client._safeAsync(this.fillGroupEventGap(groupId));
7041
6965
  }
@@ -7468,16 +7392,18 @@ var MessageDeliveryEngine = class {
7468
7392
  };
7469
7393
  if (forceTail || order[0] === "tail") result = await run(true, false, void 0, false, forceTail);
7470
7394
  const state = this.syncState(ns);
7471
- const tailMissedDuringPull = tailCompleted && Number(client._seqTracker.getMaxSeenSeq(ns) || 0) > state.head;
7472
7395
  const maxSeen = Number(client._seqTracker.getMaxSeenSeq(ns) || 0);
7473
- const pendingThroughSeq = tailMissedDuringPull ? maxSeen : 0;
7474
- const tailForward = tailCompleted && (state.ack < state.tail - 1 || tailMissedDuringPull);
7396
+ const pendingUpper = Number(this.pendingP2pPullUpper?.get(ns) ?? 0);
7397
+ const observedUpper = Math.max(maxSeen, pendingUpper);
7398
+ const tailMissedDuringPull = tailCompleted && observedUpper > state.head;
7399
+ const pendingThroughSeq = tailMissedDuringPull ? observedUpper : 0;
7400
+ const tailForward = tailCompleted && (state.ack < state.head || tailMissedDuringPull);
7475
7401
  const backgroundWindowForward = order[0] === "forward" && !headForward;
7476
7402
  if (headForward || tailForward || client._sessionOptions?.background_sync !== false && backgroundWindowForward) {
7477
7403
  const forwardTarget = Math.max(
7478
7404
  headForward ? Math.max(pushSeq, maxSeen) : 0,
7479
7405
  tailForward || backgroundWindowForward ? state.tail - 1 : 0,
7480
- tailMissedDuringPull ? maxSeen : 0
7406
+ tailMissedDuringPull ? observedUpper : 0
7481
7407
  );
7482
7408
  const forwardMaxPages = this.forwardMaxPages(state.ack, forwardTarget, pageLimit);
7483
7409
  result = await run(
@@ -7536,16 +7462,18 @@ var MessageDeliveryEngine = class {
7536
7462
  };
7537
7463
  if (forceTail || order[0] === "tail") result = await run(true, false, void 0, false, forceTail);
7538
7464
  const state = this.syncState(ns);
7539
- const tailMissedDuringPull = tailCompleted && Number(client._seqTracker.getMaxSeenSeq(ns) || 0) > state.head;
7540
7465
  const maxSeen = Number(client._seqTracker.getMaxSeenSeq(ns) || 0);
7541
- const pendingThroughSeq = tailMissedDuringPull ? maxSeen : 0;
7542
- const tailForward = tailCompleted && (state.ack < state.tail - 1 || tailMissedDuringPull);
7466
+ const pendingUpper = Number(this.pendingGroupPullUpper?.get(ns) ?? 0);
7467
+ const observedUpper = Math.max(maxSeen, pendingUpper);
7468
+ const tailMissedDuringPull = tailCompleted && observedUpper > state.head;
7469
+ const pendingThroughSeq = tailMissedDuringPull ? observedUpper : 0;
7470
+ const tailForward = tailCompleted && (state.ack < state.head || tailMissedDuringPull);
7543
7471
  const backgroundWindowForward = order[0] === "forward" && !headForward;
7544
7472
  if (headForward || tailForward || client._sessionOptions?.background_sync !== false && backgroundWindowForward) {
7545
7473
  const forwardTarget = Math.max(
7546
7474
  headForward ? Math.max(pushSeq, maxSeen) : 0,
7547
7475
  tailForward || backgroundWindowForward ? state.tail - 1 : 0,
7548
- tailMissedDuringPull ? maxSeen : 0
7476
+ tailMissedDuringPull ? observedUpper : 0
7549
7477
  );
7550
7478
  const forwardMaxPages = this.forwardMaxPages(state.ack, forwardTarget, pageLimit);
7551
7479
  result = await run(
@@ -7597,13 +7525,13 @@ var MessageDeliveryEngine = class {
7597
7525
  if (!row) return;
7598
7526
  const groupId = typeof row.group_id === "string" ? row.group_id.trim() : "";
7599
7527
  if (!groupId) return;
7600
- const seq2 = typeof row.seq === "number" ? row.seq : 0;
7601
7528
  const eventKind = String(row.kind ?? "").trim();
7602
7529
  const ns = this.resolvePendingGroupInlineAckNamespace(`group:${groupId}`);
7603
- if (client._v2Session && eventKind !== "group.online_unread_hint" && Number.isSafeInteger(seq2) && seq2 > 0) {
7530
+ const seq2 = positiveSafeSequenceHint(row.seq);
7531
+ if (client._v2Session && eventKind !== "group.online_unread_hint" && seq2 > 0) {
7604
7532
  client._seqTracker.updateMaxSeen(ns, seq2);
7605
7533
  }
7606
- const hasInlineObject = isJsonObject(row.inline_message) && eventKind !== "group.online_unread_hint";
7534
+ const hasInlineObject = hasNonEmptyInlineMessage(row.inline_message) && eventKind !== "group.online_unread_hint";
7607
7535
  const inlineGeneration = hasInlineObject ? this.captureInlineGeneration() : void 0;
7608
7536
  const operation = () => this.processGroupV2MessageCreated(data, "v2", inlineGeneration);
7609
7537
  if (hasInlineObject) await this.runPushProcessSerialized(ns, operation, inlineGeneration);
@@ -7615,7 +7543,6 @@ var MessageDeliveryEngine = class {
7615
7543
  if (!isJsonObject(data) || !client._v2Session) return;
7616
7544
  const groupId = typeof data.group_id === "string" ? data.group_id.trim() : "";
7617
7545
  const seq2 = positiveSafeSequenceHint(data.seq);
7618
- const strictSeq = typeof data.seq === "number" && Number.isSafeInteger(data.seq) && data.seq > 0;
7619
7546
  if (!groupId) return;
7620
7547
  const eventKind = String(data.kind ?? "").trim();
7621
7548
  if (eventKind === "group.online_unread_hint") {
@@ -7624,11 +7551,12 @@ var MessageDeliveryEngine = class {
7624
7551
  return;
7625
7552
  }
7626
7553
  const ns = this.resolvePendingGroupInlineAckNamespace(`group:${groupId}`);
7627
- const inlinePresent = Object.prototype.hasOwnProperty.call(data, "inline_message");
7554
+ const inlinePresent = hasNonEmptyInlineMessage(data.inline_message);
7628
7555
  if (seq2 <= 0) {
7629
7556
  if (inlinePresent) await this.recoverInvalidGroupRealtimeHead(groupId, ns, data.seq);
7630
7557
  return;
7631
7558
  }
7559
+ client._seqTracker.updateMaxSeen(ns, seq2);
7632
7560
  const inline = this.inlineMessage(data);
7633
7561
  const inlineMatches = inline !== null && this.inlineGroupBindingMatches(data, inline, groupId, seq2, expectedInlineVersion);
7634
7562
  const pendingInline = this.pendingGroupInlineAcks?.get(ns);
@@ -7649,13 +7577,12 @@ var MessageDeliveryEngine = class {
7649
7577
  }
7650
7578
  if (inlineMatches && !this.isInlineGenerationCurrent(inlineGeneration)) return;
7651
7579
  const before = this.syncState(ns);
7652
- if (strictSeq || !inlinePresent) client._seqTracker.updateMaxSeen(ns, seq2);
7653
7580
  if (seq2 <= before.ack && before.ack >= before.tail - 1) {
7654
7581
  const canonicalNs = this.resolvePendingGroupInlineAckNamespace(ns);
7655
7582
  const pending = this.pendingGroupInlineAcks?.get(canonicalNs);
7656
- if (pending && seq2 >= pending.seq) {
7583
+ if (inlinePresent && pending && seq2 >= pending.seq) {
7657
7584
  this.retryPendingGroupInlineAck(canonicalNs, pending.seq, "covered inline Group ack", seq2);
7658
- } else if (!inlinePresent || inlineMatches) {
7585
+ } else if (inlineMatches) {
7659
7586
  this.fireGroupV2Ack(
7660
7587
  groupId,
7661
7588
  ns,
@@ -7694,7 +7621,7 @@ var MessageDeliveryEngine = class {
7694
7621
  if (client._v2Session && Number.isSafeInteger(pushSeq) && pushSeq > 0) {
7695
7622
  client._seqTracker.updateMaxSeen(ns, pushSeq);
7696
7623
  }
7697
- const hasInlineObject = isJsonObject(data) && isJsonObject(data.inline_message);
7624
+ const hasInlineObject = isJsonObject(data) && hasNonEmptyInlineMessage(data.inline_message);
7698
7625
  const inlineGeneration = hasInlineObject ? this.captureInlineGeneration() : void 0;
7699
7626
  const operation = () => this.processV2P2PNotification(data, "v2", inlineGeneration);
7700
7627
  if (hasInlineObject) await this.runPushProcessSerialized(ns, operation, inlineGeneration);
@@ -7705,10 +7632,10 @@ var MessageDeliveryEngine = class {
7705
7632
  const client = this.runtime.client;
7706
7633
  if (!client._v2Session) return;
7707
7634
  const pushSeq = isJsonObject(data) ? positiveSafeSequenceHint(data.seq) : 0;
7708
- const strictSeq = isJsonObject(data) && typeof data.seq === "number" && Number.isSafeInteger(data.seq) && data.seq > 0;
7709
7635
  const ns = client._aid ? `p2p:${client._aid}` : "";
7710
7636
  if (!ns) return;
7711
- const inlinePresent = isJsonObject(data) && Object.prototype.hasOwnProperty.call(data, "inline_message");
7637
+ if (pushSeq > 0) client._seqTracker.updateMaxSeen(ns, pushSeq);
7638
+ const inlinePresent = isJsonObject(data) && hasNonEmptyInlineMessage(data.inline_message);
7712
7639
  if (pushSeq <= 0) {
7713
7640
  if (inlinePresent) await this.recoverInvalidP2PRealtimeHead(ns, isJsonObject(data) ? data.seq : void 0);
7714
7641
  return;
@@ -7731,10 +7658,9 @@ var MessageDeliveryEngine = class {
7731
7658
  }
7732
7659
  if (inlineMatches && !this.isInlineGenerationCurrent(inlineGeneration)) return;
7733
7660
  const before = this.syncState(ns);
7734
- if (strictSeq || !inlinePresent) client._seqTracker.updateMaxSeen(ns, pushSeq);
7735
7661
  if (pushSeq <= before.ack && before.ack >= before.tail - 1) {
7736
7662
  const pending = this.pendingP2PInlineAcks?.get(ns);
7737
- if (pending && pushSeq >= pending.seq) {
7663
+ if (inlinePresent && pending && pushSeq >= pending.seq) {
7738
7664
  this.retryPendingP2PInlineAck(ns, pending.seq, "covered inline P2P ack", pushSeq);
7739
7665
  } else if (inlineMatches) {
7740
7666
  this.fireP2PV2Ack(pushSeq, "P2P inline covered push ack", inlineGeneration);
@@ -15677,6 +15603,11 @@ var SIGNED_METHODS = /* @__PURE__ */ new Set([
15677
15603
  "group.v2.get_proposal",
15678
15604
  "group.kick",
15679
15605
  "group.add_member",
15606
+ "group.invite_member",
15607
+ "group.list_my_invites",
15608
+ "group.accept_invite",
15609
+ "group.reject_invite",
15610
+ "group.revoke_invite",
15680
15611
  "group.leave",
15681
15612
  "group.remove_member",
15682
15613
  "group.update",
@@ -15829,7 +15760,9 @@ var NON_IDEMPOTENT_METHODS = /* @__PURE__ */ new Set([
15829
15760
  "mail.send",
15830
15761
  "group.send",
15831
15762
  "group.create",
15832
- "group.invite",
15763
+ "group.accept_invite",
15764
+ "group.reject_invite",
15765
+ "group.revoke_invite",
15833
15766
  "group.kick",
15834
15767
  "group.remove_member",
15835
15768
  "group.leave",
@@ -16855,6 +16788,8 @@ var RpcPipeline = class {
16855
16788
  }
16856
16789
  postprocessMessagePull(params2, result) {
16857
16790
  const client = this.runtime.client;
16791
+ const isTail = String(result.window_mode ?? params2.window_mode ?? "").trim().toLowerCase() === "tail";
16792
+ if (isTail) return;
16858
16793
  const messages = result.messages;
16859
16794
  const rawMessages = (Array.isArray(messages) ? messages : []).filter(isJsonObject);
16860
16795
  if (!client._aid || !client._seqTracker) {
@@ -16862,9 +16797,9 @@ var RpcPipeline = class {
16862
16797
  }
16863
16798
  const ns = `p2p:${client._aid}`;
16864
16799
  const contigBefore = client._seqTracker.getContiguousSeq(ns);
16865
- if (rawMessages.length) {
16866
- client._seqTracker.onPullResult(ns, rawMessages, Number(params2.after_seq ?? 0) || 0);
16867
- }
16800
+ const contiguousSeq = Number.isSafeInteger(result.contiguous_seq) ? result.contiguous_seq : void 0;
16801
+ if (contiguousSeq === void 0) client._seqTracker.onPullResult(ns, rawMessages, Number(params2.after_seq ?? 0) || 0);
16802
+ else client._seqTracker.onPullResult(ns, rawMessages, Number(params2.after_seq ?? 0) || 0, contiguousSeq);
16868
16803
  const retentionFloor = Math.max(0, Number(result.retention_floor_seq ?? 0));
16869
16804
  if (retentionFloor > 0) {
16870
16805
  const contig = client._seqTracker.getContiguousSeq(ns);
@@ -16883,6 +16818,8 @@ var RpcPipeline = class {
16883
16818
  if (Array.isArray(result.messages)) {
16884
16819
  result.messages = result.messages.map((message) => normalizeGroupMentionMode(message));
16885
16820
  }
16821
+ const isTail = String(result.window_mode ?? params2.window_mode ?? "").trim().toLowerCase() === "tail";
16822
+ if (isTail) return;
16886
16823
  const gid = String(params2.group_id ?? params2.group_aid ?? "").trim();
16887
16824
  if (!gid || !client._seqTracker) {
16888
16825
  return;
@@ -16891,17 +16828,17 @@ var RpcPipeline = class {
16891
16828
  const rawMessages = (Array.isArray(messages) ? messages : []).filter(isJsonObject);
16892
16829
  const ns = `group:${gid}`;
16893
16830
  const contigBefore = client._seqTracker.getContiguousSeq(ns);
16894
- if (rawMessages.length) {
16895
- client._seqTracker.onPullResult(ns, rawMessages, Number(params2.after_message_seq ?? params2.after_seq ?? 0) || 0);
16896
- }
16831
+ const contiguousSeq = Number.isSafeInteger(result.contiguous_seq) ? result.contiguous_seq : void 0;
16832
+ const afterSeq = Number(params2.after_message_seq ?? params2.after_seq ?? 0) || 0;
16833
+ if (contiguousSeq === void 0) client._seqTracker.onPullResult(ns, rawMessages, afterSeq);
16834
+ else client._seqTracker.onPullResult(ns, rawMessages, afterSeq, contiguousSeq);
16897
16835
  const cursor = isJsonObject(result.cursor) ? result.cursor : null;
16898
16836
  const retentionFloor = Math.max(
16899
16837
  0,
16900
16838
  Number(result.retention_floor_message_seq ?? result.retention_floor_seq ?? 0),
16901
16839
  Number(cursor?.retention_floor_seq ?? 0)
16902
16840
  );
16903
- const isTail = String(result.window_mode ?? params2.window_mode ?? "").trim().toLowerCase() === "tail";
16904
- const visibilityFloor = isTail ? 0 : Math.max(
16841
+ const visibilityFloor = Math.max(
16905
16842
  0,
16906
16843
  Math.max(0, Number(result.earliest_available_message_seq ?? 1) - 1),
16907
16844
  Number(cursor?.join_seq ?? 0)
@@ -16910,7 +16847,7 @@ var RpcPipeline = class {
16910
16847
  if (effectiveFloor > 0) {
16911
16848
  const contig = client._seqTracker.getContiguousSeq(ns);
16912
16849
  if (contig < effectiveFloor) {
16913
- client._clientLog?.info?.(`group.pull floor advance: ns=${ns} contiguous=${contig} -> effective_floor=${effectiveFloor} retention_floor=${retentionFloor} visibility_floor=${visibilityFloor} mode=${isTail ? "tail" : "forward"}`);
16850
+ client._clientLog?.info?.(`group.pull floor advance: ns=${ns} contiguous=${contig} -> effective_floor=${effectiveFloor} retention_floor=${retentionFloor} visibility_floor=${visibilityFloor} mode=forward`);
16914
16851
  client._seqTracker.forceContiguousSeq(ns, effectiveFloor);
16915
16852
  }
16916
16853
  }
@@ -18443,6 +18380,21 @@ var MessageFacade = class extends RpcFacade {
18443
18380
  return this.call("message.query_online", params2);
18444
18381
  }
18445
18382
  };
18383
+ function normalizeInviteParams(params2) {
18384
+ const out = stripNil2(params2);
18385
+ const rawGroupId = out.group_id ?? out.groupId ?? out.group_aid ?? out.groupAid;
18386
+ if (rawGroupId !== void 0) {
18387
+ out.group_id = normalizeGroupId(rawGroupId);
18388
+ delete out.groupId;
18389
+ delete out.group_aid;
18390
+ delete out.groupAid;
18391
+ }
18392
+ for (const [alias, canonical] of [["inviteId", "invite_id"], ["inviteeAid", "invitee_aid"]]) {
18393
+ if (out[canonical] === void 0 && out[alias] !== void 0) out[canonical] = out[alias];
18394
+ delete out[alias];
18395
+ }
18396
+ return out;
18397
+ }
18446
18398
  var MailFacade = class extends RpcFacade {
18447
18399
  send(params2) {
18448
18400
  return this.call("mail.send", params2);
@@ -18555,6 +18507,32 @@ var GroupFacade = class extends RpcFacade {
18555
18507
  addMember(params2) {
18556
18508
  return this.call("group.add_member", params2);
18557
18509
  }
18510
+ async inviteCall(method, params2) {
18511
+ const normalized = normalizeInviteParams(params2);
18512
+ try {
18513
+ const result = await this.call(method, normalized);
18514
+ await this.client._onGroupInviteActionResult?.(method, normalized, result);
18515
+ return result;
18516
+ } catch (error) {
18517
+ await this.client._onGroupInviteActionResult?.(method, normalized, void 0, error);
18518
+ throw error;
18519
+ }
18520
+ }
18521
+ inviteMember(params2) {
18522
+ return this.inviteCall("group.invite_member", params2);
18523
+ }
18524
+ listMyInvites(params2) {
18525
+ return this.inviteCall("group.list_my_invites", params2);
18526
+ }
18527
+ acceptInvite(params2) {
18528
+ return this.inviteCall("group.accept_invite", params2);
18529
+ }
18530
+ rejectInvite(params2) {
18531
+ return this.inviteCall("group.reject_invite", params2);
18532
+ }
18533
+ revokeInvite(params2) {
18534
+ return this.inviteCall("group.revoke_invite", params2);
18535
+ }
18558
18536
  getMembers(params2) {
18559
18537
  return this.call("group.get_members", params2);
18560
18538
  }
@@ -23942,6 +23920,22 @@ function deduplicateForwardPageMessages(value, afterSeq) {
23942
23920
  }
23943
23921
  return [...bySeq.entries()].sort(([a], [b]) => a - b).map(([, row]) => row);
23944
23922
  }
23923
+ function contiguousPullSeq(afterSeq, seqs, proof) {
23924
+ let contiguous = seqs.length > 0 ? Math.max(afterSeq, ...seqs) : afterSeq;
23925
+ const serverProof = safeNonNegativeSeq(proof);
23926
+ return serverProof !== null ? Math.max(contiguous, serverProof) : contiguous;
23927
+ }
23928
+ function deduplicatePulledResults(messages) {
23929
+ const seen = /* @__PURE__ */ new Set();
23930
+ return messages.filter((message) => {
23931
+ if (!isJsonObject(message)) return true;
23932
+ const seq2 = safeNonNegativeSeq(message.seq);
23933
+ if (seq2 === null || seq2 <= 0) return true;
23934
+ if (seen.has(seq2)) return false;
23935
+ seen.add(seq2);
23936
+ return true;
23937
+ });
23938
+ }
23945
23939
  function shouldContinueForwardPage({
23946
23940
  rawCount,
23947
23941
  nextAfterSeq,
@@ -23950,10 +23944,11 @@ function shouldContinueForwardPage({
23950
23944
  hasMoreField,
23951
23945
  hasMore,
23952
23946
  serverHead,
23953
- pendingAckSeq
23947
+ pendingAckSeq,
23948
+ observedUpper
23954
23949
  }) {
23955
23950
  if (rawCount <= 0 || nextAfter <= nextAfterSeq) return false;
23956
- if (hasMore || serverHead > nextAfter || pendingAckSeq > 0) return true;
23951
+ if (hasMore || serverHead > nextAfter || pendingAckSeq > 0 || observedUpper > nextAfter) return true;
23957
23952
  return !hasMoreField && limit > 0 && rawCount >= limit;
23958
23953
  }
23959
23954
  function validateTailPage(result, afterSeq, pageLimit) {
@@ -24692,7 +24687,7 @@ var V2E2EECoordinator = class {
24692
24687
  client._clientLog.warn(`V2 sender IK pending group bootstrap failed group=${groupId}: ${String(formatE2EEError(exc))}`);
24693
24688
  }
24694
24689
  }
24695
- if (!session.getPeerIK(fromAid, senderDeviceId)) {
24690
+ if (typeof session.getPeerIK === "function" && !session.getPeerIK(fromAid, senderDeviceId)) {
24696
24691
  await this.getV2SenderPubDer(fromAid, senderDeviceId);
24697
24692
  }
24698
24693
  }
@@ -24728,6 +24723,7 @@ var V2E2EECoordinator = class {
24728
24723
  client._v2SenderIKPending.delete(key);
24729
24724
  const seq2 = Number(entry.msg.seq ?? 0);
24730
24725
  if (entry.groupId) {
24726
+ if (client._delivery.isSelfSentGroupMessage?.(entry.msg)) continue;
24731
24727
  plaintext = normalizeGroupMentionMode(plaintext, entry.msg, entry.msg.envelope_json);
24732
24728
  plaintext.group_id = entry.groupId;
24733
24729
  await client._publishPulledMessage("group.message_created", `group:${entry.groupId}`, seq2, plaintext, false, "pending_retry");
@@ -24920,6 +24916,9 @@ var V2E2EECoordinator = class {
24920
24916
  client._clientLog.warn(`Group Tail \u5355\u6761\u6D88\u606F\u89E3\u5BC6\u5931\u8D25\uFF0C\u8FDE\u7EED\u6027\u8BC1\u660E\u4ECD\u4FDD\u7559: group=${groupId}, seq=${seq2}`);
24921
24917
  return null;
24922
24918
  }
24919
+ if (publish && !history && client._delivery.isSelfSentGroupMessage?.(msg)) {
24920
+ return plaintext;
24921
+ }
24923
24922
  if (publish) {
24924
24923
  if (orderedPublish) {
24925
24924
  await client._delivery.publishOrderedMessage(
@@ -24941,13 +24940,6 @@ var V2E2EECoordinator = class {
24941
24940
  async decodeInlineGroupMessage(msg, groupId, groupAid) {
24942
24941
  return await this.decodeGroupWindowMessage(msg, groupId, groupAid, false, false, "inline_push");
24943
24942
  }
24944
- async applyTailFloor(ns, floor) {
24945
- const client = this.client;
24946
- if (!ns || floor <= 0 || client._seqTracker.getContiguousSeq(ns) >= floor) return;
24947
- client._seqTracker.forceContiguousSeq(ns, floor);
24948
- client._persistSeq(ns);
24949
- await client._saveSeqTrackerState?.();
24950
- }
24951
24943
  async pullV2TailInternal(params2, deferPublish = false) {
24952
24944
  const client = this.client;
24953
24945
  if (params2.window_mode !== "tail") throw new ValidationError("window_mode must equal tail");
@@ -24957,9 +24949,6 @@ var V2E2EECoordinator = class {
24957
24949
  const ns = client._aid ? `p2p:${client._aid}` : "";
24958
24950
  if (ns) client._delivery.onPullStarted?.(ns);
24959
24951
  const result = await client._callRawV2Rpc("message.v2.pull", { window_mode: "tail", after_seq: afterSeq, limit, _rpc_foreground: true });
24960
- const floor = Number(result.retention_floor_seq ?? 0);
24961
- const previous = ns ? client._seqTracker.getSyncState(ns) : null;
24962
- await this.applyTailFloor(ns, Number.isSafeInteger(floor) ? floor : 0);
24963
24952
  const page = validateTailPage(result, afterSeq, limit);
24964
24953
  if (ns) {
24965
24954
  client._seqTracker.commitTailWindow(ns, {
@@ -24977,7 +24966,6 @@ var V2E2EECoordinator = class {
24977
24966
  if (plaintext) decoded.push(plaintext);
24978
24967
  }
24979
24968
  const ack = ns ? client._seqTracker.getContiguousSeq(ns) : 0;
24980
- if (previous && ack > previous.ack) await this.ackV2(ack);
24981
24969
  if (ns) client._delivery.onPullPage?.(
24982
24970
  ns,
24983
24971
  decoded.length,
@@ -25097,6 +25085,11 @@ var V2E2EECoordinator = class {
25097
25085
  const pageContigBefore = ns ? client._seqTracker.getContiguousSeq(ns) : 0;
25098
25086
  const pageTrackerSnapshot = ns && typeof client._seqTracker.snapshotNamespace === "function" ? client._seqTracker.snapshotNamespace(ns) : null;
25099
25087
  const pageMaxSeq = seqs.length > 0 ? Math.max(...seqs) : nextAfterSeq;
25088
+ const pageContiguousSeq = contiguousPullSeq(
25089
+ pageContigBefore,
25090
+ seqs,
25091
+ result.contiguous_seq
25092
+ );
25100
25093
  const deferredKeyFetches = /* @__PURE__ */ new Map();
25101
25094
  let blockedSeq = 0;
25102
25095
  const pageDecryptedBefore = decrypted.length;
@@ -25188,11 +25181,12 @@ var V2E2EECoordinator = class {
25188
25181
  const serverAckSeq = parsedServerAckSeq ?? 0;
25189
25182
  const retentionFloor = Math.max(0, Number(result.retention_floor_seq ?? 0));
25190
25183
  if (ns) {
25191
- const commitTarget = blockedSeq > 0 ? pageContigBefore : Math.max(
25184
+ const commitTarget = Math.max(
25192
25185
  pageContigBefore,
25193
25186
  Number.isFinite(retentionFloor) ? retentionFloor : 0,
25194
25187
  pageCount === 1 ? deferredServerCursor : 0,
25195
- seqs.length > 0 ? pageMaxSeq : pageContigBefore
25188
+ serverAckSeq,
25189
+ blockedSeq > 0 ? blockedSeq - 1 : pageContiguousSeq
25196
25190
  );
25197
25191
  let ackSeq = pageContigBefore;
25198
25192
  let contigAdvanced = false;
@@ -25230,10 +25224,10 @@ var V2E2EECoordinator = class {
25230
25224
  this.recordForwardCursor(ns, serverAckSeq, ackSeq);
25231
25225
  }
25232
25226
  const knownServerAckSeq = hasServerAckSeq ? serverAckSeq : pageCount === 1 ? deferredServerCursor : 0;
25233
- const ackNeeded = blockedSeq <= 0 && ackSeq > 0 && ackSeq > lastAutoAckSeq && (hasServerAckSeq && ackSeq > serverAckSeq || contigAdvanced && ackSeq > knownServerAckSeq);
25227
+ const ackNeeded = ackSeq > 0 && ackSeq > lastAutoAckSeq && (hasServerAckSeq && ackSeq > serverAckSeq || contigAdvanced && ackSeq > knownServerAckSeq);
25234
25228
  if (ackNeeded) {
25235
25229
  this.recordForwardAck(ns, ackSeq);
25236
- const nextAfter2 = Math.max(pageMaxSeq, nextAfterSeq);
25230
+ const nextAfter2 = Math.max(pageContiguousSeq, nextAfterSeq);
25237
25231
  const canContinuePage = blockedSeq <= 0 && messages.length > 0 && nextAfter2 > nextAfterSeq;
25238
25232
  if (canContinuePage) {
25239
25233
  pendingAckSeq = Math.max(pendingAckSeq, ackSeq);
@@ -25254,7 +25248,7 @@ var V2E2EECoordinator = class {
25254
25248
  client._seqTracker.getContiguousSeq(ns),
25255
25249
  pageMaxSeq
25256
25250
  );
25257
- const nextAfter = Math.max(pageMaxSeq, nextAfterSeq);
25251
+ const nextAfter = Math.max(pageContiguousSeq, nextAfterSeq);
25258
25252
  const rawCount = messages.length;
25259
25253
  if (blockedSeq > 0) break;
25260
25254
  const shouldContinue = shouldContinueForwardPage({
@@ -25265,7 +25259,8 @@ var V2E2EECoordinator = class {
25265
25259
  hasMoreField,
25266
25260
  hasMore,
25267
25261
  serverHead,
25268
- pendingAckSeq
25262
+ pendingAckSeq,
25263
+ observedUpper: typeof client._seqTracker.getMaxSeenSeq === "function" ? client._seqTracker.getMaxSeenSeq(ns) : nextAfter
25269
25264
  });
25270
25265
  if (!shouldContinue) break;
25271
25266
  if (pullGateKey && opts?.gateLocked && client._rpcPipeline?.yieldPullGate) {
@@ -25289,7 +25284,7 @@ var V2E2EECoordinator = class {
25289
25284
  }
25290
25285
  }
25291
25286
  client._delivery.onPullWorkSettled?.();
25292
- return decrypted;
25287
+ return deduplicatePulledResults(decrypted);
25293
25288
  }
25294
25289
  async confirmForwardP2PAck(upToSeq, inlineGeneration) {
25295
25290
  const ns = this.client._aid ? `p2p:${this.client._aid}` : "";
@@ -25375,10 +25370,7 @@ var V2E2EECoordinator = class {
25375
25370
  if (isJsonObject(result)) {
25376
25371
  const seq2 = Number(result.seq ?? 0);
25377
25372
  if (seq2 > 0) {
25378
- const ns = `group:${gid}`;
25379
- client._seqTracker.onMessageSeq(ns, seq2);
25380
- client._markPublishedSeq(ns, seq2);
25381
- await client._saveSeqTrackerState();
25373
+ client._markPublishedSeq(`group:${gid}`, seq2);
25382
25374
  }
25383
25375
  }
25384
25376
  return client._delivery.attachSendResultEnvelope("group.send", withExplicitGroupAid({
@@ -25398,10 +25390,7 @@ var V2E2EECoordinator = class {
25398
25390
  if (isJsonObject(result)) {
25399
25391
  const seq2 = Number(result.seq ?? 0);
25400
25392
  if (seq2 > 0) {
25401
- const ns = `group:${gid}`;
25402
- client._seqTracker.onMessageSeq(ns, seq2);
25403
- client._markPublishedSeq(ns, seq2);
25404
- await client._saveSeqTrackerState();
25393
+ client._markPublishedSeq(`group:${gid}`, seq2);
25405
25394
  }
25406
25395
  }
25407
25396
  return client._delivery.attachSendResultEnvelope("group.send", withExplicitGroupAid({
@@ -25440,9 +25429,6 @@ var V2E2EECoordinator = class {
25440
25429
  }, groupAid));
25441
25430
  const resultAid = String(result.group_aid ?? result.groupAid ?? "").trim();
25442
25431
  if (resultAid) groupAid = resultAid;
25443
- const floor = Number(result.retention_floor_message_seq ?? result.retention_floor_seq ?? 0);
25444
- const previous = client._seqTracker.getSyncState(ns);
25445
- await this.applyTailFloor(ns, Number.isSafeInteger(floor) ? floor : 0);
25446
25432
  const page = validateTailPage(result, afterSeq, limit);
25447
25433
  client._seqTracker.commitTailWindow(ns, {
25448
25434
  windowStartSeq: page.windowStart,
@@ -25459,7 +25445,6 @@ var V2E2EECoordinator = class {
25459
25445
  if (plaintext) decoded.push(plaintext);
25460
25446
  }
25461
25447
  const ack = client._seqTracker.getContiguousSeq(ns);
25462
- if (ack > previous.ack) await this.ackGroupV2(groupId, ack, groupAid);
25463
25448
  if (ownsCursor) client._delivery.onPullPage?.(
25464
25449
  ns,
25465
25450
  decoded.length,
@@ -25616,6 +25601,11 @@ var V2E2EECoordinator = class {
25616
25601
  const pageContigBefore = client._seqTracker.getContiguousSeq(ns);
25617
25602
  const pageTrackerSnapshot = typeof client._seqTracker.snapshotNamespace === "function" ? client._seqTracker.snapshotNamespace(ns) : null;
25618
25603
  const pageMaxSeq = seqs.length > 0 ? Math.max(...seqs) : nextAfterSeq;
25604
+ const pageContiguousSeq = contiguousPullSeq(
25605
+ pageContigBefore,
25606
+ seqs,
25607
+ result.contiguous_seq
25608
+ );
25619
25609
  const deferredKeyFetches = /* @__PURE__ */ new Map();
25620
25610
  let blockedSeq = 0;
25621
25611
  const pageDecryptedBefore = decrypted.length;
@@ -25653,7 +25643,11 @@ var V2E2EECoordinator = class {
25653
25643
  };
25654
25644
  v1Msg = normalizeGroupMentionMode(v1Msg, msg, msg.envelope_json);
25655
25645
  attachGatewayProximity(v1Msg, msg);
25656
- await client._publishPulledMessage("group.message_created", ns, seq2, v1Msg, false);
25646
+ if (!client._delivery.isSelfSentGroupMessage?.(msg)) {
25647
+ await client._publishPulledMessage("group.message_created", ns, seq2, v1Msg, false);
25648
+ } else {
25649
+ client._markPublishedSeq(ns, seq2);
25650
+ }
25657
25651
  decrypted.push(v1Msg);
25658
25652
  continue;
25659
25653
  }
@@ -25697,7 +25691,11 @@ var V2E2EECoordinator = class {
25697
25691
  if (plaintext2) {
25698
25692
  plaintext2.group_id = gid;
25699
25693
  plaintext2.group_aid = eventGroupAid;
25700
- await client._publishPulledMessage("group.message_created", ns, seq2, plaintext2, false);
25694
+ if (!client._delivery.isSelfSentGroupMessage?.(msg)) {
25695
+ await client._publishPulledMessage("group.message_created", ns, seq2, plaintext2, false);
25696
+ } else {
25697
+ client._markPublishedSeq(ns, seq2);
25698
+ }
25701
25699
  decrypted.push(plaintext2);
25702
25700
  }
25703
25701
  }
@@ -25725,7 +25723,11 @@ var V2E2EECoordinator = class {
25725
25723
  plaintext = normalizeGroupMentionMode(plaintext, msg, msg.envelope_json);
25726
25724
  plaintext.group_id = gid;
25727
25725
  plaintext.group_aid = eventGroupAid;
25728
- await client._publishPulledMessage("group.message_created", ns, seq2, plaintext, false);
25726
+ if (!client._delivery.isSelfSentGroupMessage?.(msg)) {
25727
+ await client._publishPulledMessage("group.message_created", ns, seq2, plaintext, false);
25728
+ } else {
25729
+ client._markPublishedSeq(ns, seq2);
25730
+ }
25729
25731
  decrypted.push(plaintext);
25730
25732
  }
25731
25733
  const hasServerCursor = cursor !== null && Object.prototype.hasOwnProperty.call(cursor, "current_seq");
@@ -25741,11 +25743,12 @@ var V2E2EECoordinator = class {
25741
25743
  Number(cursor?.join_seq ?? 0)
25742
25744
  );
25743
25745
  const effectiveFloor = Math.max(retentionFloor, visibilityFloor);
25744
- const commitTarget = blockedSeq > 0 ? pageContigBefore : Math.max(
25746
+ const commitTarget = Math.max(
25745
25747
  pageContigBefore,
25746
25748
  Number.isFinite(effectiveFloor) ? effectiveFloor : 0,
25747
25749
  ownsCursor && pageCount === 1 ? deferredServerCursor : 0,
25748
- seqs.length > 0 ? pageMaxSeq : pageContigBefore
25750
+ cursorCurrentSeq,
25751
+ blockedSeq > 0 ? blockedSeq - 1 : pageContiguousSeq
25749
25752
  );
25750
25753
  let ackSeq = pageContigBefore;
25751
25754
  let contigAdvanced = false;
@@ -25795,10 +25798,10 @@ var V2E2EECoordinator = class {
25795
25798
  this.recordForwardCursor(ns, cursorCurrentSeq, ackSeq);
25796
25799
  }
25797
25800
  const knownServerCursorSeq = hasServerCursor ? cursorCurrentSeq : ownsCursor && pageCount === 1 ? deferredServerCursor : 0;
25798
- const ackNeeded = blockedSeq <= 0 && ackSeq > 0 && ackSeq > lastAutoAckSeq && ownsCursor && (hasServerCursor && ackSeq > cursorCurrentSeq || contigAdvanced && ackSeq > knownServerCursorSeq);
25801
+ const ackNeeded = ackSeq > 0 && ackSeq > lastAutoAckSeq && ownsCursor && (hasServerCursor && ackSeq > cursorCurrentSeq || contigAdvanced && ackSeq > knownServerCursorSeq);
25799
25802
  if (ackNeeded) {
25800
25803
  this.recordForwardAck(ns, ackSeq);
25801
- const nextAfter2 = Math.max(pageMaxSeq, nextAfterSeq);
25804
+ const nextAfter2 = Math.max(pageContiguousSeq, nextAfterSeq);
25802
25805
  const canContinuePage = blockedSeq <= 0 && messages.length > 0 && nextAfter2 > nextAfterSeq && ownsCursor;
25803
25806
  if (canContinuePage) {
25804
25807
  pendingAckSeq = Math.max(pendingAckSeq, ackSeq);
@@ -25808,7 +25811,7 @@ var V2E2EECoordinator = class {
25808
25811
  lastAutoAckSeq = Math.max(lastAutoAckSeq, ackSeq);
25809
25812
  }
25810
25813
  }
25811
- const nextAfter = Math.max(pageMaxSeq, nextAfterSeq);
25814
+ const nextAfter = Math.max(pageContiguousSeq, nextAfterSeq);
25812
25815
  if (!ownsCursor) break;
25813
25816
  const rawCount = messages.length;
25814
25817
  if (blockedSeq > 0) break;
@@ -25820,7 +25823,8 @@ var V2E2EECoordinator = class {
25820
25823
  hasMoreField,
25821
25824
  hasMore,
25822
25825
  serverHead,
25823
- pendingAckSeq
25826
+ pendingAckSeq,
25827
+ observedUpper: typeof client._seqTracker.getMaxSeenSeq === "function" ? client._seqTracker.getMaxSeenSeq(ns) : nextAfter
25824
25828
  });
25825
25829
  if (!shouldContinue) break;
25826
25830
  if (pullGateKey && opts?.gateLocked && client._rpcPipeline?.yieldPullGate) {
@@ -25846,7 +25850,7 @@ var V2E2EECoordinator = class {
25846
25850
  }
25847
25851
  }
25848
25852
  client._delivery.onPullWorkSettled?.();
25849
- return decrypted;
25853
+ return deduplicatePulledResults(decrypted);
25850
25854
  }
25851
25855
  async confirmForwardGroupAck(groupId, upToSeq, groupAid) {
25852
25856
  const ns = `group:${String(groupId ?? "").trim()}`;
@@ -26683,7 +26687,8 @@ var MEMBERSHIP_MUTATION_METHODS = /* @__PURE__ */ new Set([
26683
26687
  "group.review_join_request",
26684
26688
  "group.batch_review_join_request",
26685
26689
  "group.use_invite_code",
26686
- "group.request_join"
26690
+ "group.request_join",
26691
+ "group.accept_invite"
26687
26692
  ]);
26688
26693
  var SPK_REGISTRATION_MUTATION_METHODS = /* @__PURE__ */ new Set([
26689
26694
  "group.create",
@@ -29412,6 +29417,7 @@ var RECONNECT_MIN_BASE_DELAY_SECONDS = 1;
29412
29417
  var RECONNECT_MAX_BASE_DELAY_SECONDS = 64;
29413
29418
  var TOKEN_REFRESH_CHECK_INTERVAL_MS = 3e4;
29414
29419
  var MAX_NOTIFY_PAYLOAD_SIZE = 64 * 1024;
29420
+ var GROUP_INVITE_METADATA_KEY = "group_invite_pending_v1";
29415
29421
  function clampReconnectDelaySeconds(value, fallback, upper = RECONNECT_MAX_BASE_DELAY_SECONDS) {
29416
29422
  const parsed = Number(value);
29417
29423
  const seconds = Number.isFinite(parsed) ? parsed : fallback;
@@ -29804,6 +29810,9 @@ var _AUNClient = class _AUNClient {
29804
29810
  __publicField(this, "_mailFacade");
29805
29811
  __publicField(this, "_groupFacade");
29806
29812
  __publicField(this, "_streamFacade");
29813
+ __publicField(this, "_groupInvitePending", /* @__PURE__ */ new Map());
29814
+ __publicField(this, "_groupInviteStateAid", null);
29815
+ __publicField(this, "_groupInviteStateLoad", null);
29807
29816
  /**
29808
29817
  * 处理 V2 push 通知:自动 pull + decrypt + emit。
29809
29818
  */
@@ -29927,6 +29936,15 @@ var _AUNClient = class _AUNClient {
29927
29936
  this._dispatcher.subscribe("_raw.group.changed", (data) => {
29928
29937
  this._onRawGroupChanged(data);
29929
29938
  });
29939
+ this._dispatcher.subscribe("_raw.group.invite_created", (data) => {
29940
+ this._safeAsync(this._onRawGroupInviteReceived(data, "push"));
29941
+ });
29942
+ this._dispatcher.subscribe("_raw.group.invite_received", (data) => {
29943
+ this._safeAsync(this._onRawGroupInviteReceived(data, "push"));
29944
+ });
29945
+ this._dispatcher.subscribe("_raw.group.invite_finalized", (data) => {
29946
+ this._safeAsync(this._onRawGroupInviteFinalized(data));
29947
+ });
29930
29948
  this._dispatcher.subscribe("_raw.peer.v2.message_received", (data) => {
29931
29949
  this._safeAsync(this._onV2PushNotification(data));
29932
29950
  });
@@ -30474,6 +30492,8 @@ var _AUNClient = class _AUNClient {
30474
30492
  }
30475
30493
  this._aidStore = store;
30476
30494
  const payload = { ...params2 };
30495
+ const initialMembers = payload.members;
30496
+ delete payload.members;
30477
30497
  if (payload.group_name === void 0 && payload.groupName !== void 0) {
30478
30498
  payload.group_name = payload.groupName;
30479
30499
  delete payload.groupName;
@@ -30503,7 +30523,8 @@ var _AUNClient = class _AUNClient {
30503
30523
  const result2 = { ...createdMap, ...boundMap, group: { ...createdGroup, ...boundGroup } };
30504
30524
  const postprocessParams2 = { ...payload };
30505
30525
  delete postprocessParams2._defer_group_ready_postprocess;
30506
- return await this._groupState.postprocessResult("group.create", postprocessParams2, result2);
30526
+ const processed2 = await this._groupState.postprocessResult("group.create", postprocessParams2, result2);
30527
+ return await this._inviteInitialGroupMembers(processed2, groupId, initialMembers);
30507
30528
  }
30508
30529
  const pendingKey = `create:${String(payload.group_name).trim().toLowerCase()}`;
30509
30530
  const keystore = store._keystore;
@@ -30553,7 +30574,29 @@ var _AUNClient = class _AUNClient {
30553
30574
  }
30554
30575
  const postprocessParams = { ...payload };
30555
30576
  delete postprocessParams._defer_group_ready_postprocess;
30556
- return await this._groupState.postprocessResult("group.create", postprocessParams, result);
30577
+ const processed = await this._groupState.postprocessResult("group.create", postprocessParams, result);
30578
+ return await this._inviteInitialGroupMembers(processed, String(group.group_id ?? groupAid).trim(), initialMembers);
30579
+ }
30580
+ async _inviteInitialGroupMembers(result, groupId, members) {
30581
+ const invites = [];
30582
+ const inviteErrors = [];
30583
+ const seen = /* @__PURE__ */ new Set();
30584
+ if (Array.isArray(members)) {
30585
+ for (const item of members) {
30586
+ const aid = typeof item === "string" ? item.trim() : isJsonObject(item) ? String(item.aid ?? "").trim() : "";
30587
+ const memberType = isJsonObject(item) ? String(item.member_type ?? "").trim() : "";
30588
+ if (!aid || aid === String(this._aid ?? this._currentAid?.aid ?? "").trim() || seen.has(aid)) continue;
30589
+ seen.add(aid);
30590
+ const params2 = { group_id: groupId, invitee_aid: aid };
30591
+ if (memberType) params2.member_type = memberType;
30592
+ try {
30593
+ invites.push(await this.group.inviteMember(params2));
30594
+ } catch (err) {
30595
+ inviteErrors.push({ aid, error: err instanceof Error ? err.message : String(err) });
30596
+ }
30597
+ }
30598
+ }
30599
+ return { ...isJsonObject(result) ? result : {}, invites, invite_errors: inviteErrors };
30557
30600
  }
30558
30601
  _groupAgentMdContent(groupAid, params2 = {}, group = {}) {
30559
30602
  const explicit = params2.group_agent_md ?? params2.groupAgentMd ?? params2.content;
@@ -31135,6 +31178,239 @@ var _AUNClient = class _AUNClient {
31135
31178
  const member = isJsonObject(result.member) ? result.member : null;
31136
31179
  return member ? String(member.group_id ?? member.groupId ?? member.group_aid ?? member.groupAid ?? "") : "";
31137
31180
  }
31181
+ groupInviteKey(groupId, inviteeAid) {
31182
+ const group = normalizeGroupId(groupId);
31183
+ const invitee = String(inviteeAid ?? "").trim().toLowerCase();
31184
+ return group && invitee ? `${group}
31185
+ ${invitee}` : "";
31186
+ }
31187
+ async ensureGroupInviteStateLoaded() {
31188
+ const aid = String(this._aid ?? "").trim();
31189
+ if (!aid) {
31190
+ this._groupInvitePending.clear();
31191
+ this._groupInviteStateAid = null;
31192
+ return;
31193
+ }
31194
+ if (this._groupInviteStateAid === aid) return;
31195
+ if (this._groupInviteStateLoad) {
31196
+ await this._groupInviteStateLoad;
31197
+ if (this._groupInviteStateAid === aid) return;
31198
+ }
31199
+ this._groupInviteStateLoad = (async () => {
31200
+ this._groupInvitePending.clear();
31201
+ this._groupInviteStateAid = aid;
31202
+ const getter = this._tokenStore?.getMetadata;
31203
+ if (typeof getter !== "function") return;
31204
+ try {
31205
+ const raw = await getter.call(this._tokenStore, aid, GROUP_INVITE_METADATA_KEY);
31206
+ const parsed = raw ? JSON.parse(String(raw)) : {};
31207
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return;
31208
+ for (const [key, value] of Object.entries(parsed)) {
31209
+ if (value && typeof value === "object" && !Array.isArray(value)) {
31210
+ this._groupInvitePending.set(key, value);
31211
+ }
31212
+ }
31213
+ } catch (exc) {
31214
+ this._clientLog.debug(`group invite state restore skipped: ${String(exc)}`);
31215
+ }
31216
+ })().finally(() => {
31217
+ this._groupInviteStateLoad = null;
31218
+ });
31219
+ return this._groupInviteStateLoad;
31220
+ }
31221
+ async persistGroupInviteState() {
31222
+ const aid = String(this._aid ?? "").trim();
31223
+ const setter = this._tokenStore?.setMetadata;
31224
+ if (!aid || typeof setter !== "function") return;
31225
+ const state = {};
31226
+ for (const [key, value] of this._groupInvitePending) state[key] = value;
31227
+ try {
31228
+ await setter.call(this._tokenStore, aid, GROUP_INVITE_METADATA_KEY, JSON.stringify(state));
31229
+ } catch (exc) {
31230
+ this._clientLog.debug(`group invite state persist skipped: ${String(exc)}`);
31231
+ }
31232
+ }
31233
+ inviteArray(value, singular) {
31234
+ const values = Array.isArray(value) ? value : singular !== void 0 ? [singular] : [];
31235
+ return [...new Set(values.map((item) => String(item ?? "").trim()).filter(Boolean))];
31236
+ }
31237
+ inviteBool(value) {
31238
+ return value === true || value === 1 || String(value ?? "").trim().toLowerCase() === "true";
31239
+ }
31240
+ async _onRawGroupInviteReceived(data, source = "push") {
31241
+ if (!isJsonObject(data)) return;
31242
+ const groupId = normalizeGroupId(data.group_id ?? data.groupId ?? data.group_aid ?? data.groupAid);
31243
+ const inviteeAid = String(data.invitee_aid ?? data.inviteeAid ?? this._aid ?? "").trim();
31244
+ if (!groupId || !inviteeAid || this._aid && inviteeAid.toLowerCase() !== this._aid.toLowerCase()) return;
31245
+ if (String(data.status ?? "pending").trim().toLowerCase() !== "pending") {
31246
+ await this._onRawGroupInviteFinalized(data);
31247
+ return;
31248
+ }
31249
+ await this.ensureGroupInviteStateLoaded();
31250
+ const key = this.groupInviteKey(groupId, inviteeAid);
31251
+ if (!key) return;
31252
+ const inviteIds = this.inviteArray(data.invite_ids, data.invite_id);
31253
+ const inviterAids = this.inviteArray(data.inviter_aids, data.inviter_aid);
31254
+ const existing = this._groupInvitePending.get(key);
31255
+ if (existing) {
31256
+ existing.invite_ids = [.../* @__PURE__ */ new Set([...this.inviteArray(existing.invite_ids), ...inviteIds])];
31257
+ existing.inviter_aids = [.../* @__PURE__ */ new Set([...this.inviteArray(existing.inviter_aids), ...inviterAids])];
31258
+ await this.persistGroupInviteState();
31259
+ return;
31260
+ }
31261
+ const payload = {
31262
+ ...data,
31263
+ group_id: groupId,
31264
+ group_aid: groupId,
31265
+ invitee_aid: inviteeAid,
31266
+ invite_ids: inviteIds,
31267
+ inviter_aids: inviterAids,
31268
+ role: data.role ?? "member",
31269
+ member_type: data.member_type ?? data.memberType ?? "human",
31270
+ created_at: data.created_at ?? data.createdAt ?? 0,
31271
+ expires_at: data.expires_at ?? data.expiresAt ?? 0,
31272
+ status: "pending"
31273
+ };
31274
+ delete payload.groupId;
31275
+ delete payload.groupAid;
31276
+ delete payload.inviteeAid;
31277
+ delete payload.inviterAid;
31278
+ this._groupInvitePending.set(key, payload);
31279
+ await this.persistGroupInviteState();
31280
+ await this._publishAppEvent("group.invite_received", JSON.parse(JSON.stringify(payload)), source);
31281
+ }
31282
+ async _onRawGroupInviteFinalized(data) {
31283
+ if (!isJsonObject(data)) return;
31284
+ await this.ensureGroupInviteStateLoaded();
31285
+ const groupId = normalizeGroupId(data.group_id ?? data.groupId ?? data.group_aid ?? data.groupAid);
31286
+ const inviteeAid = String(data.invitee_aid ?? data.inviteeAid ?? this._aid ?? "").trim();
31287
+ if (this._aid && inviteeAid.toLowerCase() !== this._aid.toLowerCase()) return;
31288
+ const inviteId = String(data.invite_id ?? "").trim();
31289
+ const key = this.groupInviteKey(groupId, inviteeAid);
31290
+ const status = String(data.status ?? "").trim().toLowerCase();
31291
+ const aggregateStatus = String(data.aggregate_status ?? data.aggregateStatus ?? "").trim().toLowerCase();
31292
+ const pendingIds = this.inviteArray(data.pending_invite_ids ?? data.pendingInviteIds);
31293
+ const pendingInviters = this.inviteArray(data.pending_inviter_aids ?? data.pendingInviterAids);
31294
+ const finalizedIds = this.inviteBool(data.remaining_pending) && inviteId ? [inviteId] : this.inviteArray(data.invite_ids, data.invite_id);
31295
+ const finalizedInviters = this.inviteArray(data.inviter_aids, data.inviter_aid);
31296
+ const leavesPending = this.inviteBool(data.remaining_pending) || this.inviteBool(data.pending) || status === "pending" || aggregateStatus === "pending" || pendingIds.length > 0;
31297
+ let changed = false;
31298
+ const candidates = [];
31299
+ if (key) {
31300
+ const current = this._groupInvitePending.get(key);
31301
+ if (current) candidates.push([key, current]);
31302
+ }
31303
+ if (candidates.length === 0 && inviteId) {
31304
+ for (const [itemKey, item] of this._groupInvitePending) {
31305
+ if (this.inviteArray(item.invite_ids).includes(inviteId)) candidates.push([itemKey, item]);
31306
+ }
31307
+ }
31308
+ for (const [itemKey, item] of candidates) {
31309
+ if (!leavesPending) {
31310
+ this._groupInvitePending.delete(itemKey);
31311
+ changed = true;
31312
+ continue;
31313
+ }
31314
+ const currentIds = this.inviteArray(item.invite_ids);
31315
+ const currentInviters = this.inviteArray(item.inviter_aids);
31316
+ const finalizedIndexes = new Set(currentIds.map((id, index) => finalizedIds.includes(id) ? index : -1).filter((index) => index >= 0));
31317
+ const nextIds = pendingIds.length > 0 ? pendingIds : finalizedIds.length > 0 ? currentIds.filter((id) => !finalizedIds.includes(id)) : currentIds;
31318
+ const nextInviters = pendingInviters.length > 0 ? pendingInviters : finalizedInviters.length > 0 ? currentInviters.filter((aid) => !finalizedInviters.includes(aid)) : finalizedIndexes.size > 0 ? currentInviters.filter((_aid, index) => !finalizedIndexes.has(index)) : currentInviters;
31319
+ if (nextIds.length === 0) {
31320
+ this._groupInvitePending.delete(itemKey);
31321
+ } else {
31322
+ item.invite_ids = nextIds;
31323
+ item.inviter_aids = nextInviters;
31324
+ }
31325
+ changed = true;
31326
+ }
31327
+ if (changed) await this.persistGroupInviteState();
31328
+ }
31329
+ async _onGroupInviteActionResult(method, params2, result, error) {
31330
+ if (method === "group.list_my_invites" && !error && (isJsonObject(result) || Array.isArray(result))) {
31331
+ await this.restoreGroupInvitesFromResult(result, params2);
31332
+ return;
31333
+ }
31334
+ if (!["group.accept_invite", "group.reject_invite", "group.revoke_invite"].includes(method)) return;
31335
+ const text3 = String(error instanceof Error ? error.message : error ?? "").toLowerCase();
31336
+ const resultStatus = isJsonObject(result) ? String(result.status ?? "").toLowerCase() : "";
31337
+ const aggregateResultStatus = isJsonObject(result) ? String(result.aggregate_status ?? result.aggregateStatus ?? "").toLowerCase() : "";
31338
+ const terminalStatus = resultStatus || aggregateResultStatus;
31339
+ const pendingResult = resultStatus === "pending" || aggregateResultStatus === "pending" || isJsonObject(result) && (this.inviteBool(result.remaining_pending) || this.inviteBool(result.pending));
31340
+ if (pendingResult) {
31341
+ if (isJsonObject(result) && (this.inviteBool(result.remaining_pending) || this.inviteArray(result.pending_invite_ids ?? result.pendingInviteIds).length > 0)) {
31342
+ await this._onRawGroupInviteFinalized({ ...params2, ...result });
31343
+ }
31344
+ return;
31345
+ }
31346
+ const terminal = !pendingResult && (!error || ["accepted", "rejected", "revoked", "expired", "ended"].includes(terminalStatus) || /ended|expired|not.*pending|no longer.*pending|not found|finalized|already\s+(?:been\s+)?(?:accepted|rejected|revoked)/.test(text3));
31347
+ if (!terminal) return;
31348
+ await this._onRawGroupInviteFinalized({
31349
+ ...params2,
31350
+ ...isJsonObject(result) ? result : {},
31351
+ status: terminalStatus || "ended"
31352
+ });
31353
+ }
31354
+ async restoreGroupInvitesFromResult(result, requestParams = {}) {
31355
+ const seen = /* @__PURE__ */ new Set();
31356
+ const visitedPages = /* @__PURE__ */ new Set();
31357
+ let current = result;
31358
+ let currentParams = { ...requestParams };
31359
+ let finalPage = false;
31360
+ while (!finalPage) {
31361
+ const root = Array.isArray(current) ? null : isJsonObject(current) ? current : null;
31362
+ const nestedValue = root && isJsonObject(root.data) ? root.data : root;
31363
+ const nested = nestedValue ?? {};
31364
+ const rawItems = Array.isArray(nested.items) ? nested.items : Array.isArray(nested.invites) ? nested.invites : Array.isArray(current) ? current : [];
31365
+ for (const item of rawItems) {
31366
+ if (!isJsonObject(item)) continue;
31367
+ const status = String(item.status ?? "pending").toLowerCase();
31368
+ const gid = normalizeGroupId(item.group_id ?? item.group_aid ?? item.groupId ?? item.groupAid);
31369
+ const aid = String(item.invitee_aid ?? item.inviteeAid ?? this._aid ?? "").trim();
31370
+ if (this._aid && aid.toLowerCase() !== this._aid.toLowerCase()) continue;
31371
+ const key = this.groupInviteKey(gid, aid);
31372
+ if (status === "pending" && key) {
31373
+ seen.add(key);
31374
+ await this._onRawGroupInviteReceived(item, "pull");
31375
+ } else if (key) {
31376
+ seen.add(key);
31377
+ await this._onRawGroupInviteFinalized(item);
31378
+ }
31379
+ }
31380
+ const hasMore = this.inviteBool(nested.has_more ?? nested.hasMore ?? (root ? root.has_more ?? root.hasMore : void 0));
31381
+ if (!hasMore) {
31382
+ finalPage = true;
31383
+ break;
31384
+ }
31385
+ const page = Number(nested.page ?? root?.page ?? currentParams.page ?? 1);
31386
+ const nextPageValue = nested.next_page ?? nested.nextPage ?? root?.next_page ?? root?.nextPage;
31387
+ const nextPage = Number(nextPageValue ?? page + 1);
31388
+ if (!Number.isFinite(nextPage) || nextPage <= 0 || visitedPages.has(nextPage) || nextPage === page) break;
31389
+ visitedPages.add(page);
31390
+ currentParams = { ...currentParams, page: nextPage };
31391
+ if (currentParams.size === void 0) {
31392
+ const size = nested.size ?? root?.size;
31393
+ if (size !== void 0) currentParams.size = size;
31394
+ }
31395
+ current = await this.call("group.list_my_invites", currentParams);
31396
+ }
31397
+ await this.ensureGroupInviteStateLoaded();
31398
+ let changed = false;
31399
+ for (const key of this._groupInvitePending.keys()) {
31400
+ if (!seen.has(key)) {
31401
+ this._groupInvitePending.delete(key);
31402
+ changed = true;
31403
+ }
31404
+ }
31405
+ if (changed) await this.persistGroupInviteState();
31406
+ }
31407
+ async restoreGroupInvitesAfterConnect() {
31408
+ if (!this._aid || this._state !== "connected") return;
31409
+ const result = await this.call("group.list_my_invites", { page: 1, size: 200, _rpc_background: true });
31410
+ if (isJsonObject(result) || Array.isArray(result)) {
31411
+ await this.restoreGroupInvitesFromResult(result, { page: 1, size: 200, _rpc_background: true });
31412
+ }
31413
+ }
31138
31414
  async _onRawGroupChanged(data) {
31139
31415
  const tStart = Date.now();
31140
31416
  const action = String(data?.action ?? "");
@@ -31465,6 +31741,7 @@ var _AUNClient = class _AUNClient {
31465
31741
  }
31466
31742
  this._lifecycle.assertConnectionAttemptOwner(connectionOwner);
31467
31743
  this._startBackgroundTasks();
31744
+ this._safeAsync(this.restoreGroupInvitesAfterConnect());
31468
31745
  const connectionKind = String(params2.connection_kind ?? "long");
31469
31746
  const isShortConnection = connectionKind === "short";
31470
31747
  const hasExplicitBackgroundSync = Object.prototype.hasOwnProperty.call(params2, "background_sync");
@@ -32175,20 +32452,7 @@ var _AUNClient = class _AUNClient {
32175
32452
  return this._delivery.clampAckParams(method, params2);
32176
32453
  }
32177
32454
  _repairPushContiguousBound(ns, pushSeq, hasPayload, label) {
32178
- if (!ns || !Number.isFinite(pushSeq) || pushSeq <= 0) {
32179
- return ns ? this._seqTracker.getContiguousSeq(ns) : 0;
32180
- }
32181
- const contig = this._seqTracker.getContiguousSeq(ns);
32182
- const shouldRepair = contig > pushSeq;
32183
- if (!shouldRepair) return contig;
32184
- const repairedTo = Math.max(0, pushSeq - 1);
32185
- this._seqTracker.repairContiguousSeq(ns, repairedTo);
32186
- const repaired = this._seqTracker.getContiguousSeq(ns);
32187
- this._persistRepairedSeq(ns);
32188
- this._clientLog.warn(
32189
- `${label} push repaired contiguous_seq: ns=${ns} payload=${hasPayload} push_seq=${pushSeq} contiguous=${contig}->${repaired}`
32190
- );
32191
- return repaired;
32455
+ return ns ? this._seqTracker.getContiguousSeq(ns) : 0;
32192
32456
  }
32193
32457
  async _ensureV2SessionReady(method, errorMessage3) {
32194
32458
  if (!this._v2SessionMatchesIdentity()) {