@agentunion/fastaun-browser 0.5.10 → 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 (44) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/_packed_docs/CHANGELOG.md +62 -0
  3. package/_packed_docs/INDEX.md +3 -3
  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/06-API/346/211/213/345/206/214.md +59 -0
  7. package/_packed_docs/sdk/09-group-rpc-manual.md +29 -2
  8. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +3 -2
  9. package/_packed_docs/sdk/INDEX.md +2 -1
  10. package/dist/bundle.js +1145 -381
  11. package/dist/client/delivery.d.ts +12 -4
  12. package/dist/client/delivery.d.ts.map +1 -1
  13. package/dist/client/delivery.js +341 -273
  14. package/dist/client/delivery.js.map +1 -1
  15. package/dist/client/group-state.d.ts.map +1 -1
  16. package/dist/client/group-state.js +1 -0
  17. package/dist/client/group-state.js.map +1 -1
  18. package/dist/client/rpc-pipeline.d.ts.map +1 -1
  19. package/dist/client/rpc-pipeline.js +38 -13
  20. package/dist/client/rpc-pipeline.js.map +1 -1
  21. package/dist/client/v2-e2ee.d.ts +3 -1
  22. package/dist/client/v2-e2ee.d.ts.map +1 -1
  23. package/dist/client/v2-e2ee.js +211 -75
  24. package/dist/client/v2-e2ee.js.map +1 -1
  25. package/dist/client.d.ts +15 -0
  26. package/dist/client.d.ts.map +1 -1
  27. package/dist/client.js +402 -20
  28. package/dist/client.js.map +1 -1
  29. package/dist/facades.d.ts +7 -0
  30. package/dist/facades.d.ts.map +1 -1
  31. package/dist/facades.js +44 -0
  32. package/dist/facades.js.map +1 -1
  33. package/dist/seq-tracker.d.ts +2 -2
  34. package/dist/seq-tracker.d.ts.map +1 -1
  35. package/dist/seq-tracker.js +11 -7
  36. package/dist/seq-tracker.js.map +1 -1
  37. package/dist/tools/cross-sdk-agent.js +50 -12
  38. package/dist/tools/cross-sdk-agent.js.map +1 -1
  39. package/dist/transport.d.ts.map +1 -1
  40. package/dist/transport.js +162 -15
  41. package/dist/transport.js.map +1 -1
  42. package/dist/version.d.ts +1 -1
  43. package/dist/version.js +1 -1
  44. 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.10";
463
+ var VERSION = "0.5.12";
464
464
 
465
465
  // src/types.ts
466
466
  var ConnectionState = /* @__PURE__ */ ((ConnectionState2) => {
@@ -1296,6 +1296,8 @@ var GatewayDiscovery = class {
1296
1296
  var MAX_WS_PAYLOAD_SIZE = 1e6;
1297
1297
  var MAX_RPC_INFLIGHT = 16;
1298
1298
  var MAX_BACKGROUND_RPC_INFLIGHT = 8;
1299
+ var WORKER_START_TIMEOUT_MS = 2e3;
1300
+ var WORKER_OPEN_TIMEOUT_MS = 2e3;
1299
1301
  var _noopLog3 = { error: () => {
1300
1302
  }, warn: () => {
1301
1303
  }, info: () => {
@@ -1304,6 +1306,7 @@ var _noopLog3 = { error: () => {
1304
1306
  var _rpcIdCounter = 0;
1305
1307
  var WORKER_WEBSOCKET_SOURCE = `
1306
1308
  let socket = null;
1309
+ self.postMessage({ type: 'ready' });
1307
1310
  self.onmessage = (event) => {
1308
1311
  const command = event.data || {};
1309
1312
  if (command.type === 'connect') {
@@ -1343,11 +1346,19 @@ var _WorkerWebSocketProxy = class _WorkerWebSocketProxy {
1343
1346
  __publicField(this, "onmessage", null);
1344
1347
  __publicField(this, "onerror", null);
1345
1348
  __publicField(this, "onclose", null);
1349
+ __publicField(this, "_url");
1346
1350
  __publicField(this, "_worker");
1347
1351
  __publicField(this, "_workerUrl");
1352
+ __publicField(this, "_nativeSocket", null);
1353
+ __publicField(this, "_workerReady", false);
1354
+ __publicField(this, "_opened", false);
1355
+ __publicField(this, "_closedEventEmitted", false);
1356
+ __publicField(this, "_workerStartTimer", null);
1357
+ __publicField(this, "_workerOpenTimer", null);
1348
1358
  __publicField(this, "_listeners", /* @__PURE__ */ new Map());
1349
1359
  __publicField(this, "_pendingSends", /* @__PURE__ */ new Map());
1350
1360
  __publicField(this, "_sendSeq", 0);
1361
+ this._url = url;
1351
1362
  this._workerUrl = URL.createObjectURL(new Blob([WORKER_WEBSOCKET_SOURCE], { type: "text/javascript" }));
1352
1363
  try {
1353
1364
  this._worker = new Worker(this._workerUrl);
@@ -1355,17 +1366,35 @@ var _WorkerWebSocketProxy = class _WorkerWebSocketProxy {
1355
1366
  URL.revokeObjectURL(this._workerUrl);
1356
1367
  throw error;
1357
1368
  }
1358
- this._worker.onmessage = (event) => this._handleWorkerMessage(event.data);
1359
- this._worker.onerror = (event) => this._emitError(event.message || "websocket worker error");
1360
- this._worker.postMessage({ type: "connect", url });
1369
+ const worker = this._worker;
1370
+ worker.onmessage = (event) => this._handleWorkerMessage(event.data);
1371
+ worker.onerror = (event) => {
1372
+ event.preventDefault?.();
1373
+ if (!this._opened) {
1374
+ this._fallbackToNative();
1375
+ return;
1376
+ }
1377
+ this._fallbackToNative(true);
1378
+ };
1379
+ this._workerStartTimer = globalThis.setTimeout(() => this._fallbackToNative(), WORKER_START_TIMEOUT_MS);
1361
1380
  }
1362
1381
  send(data) {
1363
1382
  if (this.readyState !== _WorkerWebSocketProxy.OPEN) return Promise.reject(new Error("websocket is not open"));
1383
+ if (this._nativeSocket) {
1384
+ try {
1385
+ this._nativeSocket.send(data);
1386
+ return Promise.resolve();
1387
+ } catch (error) {
1388
+ return Promise.reject(error instanceof Error ? error : new Error(String(error)));
1389
+ }
1390
+ }
1391
+ const worker = this._worker;
1392
+ if (!worker) return Promise.reject(new Error("websocket worker is not available"));
1364
1393
  const id = ++this._sendSeq;
1365
1394
  return new Promise((resolve, reject) => {
1366
1395
  this._pendingSends.set(id, { resolve, reject });
1367
1396
  try {
1368
- this._worker.postMessage({ type: "send", id, data });
1397
+ worker.postMessage({ type: "send", id, data });
1369
1398
  } catch (error) {
1370
1399
  this._pendingSends.delete(id);
1371
1400
  reject(error instanceof Error ? error : new Error(String(error)));
@@ -1375,7 +1404,24 @@ var _WorkerWebSocketProxy = class _WorkerWebSocketProxy {
1375
1404
  close(code, reason) {
1376
1405
  if (this.readyState === _WorkerWebSocketProxy.CLOSED) return;
1377
1406
  this.readyState = _WorkerWebSocketProxy.CLOSING;
1378
- this._worker.postMessage({ type: "close", code, reason });
1407
+ if (this._nativeSocket) {
1408
+ try {
1409
+ this._nativeSocket.close(code, reason);
1410
+ } catch {
1411
+ this._emitClose(new CloseEvent("close", { code: code ?? 1e3, reason: reason ?? "", wasClean: true }));
1412
+ }
1413
+ return;
1414
+ }
1415
+ if (!this._workerReady || !this._worker) {
1416
+ this._disposeWorker();
1417
+ this._emitClose(new CloseEvent("close", { code: code ?? 1e3, reason: reason ?? "", wasClean: true }));
1418
+ return;
1419
+ }
1420
+ try {
1421
+ this._worker.postMessage({ type: "close", code, reason });
1422
+ } catch {
1423
+ this._fallbackToNative();
1424
+ }
1379
1425
  }
1380
1426
  addEventListener(type, listener) {
1381
1427
  const callback = typeof listener === "function" ? listener : (event) => listener.handleEvent(event);
@@ -1387,6 +1433,21 @@ var _WorkerWebSocketProxy = class _WorkerWebSocketProxy {
1387
1433
  if (typeof listener === "function") this._listeners.get(type)?.delete(listener);
1388
1434
  }
1389
1435
  _handleWorkerMessage(reply) {
1436
+ if (reply.type === "ready") {
1437
+ if (this._workerReady || !this._worker) return;
1438
+ this._workerReady = true;
1439
+ this._clearWorkerStartTimer();
1440
+ try {
1441
+ this._worker.postMessage({ type: "connect", url: this._url });
1442
+ this._workerOpenTimer = globalThis.setTimeout(
1443
+ () => this._fallbackToNative(),
1444
+ WORKER_OPEN_TIMEOUT_MS
1445
+ );
1446
+ } catch {
1447
+ this._fallbackToNative();
1448
+ }
1449
+ return;
1450
+ }
1390
1451
  if (reply.type === "send_result" && reply.id !== void 0) {
1391
1452
  const pending = this._pendingSends.get(reply.id);
1392
1453
  if (!pending) return;
@@ -1396,6 +1457,8 @@ var _WorkerWebSocketProxy = class _WorkerWebSocketProxy {
1396
1457
  return;
1397
1458
  }
1398
1459
  if (reply.type === "open") {
1460
+ this._clearWorkerOpenTimer();
1461
+ this._opened = true;
1399
1462
  this.readyState = _WorkerWebSocketProxy.OPEN;
1400
1463
  const event = new Event("open");
1401
1464
  this.onopen?.(event);
@@ -1409,24 +1472,94 @@ var _WorkerWebSocketProxy = class _WorkerWebSocketProxy {
1409
1472
  return;
1410
1473
  }
1411
1474
  if (reply.type === "error") {
1475
+ if (!this._opened && this.readyState === _WorkerWebSocketProxy.CONNECTING) {
1476
+ this._fallbackToNative();
1477
+ return;
1478
+ }
1412
1479
  this._emitError(reply.message || "websocket worker error");
1413
1480
  return;
1414
1481
  }
1415
1482
  if (reply.type === "close") {
1416
- this.readyState = _WorkerWebSocketProxy.CLOSED;
1417
- for (const pending of this._pendingSends.values()) pending.reject(new Error("websocket closed"));
1418
- this._pendingSends.clear();
1419
- const event = new CloseEvent("close", {
1483
+ if (!this._opened && this.readyState === _WorkerWebSocketProxy.CONNECTING) {
1484
+ this._fallbackToNative();
1485
+ return;
1486
+ }
1487
+ this._emitClose(new CloseEvent("close", {
1420
1488
  code: reply.code ?? 1006,
1421
1489
  reason: reply.reason ?? "",
1422
1490
  wasClean: reply.wasClean === true
1423
- });
1424
- this.onclose?.(event);
1425
- this._emit("close", event);
1426
- this._worker.terminate();
1427
- URL.revokeObjectURL(this._workerUrl);
1491
+ }));
1428
1492
  }
1429
1493
  }
1494
+ _fallbackToNative(force = false) {
1495
+ if (this._nativeSocket || this.readyState === _WorkerWebSocketProxy.CLOSED) return;
1496
+ if (!force && (this.readyState !== _WorkerWebSocketProxy.CONNECTING || this._opened)) return;
1497
+ const wasOpened = this._opened;
1498
+ this._opened = false;
1499
+ this.readyState = _WorkerWebSocketProxy.CONNECTING;
1500
+ this._disposeWorker();
1501
+ try {
1502
+ const socket = new WebSocket(this._url);
1503
+ this._nativeSocket = socket;
1504
+ socket.onopen = (event) => {
1505
+ this._opened = true;
1506
+ this.readyState = _WorkerWebSocketProxy.OPEN;
1507
+ if (!wasOpened) {
1508
+ this.onopen?.(event);
1509
+ this._emit("open", event);
1510
+ }
1511
+ };
1512
+ socket.onmessage = (event) => {
1513
+ this.onmessage?.(event);
1514
+ this._emit("message", event);
1515
+ };
1516
+ socket.onerror = (event) => {
1517
+ this.onerror?.(event);
1518
+ this._emit("error", event);
1519
+ };
1520
+ socket.onclose = (event) => this._emitClose(event);
1521
+ } catch (error) {
1522
+ this.readyState = _WorkerWebSocketProxy.CLOSED;
1523
+ this._emitError(error instanceof Error ? error.message : String(error));
1524
+ }
1525
+ }
1526
+ _emitClose(event) {
1527
+ if (this._closedEventEmitted) return;
1528
+ this._closedEventEmitted = true;
1529
+ this.readyState = _WorkerWebSocketProxy.CLOSED;
1530
+ for (const pending of this._pendingSends.values()) pending.reject(new Error("websocket closed"));
1531
+ this._pendingSends.clear();
1532
+ this.onclose?.(event);
1533
+ this._emit("close", event);
1534
+ this._disposeWorker();
1535
+ this._nativeSocket = null;
1536
+ }
1537
+ _clearWorkerStartTimer() {
1538
+ if (this._workerStartTimer === null) return;
1539
+ globalThis.clearTimeout(this._workerStartTimer);
1540
+ this._workerStartTimer = null;
1541
+ }
1542
+ _clearWorkerOpenTimer() {
1543
+ if (this._workerOpenTimer === null) return;
1544
+ globalThis.clearTimeout(this._workerOpenTimer);
1545
+ this._workerOpenTimer = null;
1546
+ }
1547
+ _disposeWorker() {
1548
+ this._clearWorkerStartTimer();
1549
+ this._clearWorkerOpenTimer();
1550
+ for (const pending of this._pendingSends.values()) pending.reject(new Error("websocket worker unavailable"));
1551
+ this._pendingSends.clear();
1552
+ const worker = this._worker;
1553
+ this._worker = null;
1554
+ if (worker) {
1555
+ worker.onmessage = null;
1556
+ worker.onerror = null;
1557
+ worker.terminate();
1558
+ }
1559
+ const workerUrl = this._workerUrl;
1560
+ this._workerUrl = null;
1561
+ if (workerUrl && typeof URL.revokeObjectURL === "function") URL.revokeObjectURL(workerUrl);
1562
+ }
1430
1563
  _emitError(message) {
1431
1564
  const event = new ErrorEvent("error", { message });
1432
1565
  this.onerror?.(event);
@@ -1614,6 +1747,9 @@ var EVENT_NAME_MAP = {
1614
1747
  "message.recalled": "message.recalled",
1615
1748
  "message.ack": "message.ack",
1616
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",
1617
1753
  "group.message_created": "group.message_created",
1618
1754
  "group.message_recalled": "group.message_recalled",
1619
1755
  "group.state_committed": "group.state_committed",
@@ -4070,7 +4206,6 @@ var SeqTracker = class {
4070
4206
  if (headSeq === t.headSeq) {
4071
4207
  if (windowStartSeq <= t.tailSeq) return false;
4072
4208
  t.tailSeq = windowStartSeq;
4073
- this._normalizeWindow(t);
4074
4209
  return true;
4075
4210
  }
4076
4211
  if (t.headSeq + pageLimit < headSeq && windowStartSeq <= t.headSeq + 1) {
@@ -4079,7 +4214,6 @@ var SeqTracker = class {
4079
4214
  if (windowStartSeq > t.headSeq + 1) t.tailSeq = windowStartSeq;
4080
4215
  t.headSeq = headSeq;
4081
4216
  t.maxSeenSeq = Math.max(t.maxSeenSeq, headSeq);
4082
- this._normalizeWindow(t);
4083
4217
  return true;
4084
4218
  }
4085
4219
  /** Push 专用:只扩展上界 maxSeenSeq,不动 contiguousSeq。 */
@@ -4154,8 +4288,8 @@ var SeqTracker = class {
4154
4288
  }
4155
4289
  return true;
4156
4290
  }
4157
- /** 提交已校验的 Forward 原始页;页内缺号视为服务端永久空号。 */
4158
- onPullResult(ns, messages, afterSeq) {
4291
+ /** 提交已校验的 Forward 原始页;缺号只有服务端明确证明时才能跳过。 */
4292
+ onPullResult(ns, messages, afterSeq, contiguousSeq) {
4159
4293
  const t = this._get(ns);
4160
4294
  const pulledSeqs = /* @__PURE__ */ new Set();
4161
4295
  for (const m of messages) {
@@ -4185,6 +4319,7 @@ var SeqTracker = class {
4185
4319
  if (maxPulled > 0) {
4186
4320
  t.maxSeenSeq = Math.max(t.maxSeenSeq, maxPulled);
4187
4321
  }
4322
+ this._tryAdvance(t);
4188
4323
  if (maxPulled > t.contiguousSeq) {
4189
4324
  for (const [key, probe] of t.pendingGaps) {
4190
4325
  if (probe.gapEnd <= maxPulled) t.pendingGaps.delete(key);
@@ -4194,7 +4329,11 @@ var SeqTracker = class {
4194
4329
  }
4195
4330
  t.contiguousSeq = maxPulled;
4196
4331
  }
4197
- 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);
4198
4337
  const remaining = [...t.receivedSeqs].filter((seq2) => seq2 > t.contiguousSeq);
4199
4338
  if (remaining.length > 0) {
4200
4339
  const firstPending = Math.min(...remaining);
@@ -4373,7 +4512,6 @@ var SeqTracker = class {
4373
4512
  restored.tailSeq = Number(tailRaw);
4374
4513
  restored.headSeq = Number(headRaw);
4375
4514
  restored.maxSeenSeq = Math.max(ack, restored.headSeq);
4376
- this._normalizeWindow(restored);
4377
4515
  this._trackers.set(ns, restored);
4378
4516
  }
4379
4517
  return issues;
@@ -4873,10 +5011,33 @@ function positiveSafeSequenceHint(value) {
4873
5011
  const parsed = Number(value);
4874
5012
  return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : 0;
4875
5013
  }
5014
+ function hasNonEmptyInlineMessage(value) {
5015
+ return isJsonObject(value) && Object.keys(value).length > 0;
5016
+ }
4876
5017
  function nonNegativeSafeSequenceHint(value) {
4877
5018
  if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) return null;
4878
5019
  return value;
4879
5020
  }
5021
+ function canonicalDeliverySource(source) {
5022
+ switch (String(source ?? "").trim()) {
5023
+ case "group-push":
5024
+ case "ordered":
5025
+ case "legacy":
5026
+ return "push";
5027
+ case "tail":
5028
+ case "pull-drained":
5029
+ case "pull_drained":
5030
+ return "pull";
5031
+ case "inline-push":
5032
+ return "inline_push";
5033
+ case "pending-retry":
5034
+ return "pending_retry";
5035
+ case "":
5036
+ return "direct";
5037
+ default:
5038
+ return String(source).trim();
5039
+ }
5040
+ }
4880
5041
  function deduplicatePlainForwardPageMessages(value, afterSeq) {
4881
5042
  if (!Array.isArray(value)) return [];
4882
5043
  const bySeq = /* @__PURE__ */ new Map();
@@ -4905,6 +5066,9 @@ function p2pAppEventFromPlainPullMessage(message) {
4905
5066
  var MessageDeliveryEngine = class {
4906
5067
  constructor(runtime) {
4907
5068
  __publicField(this, "runtime");
5069
+ __publicField(this, "syncRun", 0);
5070
+ __publicField(this, "syncingNamespaces", /* @__PURE__ */ new Set());
5071
+ __publicField(this, "syncSessions", /* @__PURE__ */ new Map());
4908
5072
  __publicField(this, "pendingPullDeliveryChanges", null);
4909
5073
  __publicField(this, "deliveryGeneration", 0);
4910
5074
  __publicField(this, "realtimeTailResults", null);
@@ -4925,6 +5089,7 @@ var MessageDeliveryEngine = class {
4925
5089
  this.runtime = runtime;
4926
5090
  }
4927
5091
  resetInlineAckState() {
5092
+ this.syncStopped("aborted");
4928
5093
  this.inlineGeneration += 1;
4929
5094
  this.deliveryGeneration += 1;
4930
5095
  this.pendingPullDeliveryChanges = null;
@@ -4940,6 +5105,8 @@ var MessageDeliveryEngine = class {
4940
5105
  this.pendingP2PInlineAcks = null;
4941
5106
  this.pendingGroupInlineAcks = null;
4942
5107
  this.pendingGroupInlineAckNamespaces = null;
5108
+ this.syncingNamespaces.clear();
5109
+ this.syncSessions.clear();
4943
5110
  }
4944
5111
  isInlineGenerationCurrent(generation) {
4945
5112
  return generation === this.inlineGeneration;
@@ -4967,6 +5134,91 @@ var MessageDeliveryEngine = class {
4967
5134
  endRealtimeSync(ns) {
4968
5135
  this.realtimeSyncing?.delete(ns);
4969
5136
  }
5137
+ onPullStarted(ns) {
5138
+ if (!ns || this.syncingNamespaces.has(ns)) return;
5139
+ if (this.syncingNamespaces.size === 0) {
5140
+ this.syncRun += 1;
5141
+ this.syncSessions.clear();
5142
+ this.runtime.client._dispatcher?.enqueue?.("sync.started", {
5143
+ run_id: this.syncRun,
5144
+ source: "pull",
5145
+ syncing: true,
5146
+ namespaces_pending: 1,
5147
+ received_total: 0,
5148
+ namespace: ns,
5149
+ started_at: Date.now()
5150
+ });
5151
+ }
5152
+ this.syncingNamespaces.add(ns);
5153
+ this.syncSessions.set(ns, { pages: 0, raw: 0, pulled: 0 });
5154
+ }
5155
+ onPullPage(ns, pulledCount, hasMore, maxSeen, remaining, rawCount = pulledCount, currentSeq, targetSeq) {
5156
+ this.onPullStarted(ns);
5157
+ if (!ns) return;
5158
+ const session = this.syncSessions.get(ns) ?? { pages: 0, raw: 0, pulled: 0 };
5159
+ session.pages += 1;
5160
+ session.raw += Math.max(0, rawCount);
5161
+ session.pulled += Math.max(0, pulledCount);
5162
+ this.syncSessions.set(ns, session);
5163
+ const received = session.pulled;
5164
+ const progress = {
5165
+ run_id: this.syncRun,
5166
+ source: "pull",
5167
+ namespace: ns,
5168
+ page: session.pages,
5169
+ page_raw_count: Math.max(0, rawCount),
5170
+ page_pulled_count: Math.max(0, pulledCount),
5171
+ pulled_count: received,
5172
+ received_total: received,
5173
+ batch_size: Math.max(0, pulledCount),
5174
+ estimated_remaining: hasMore ? void 0 : 0,
5175
+ max_seen: maxSeen,
5176
+ has_more: hasMore
5177
+ };
5178
+ if (typeof currentSeq === "number" && Number.isSafeInteger(currentSeq) && currentSeq >= 0) {
5179
+ progress.current_seq = currentSeq;
5180
+ }
5181
+ if (typeof targetSeq === "number" && Number.isSafeInteger(targetSeq) && targetSeq >= 0) {
5182
+ progress.target_seq = targetSeq;
5183
+ }
5184
+ if (typeof remaining === "number" && Number.isSafeInteger(remaining) && remaining >= 0) {
5185
+ progress.remaining = remaining;
5186
+ progress.estimated_remaining = progress.remaining;
5187
+ }
5188
+ this.runtime.client._dispatcher?.enqueue?.("sync.progress", progress);
5189
+ }
5190
+ onPullAborted() {
5191
+ this.syncStopped("aborted");
5192
+ }
5193
+ syncStopped(reason = "pull_drained") {
5194
+ if (this.syncingNamespaces.size === 0) return;
5195
+ const sessions = [...this.syncSessions.entries()];
5196
+ const receivedTotal = sessions.reduce((total, [, session]) => total + session.pulled, 0);
5197
+ this.syncingNamespaces.clear();
5198
+ this.syncSessions.clear();
5199
+ const payload = {
5200
+ run_id: this.syncRun,
5201
+ source: "pull",
5202
+ syncing: false,
5203
+ namespaces_pending: 0,
5204
+ received_total: receivedTotal,
5205
+ reason,
5206
+ stopped_at: Date.now()
5207
+ };
5208
+ if (sessions.length === 1) {
5209
+ const [namespace, session] = sessions[0];
5210
+ payload.namespace = namespace;
5211
+ payload.pages = session.pages;
5212
+ payload.pulled_count = session.pulled;
5213
+ } else if (sessions.length > 1) {
5214
+ payload.namespaces = sessions.map(([namespace]) => namespace);
5215
+ payload.pages = sessions.reduce((total, [, session]) => total + session.pages, 0);
5216
+ payload.pulled_count = receivedTotal;
5217
+ }
5218
+ this.runtime.client._dispatcher?.enqueue?.("sync.stopped", {
5219
+ ...payload
5220
+ });
5221
+ }
4970
5222
  hasPendingPull(ns) {
4971
5223
  const pending = ns.startsWith("p2p:") ? this.pendingP2pPullUpper : this.pendingGroupPullUpper;
4972
5224
  return (pending?.get(ns) ?? 0) > 0;
@@ -4978,7 +5230,7 @@ var MessageDeliveryEngine = class {
4978
5230
  onPullWorkSettled() {
4979
5231
  const pipeline = this.runtime.client._rpcPipeline;
4980
5232
  if (pipeline?.hasAnyPullActivity?.() === true) return;
4981
- void this.flushPullDeliveryChanges();
5233
+ void this.flushPullDeliveryChanges().finally(() => this.syncStopped("pull_drained"));
4982
5234
  }
4983
5235
  deliveryChangeNamespace(event, payload, ns = "") {
4984
5236
  if (event === "message.received") return "p2p";
@@ -5024,7 +5276,11 @@ var MessageDeliveryEngine = class {
5024
5276
  if (batch.generation !== this.deliveryGeneration) return;
5025
5277
  const changes = this.deliveryChangesPayload(batch.changes);
5026
5278
  if (changes.length === 0) return;
5027
- this.runtime.client._dispatcher.enqueue("delivery.changed", { trigger: batch.trigger, changes });
5279
+ this.runtime.client._dispatcher.enqueue("delivery.changed", {
5280
+ trigger: batch.trigger,
5281
+ source: batch.trigger,
5282
+ changes
5283
+ });
5028
5284
  }
5029
5285
  async flushPullDeliveryChanges() {
5030
5286
  const generation = this.deliveryGeneration;
@@ -5034,7 +5290,11 @@ var MessageDeliveryEngine = class {
5034
5290
  if (generation !== this.deliveryGeneration) return;
5035
5291
  const changes = this.deliveryChangesPayload(pending);
5036
5292
  if (changes.length === 0) return;
5037
- this.runtime.client._dispatcher.enqueue("delivery.changed", { trigger: "pull_drained", changes });
5293
+ this.runtime.client._dispatcher.enqueue("delivery.changed", {
5294
+ trigger: "pull_drained",
5295
+ source: "pull",
5296
+ changes
5297
+ });
5038
5298
  }
5039
5299
  recordPendingPull(ns, seq2) {
5040
5300
  if (!ns || !Number.isSafeInteger(seq2) || seq2 <= 0) return;
@@ -5537,18 +5797,22 @@ var MessageDeliveryEngine = class {
5537
5797
  }
5538
5798
  const message = normalizeGroupMentionMode(rawMessage);
5539
5799
  if (this.recallEventFromGroupMessage(message)) {
5540
- if (await this.publishGroupRecallTombstone(groupId, seq2, message)) {
5800
+ if (await this.publishGroupRecallTombstone(groupId, seq2, message, "pull")) {
5541
5801
  this.ensurePullOperationCurrent();
5542
5802
  this.markPublishedSeq(ns, seq2);
5543
5803
  publishedCount += 1;
5544
5804
  }
5805
+ } else if (this.isSelfSentGroupMessage(message)) {
5806
+ this.markPublishedSeq(ns, seq2);
5545
5807
  } else if (await this.publishPulledMessage("group.message_created", ns, seq2, message, false)) {
5546
5808
  this.ensurePullOperationCurrent();
5547
5809
  publishedCount += 1;
5548
5810
  }
5549
5811
  }
5550
5812
  this.ensurePullOperationCurrent();
5551
- 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);
5552
5816
  const commitTarget = Math.max(
5553
5817
  client._seqTracker.getContiguousSeq(ns),
5554
5818
  retentionFloor,
@@ -5608,6 +5872,17 @@ var MessageDeliveryEngine = class {
5608
5872
  this.ensurePullOperationCurrent();
5609
5873
  await this.confirmPlainForwardAck(ns, ackMethod, pendingAckSeq, groupId);
5610
5874
  }
5875
+ const remaining = typeof response.remaining === "number" && Number.isSafeInteger(response.remaining) && response.remaining >= 0 ? response.remaining : null;
5876
+ this.onPullPage(
5877
+ ns,
5878
+ publishedCount,
5879
+ response.has_more === true,
5880
+ messages.length > 0 ? Number(messages[messages.length - 1].seq ?? 0) : void 0,
5881
+ remaining,
5882
+ messages.length,
5883
+ client._seqTracker.getContiguousSeq(ns),
5884
+ messages.length > 0 ? Number(messages[messages.length - 1].seq ?? 0) : void 0
5885
+ );
5611
5886
  return { rawCount: messages.length, publishedCount };
5612
5887
  }
5613
5888
  enqueueOrderedMessage(ns, event, seq2, payload, source = "push") {
@@ -5629,11 +5904,11 @@ var MessageDeliveryEngine = class {
5629
5904
  async publishOrderedQueueItem(ns, event, seq2, payload, pullResponse = false, source = "push", batch) {
5630
5905
  const client = this.runtime.client;
5631
5906
  if (event === "group.changed" && this.isGroupEventNamespace(ns)) {
5632
- await this.publishOrderedGroupChanged(payload);
5907
+ await this.publishOrderedGroupChanged(payload, source);
5633
5908
  return;
5634
5909
  }
5635
5910
  if (event === "message.recalled") {
5636
- await this.publishMessageRecallTombstone(seq2, payload);
5911
+ await this.publishMessageRecallTombstone(seq2, payload, source);
5637
5912
  return;
5638
5913
  }
5639
5914
  if (pullResponse) {
@@ -5642,7 +5917,7 @@ var MessageDeliveryEngine = class {
5642
5917
  }
5643
5918
  await client._publishAppEvent(event, payload, source, ns, batch);
5644
5919
  }
5645
- async publishOrderedGroupChanged(payload) {
5920
+ async publishOrderedGroupChanged(payload, source = "ordered") {
5646
5921
  const client = this.runtime.client;
5647
5922
  if (isJsonObject(payload)) {
5648
5923
  const eventPayload = payload;
@@ -5653,7 +5928,7 @@ var MessageDeliveryEngine = class {
5653
5928
  if (groupId) client._cleanupDissolvedGroup?.(groupId);
5654
5929
  }
5655
5930
  }
5656
- await client._publishAppEvent("group.changed", payload);
5931
+ await client._publishAppEvent("group.changed", payload, source);
5657
5932
  }
5658
5933
  isInstanceScopedMessageEvent(event) {
5659
5934
  return event === "message.received" || event === "message.recalled" || event === "message.undecryptable" || event === "group.message_created" || event === "group.message_recalled" || event === "group.message_undecryptable";
@@ -5670,17 +5945,22 @@ var MessageDeliveryEngine = class {
5670
5945
  }
5671
5946
  return result;
5672
5947
  }
5673
- normalizePublishedMessagePayload(event, payload) {
5948
+ normalizePublishedMessagePayload(event, payload, source = "direct") {
5949
+ let normalized;
5674
5950
  if (this.isInstanceScopedMessageEvent(event)) {
5675
5951
  payload = this.stripInlineInternalFields(payload);
5676
5952
  if (event === "group.message_created") payload = normalizeGroupMentionMode(payload);
5677
- return this.attachAppMessageEnvelope(this.stripInternalSenderDeviceFields(this.attachCurrentInstanceContext(payload)));
5678
- }
5679
- if (this.isGroupScopedEvent(event)) {
5953
+ normalized = this.attachAppMessageEnvelope(this.stripInternalSenderDeviceFields(this.attachCurrentInstanceContext(payload)));
5954
+ } else if (this.isGroupScopedEvent(event)) {
5680
5955
  payload = this.stripInlineInternalFields(payload);
5681
- return this.attachAppGroupEventEnvelope(this.attachCurrentInstanceContext(payload));
5956
+ normalized = this.attachAppGroupEventEnvelope(this.attachCurrentInstanceContext(payload));
5957
+ } else {
5958
+ normalized = payload;
5682
5959
  }
5683
- return payload;
5960
+ const canonicalSource = canonicalDeliverySource(source);
5961
+ if (canonicalSource === "direct" || !isJsonObject(normalized)) return normalized;
5962
+ if (!this.isInstanceScopedMessageEvent(event) && !this.isGroupScopedEvent(event)) return normalized;
5963
+ return { ...normalized, source: canonicalSource };
5684
5964
  }
5685
5965
  stripInlineInternalFields(payload) {
5686
5966
  if (!isJsonObject(payload)) return payload;
@@ -5896,7 +6176,7 @@ var MessageDeliveryEngine = class {
5896
6176
  if (tombstoneId) return `p2p|tombstone:${tombstoneId}`;
5897
6177
  return `p2p|unknown:${Date.now()}:${Math.random()}`;
5898
6178
  }
5899
- async publishMessageRecallTombstone(seq2, message) {
6179
+ async publishMessageRecallTombstone(seq2, message, source = "push") {
5900
6180
  const client = this.runtime.client;
5901
6181
  const eventPayload = this.recallEventFromMessage(message);
5902
6182
  if (!eventPayload) return false;
@@ -5915,7 +6195,7 @@ var MessageDeliveryEngine = class {
5915
6195
  const drop = [...seen.entries()].sort((a, b) => a[1] - b[1]).slice(0, seen.size - MESSAGE_RECALL_SEEN_LIMIT);
5916
6196
  for (const [oldKey] of drop) seen.delete(oldKey);
5917
6197
  }
5918
- await client._publishAppEvent("message.recalled", eventPayload);
6198
+ await client._publishAppEvent("message.recalled", eventPayload, source);
5919
6199
  client._clientLog.debug(`message.recalled published: seq=${String(seq2)} ids=${JSON.stringify(eventPayload.message_ids)}`);
5920
6200
  return true;
5921
6201
  }
@@ -5977,7 +6257,7 @@ var MessageDeliveryEngine = class {
5977
6257
  if (tombstoneId) return `${normalizedGroupId}|tombstone:${tombstoneId}`;
5978
6258
  return `${normalizedGroupId}|unknown:${Date.now()}:${Math.random()}`;
5979
6259
  }
5980
- async publishGroupRecallTombstone(groupId, seq2, message) {
6260
+ async publishGroupRecallTombstone(groupId, seq2, message, source = "push") {
5981
6261
  const client = this.runtime.client;
5982
6262
  const eventPayload = this.recallEventFromGroupMessage(message);
5983
6263
  if (!eventPayload) return false;
@@ -5998,7 +6278,7 @@ var MessageDeliveryEngine = class {
5998
6278
  const drop = [...seen.entries()].sort((a, b) => a[1] - b[1]).slice(0, seen.size - GROUP_RECALL_SEEN_LIMIT);
5999
6279
  for (const [oldKey] of drop) seen.delete(oldKey);
6000
6280
  }
6001
- await client._publishAppEvent("group.message_recalled", eventPayload);
6281
+ await client._publishAppEvent("group.message_recalled", eventPayload, source);
6002
6282
  client._clientLog.debug(`group.message_recalled published: group=${groupId} seq=${String(seq2)} ids=${JSON.stringify(eventPayload.message_ids)}`);
6003
6283
  return true;
6004
6284
  }
@@ -6028,40 +6308,11 @@ var MessageDeliveryEngine = class {
6028
6308
  return;
6029
6309
  }
6030
6310
  const ns = `group:${groupId}`;
6031
- if (seqNum > 0) {
6032
- client._seqTracker.updateMaxSeen(ns, seqNum);
6033
- if (client._seqTracker.getContiguousSeq(ns) === seqNum) {
6034
- await this.publishGroupRecallTombstone(groupId, seq2, wrapped);
6035
- return;
6036
- }
6037
- client._repairPushContiguousBound(ns, seqNum, true, "_raw.group.message_recalled");
6038
- }
6039
- const pushed = client._pushedSeqs.get(ns);
6040
- const pending = client._pendingOrderedMsgs.get(ns);
6041
- if (pushed?.has(seqNum) || pending?.has(seqNum)) {
6042
- await this.publishGroupRecallTombstone(groupId, seq2, wrapped);
6043
- return;
6044
- }
6045
- const contigBefore = client._seqTracker.getContiguousSeq(ns);
6046
- 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));
6047
6313
  await this.publishGroupRecallTombstone(groupId, seq2, wrapped);
6048
- this.markPublishedSeq(ns, seqNum);
6049
- const contig = client._seqTracker.getContiguousSeq(ns);
6050
- if (contig > 0) {
6051
- const ackSeq = this.clampAckSeq("group.ack_messages", "msg_seq", ns, contig);
6052
- client._transport.call("group.ack_messages", {
6053
- group_id: groupId,
6054
- msg_seq: ackSeq,
6055
- device_id: client._deviceId,
6056
- slot_id: client._slotId,
6057
- _rpc_background: true
6058
- }).catch((e) => {
6059
- client._clientLog.warn("group recall auto-ack failed: group=" + groupId, e);
6060
- });
6061
- }
6062
- if (contig !== contigBefore) this.persistSeq(ns);
6063
6314
  }
6064
- async publishAppEvent(event, payload, source = "", ns = "", batch) {
6315
+ async publishAppEvent(event, payload, source = "direct", ns = "", batch) {
6065
6316
  const client = this.runtime.client;
6066
6317
  const generation = this.deliveryGeneration;
6067
6318
  if ((event === "message.received" || event === "group.message_created") && isJsonObject(payload)) {
@@ -6082,11 +6333,16 @@ var MessageDeliveryEngine = class {
6082
6333
  client._clientLog.debug(`agent_md etag inject skipped: ${String(exc)}`);
6083
6334
  }
6084
6335
  }
6085
- client._dispatcher.enqueue(event, this.normalizePublishedMessagePayload(event, payload));
6336
+ const canonicalSource = canonicalDeliverySource(source);
6337
+ let normalized = this.normalizePublishedMessagePayload(event, payload, source);
6338
+ if (isJsonObject(normalized) && (this.isInstanceScopedMessageEvent(event) || this.isGroupScopedEvent(event)) && canonicalSource !== "direct") {
6339
+ normalized = { ...normalized, source: canonicalSource };
6340
+ }
6341
+ client._dispatcher.enqueue(event, normalized);
6086
6342
  if (generation !== this.deliveryGeneration) return;
6087
- if (source !== "pull" && source !== "tail" && source !== "pending_retry" && source !== "push" && source !== "inline_push") return;
6088
- if (source === "push" || source === "inline_push") {
6089
- const localBatch = batch ?? this.createDeliveryChangeBatch(source);
6343
+ if (canonicalSource !== "pull" && canonicalSource !== "pending_retry" && canonicalSource !== "push" && canonicalSource !== "inline_push") return;
6344
+ if (canonicalSource === "push" || canonicalSource === "inline_push") {
6345
+ const localBatch = batch ?? this.createDeliveryChangeBatch(canonicalSource);
6090
6346
  this.recordDeliveryChange(localBatch.changes, event, payload, ns, generation);
6091
6347
  if (!batch) await this.flushRealtimeDeliveryChanges(localBatch);
6092
6348
  } else {
@@ -6119,6 +6375,16 @@ var MessageDeliveryEngine = class {
6119
6375
  }
6120
6376
  return true;
6121
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
+ }
6122
6388
  strictTargetString(source, key) {
6123
6389
  if (!Object.prototype.hasOwnProperty.call(source, key)) {
6124
6390
  return { present: false, valid: false, value: "" };
@@ -6179,11 +6445,11 @@ var MessageDeliveryEngine = class {
6179
6445
  const client = this.runtime.client;
6180
6446
  client._clientLog.debug(`_onRawMessageReceived enter: from=${data?.from ?? "-"} mid=${data?.message_id ?? "-"} seq=${data?.seq ?? "-"}`);
6181
6447
  const ns = isJsonObject(data) && client._aid && data.seq !== void 0 ? `p2p:${client._aid}` : "";
6182
- const seq2 = isJsonObject(data) && typeof data.seq === "number" ? data.seq : 0;
6183
- 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)) {
6184
6450
  client._seqTracker.updateMaxSeen(ns, seq2);
6185
6451
  }
6186
- const hasInlineObject = isJsonObject(data) && isJsonObject(data.inline_message);
6452
+ const hasInlineObject = isJsonObject(data) && hasNonEmptyInlineMessage(data.inline_message);
6187
6453
  const inlineGeneration = hasInlineObject ? this.captureInlineGeneration() : void 0;
6188
6454
  const operation = () => this.processAndPublishMessage(data, inlineGeneration);
6189
6455
  client._safeAsync(client._v2Session && ns && !hasInlineObject ? operation() : this.runPushProcessSerialized(ns, operation, inlineGeneration));
@@ -6195,33 +6461,13 @@ var MessageDeliveryEngine = class {
6195
6461
  const msg = { ...data };
6196
6462
  if (!("type" in msg)) msg.type = "message.recalled";
6197
6463
  if (!this.messageTargetsCurrentInstance(msg)) return;
6198
- const seq2 = msg.seq;
6199
- if (seq2 !== void 0 && seq2 !== null && client._aid) {
6464
+ const seq2 = positiveSafeSequenceHint(msg.seq);
6465
+ if (client._aid && seq2 > 0) {
6200
6466
  const ns = `p2p:${client._aid}`;
6201
- if (seq2 > 0) client._seqTracker.updateMaxSeen(ns, seq2);
6202
- const contigBefore = client._seqTracker.getContiguousSeq(ns);
6203
- const seqNeedsPull = client._seqTracker.onMessageSeq(ns, seq2);
6204
- const published = await this.publishOrderedMessage("message.recalled", ns, seq2, msg);
6205
- const contigAfter = client._seqTracker.getContiguousSeq(ns);
6206
- if (seqNeedsPull && !published && client._sessionOptions?.background_sync !== false) {
6207
- client._safeAsync(this.fillP2pGap());
6208
- }
6209
- const contig = client._seqTracker.getContiguousSeq(ns);
6210
- if (contig > 0) {
6211
- const ackSeq = this.clampAckSeq("message.ack", "seq", ns, contig);
6212
- client._transport.call("message.ack", {
6213
- seq: ackSeq,
6214
- device_id: client._deviceId,
6215
- slot_id: client._slotId,
6216
- _rpc_background: true
6217
- }).catch((e) => {
6218
- client._clientLog.warn(`P2P recall auto-ack failed:${String(e)}`);
6219
- });
6220
- }
6221
- if (contigAfter !== contigBefore) this.persistSeq(ns);
6222
- return;
6467
+ client._seqTracker.updateMaxSeen(ns, seq2);
6468
+ if (seq2 > client._seqTracker.getContiguousSeq(ns) && client._sessionOptions?.background_sync !== false) client._safeAsync(this.fillP2pGap());
6223
6469
  }
6224
- await this.publishMessageRecallTombstone(seq2, msg);
6470
+ await this.publishMessageRecallTombstone(msg.seq, msg);
6225
6471
  }
6226
6472
  async processAndPublishMessage(data, inlineGeneration) {
6227
6473
  const client = this.runtime.client;
@@ -6235,43 +6481,32 @@ var MessageDeliveryEngine = class {
6235
6481
  return;
6236
6482
  }
6237
6483
  const legacyPushSeq = positiveSafeSequenceHint(msg.seq);
6238
- const hasInlineField = Object.prototype.hasOwnProperty.call(msg, "inline_message");
6484
+ const hasInlineField = hasNonEmptyInlineMessage(msg.inline_message);
6239
6485
  if (client._v2Session && client._aid && (legacyPushSeq > 0 || hasInlineField)) {
6240
6486
  await this.processV2P2PNotification(msg, "v1", inlineGeneration);
6241
6487
  return;
6242
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
+ }
6243
6495
  const seq2 = msg.seq;
6244
6496
  const encryptedPush = client._isEncryptedPushMessage(msg);
6245
- if (seq2 !== void 0 && seq2 !== null && client._aid) {
6246
- const ns = `p2p:${client._aid}`;
6247
- if (seq2 > 0) client._seqTracker.updateMaxSeen(ns, seq2);
6248
- const contigBefore = client._seqTracker.getContiguousSeq(ns);
6249
- const seqNeedsPull = client._seqTracker.onMessageSeq(ns, seq2);
6250
- const published = encryptedPush ? await client._publishEncryptedPushMessage("message.received", "message.undecryptable", ns, seq2, msg, false) : await this.publishOrderedMessage("message.received", ns, seq2, msg);
6251
- const contigAfter = client._seqTracker.getContiguousSeq(ns);
6252
- const needPull = seqNeedsPull && !published;
6253
- if (needPull && client._sessionOptions?.background_sync !== false) {
6254
- client._safeAsync(this.fillP2pGap());
6255
- }
6256
- const contig = client._seqTracker.getContiguousSeq(ns);
6257
- if (contig > 0) {
6258
- const ackSeq = this.clampAckSeq("message.ack", "seq", ns, contig);
6259
- client._transport.call("message.ack", {
6260
- seq: ackSeq,
6261
- device_id: client._deviceId,
6262
- slot_id: client._slotId,
6263
- _rpc_background: true
6264
- }).catch((e) => {
6265
- client._clientLog.warn(`P2P auto-ack failed:${String(e)}`);
6266
- });
6267
- }
6268
- if (contigAfter !== contigBefore) this.persistSeq(ns);
6269
- 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);
6270
6509
  } else {
6271
- if (encryptedPush) {
6272
- await client._publishEncryptedPushMessage("message.received", "message.undecryptable", "", seq2 ?? 0, msg, false);
6273
- return;
6274
- }
6275
6510
  await client._publishAppEvent("message.received", msg, "push");
6276
6511
  }
6277
6512
  } catch (exc) {
@@ -6287,7 +6522,7 @@ var MessageDeliveryEngine = class {
6287
6522
  _decrypt_error: String(exc)
6288
6523
  };
6289
6524
  client._attachV2EnvelopeMetadataFromSource(safeEvent, data);
6290
- await client._publishAppEvent("message.undecryptable", safeEvent);
6525
+ await client._publishAppEvent("message.undecryptable", safeEvent, "push");
6291
6526
  }
6292
6527
  }
6293
6528
  }
@@ -6295,13 +6530,13 @@ var MessageDeliveryEngine = class {
6295
6530
  const client = this.runtime.client;
6296
6531
  client._clientLog.debug(`_onRawGroupMessageCreated enter: group_id=${data?.group_id ?? "-"} from=${data?.from ?? "-"} seq=${data?.seq ?? "-"}`);
6297
6532
  const groupId = isJsonObject(data) && typeof data.group_id === "string" ? data.group_id.trim() : "";
6298
- const seq2 = isJsonObject(data) && typeof data.seq === "number" ? data.seq : 0;
6299
6533
  const kind = isJsonObject(data) ? String(data.kind ?? "").trim() : "";
6300
- if (client._v2Session && groupId && kind !== "group.online_unread_hint" && Number.isSafeInteger(seq2) && seq2 > 0) {
6301
- client._seqTracker.updateMaxSeen(`group:${groupId}`, seq2);
6302
- }
6303
6534
  const ns = groupId ? `group:${groupId}` : "";
6304
- 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);
6305
6540
  const inlineGeneration = hasInlineObject ? this.captureInlineGeneration() : void 0;
6306
6541
  const operation = () => this.processAndPublishGroupMessage(data, inlineGeneration);
6307
6542
  client._safeAsync(client._v2Session && ns && !hasInlineObject ? operation() : this.runPushProcessSerialized(ns, operation, inlineGeneration));
@@ -6322,69 +6557,56 @@ var MessageDeliveryEngine = class {
6322
6557
  client._groupSynced.add(groupId);
6323
6558
  }
6324
6559
  const legacyPushSeq = positiveSafeSequenceHint(seq2);
6325
- const hasInlineField = Object.prototype.hasOwnProperty.call(msg, "inline_message");
6560
+ const hasInlineField = hasNonEmptyInlineMessage(msg.inline_message);
6326
6561
  if (client._v2Session && groupId && (legacyPushSeq > 0 || hasInlineField)) {
6327
6562
  await this.processGroupV2MessageCreated(msg, "v1", inlineGeneration);
6328
6563
  return;
6329
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
+ }
6330
6571
  if (payload === void 0 || payload === null || typeof payload === "object" && Object.keys(payload).length === 0) {
6331
- 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");
6332
6582
  return;
6333
6583
  }
6334
6584
  const encryptedPush = client._isEncryptedPushMessage(msg);
6335
- if (groupId && seq2 !== void 0 && seq2 !== null) {
6336
- const ns = `group:${groupId}`;
6337
- if (seq2 > 0) client._seqTracker.updateMaxSeen(ns, seq2);
6338
- const contigBefore = client._seqTracker.getContiguousSeq(ns);
6339
- const seqNeedsPull = client._seqTracker.onMessageSeq(ns, seq2);
6340
- if (!encryptedPush && this.recallEventFromGroupMessage(msg)) {
6341
- await this.publishGroupRecallTombstone(groupId, seq2, msg);
6342
- this.markPublishedSeq(ns, Number(seq2));
6343
- const contigAfter2 = client._seqTracker.getContiguousSeq(ns);
6344
- const contig2 = client._seqTracker.getContiguousSeq(ns);
6345
- if (contig2 > 0) {
6346
- const ackSeq = this.clampAckSeq("group.ack_messages", "msg_seq", ns, contig2);
6347
- client._transport.call("group.ack_messages", {
6348
- group_id: groupId,
6349
- msg_seq: ackSeq,
6350
- device_id: client._deviceId,
6351
- slot_id: client._slotId,
6352
- _rpc_background: true
6353
- }).catch((e) => {
6354
- client._clientLog.warn("group recall auto-ack failed: group=" + groupId, e);
6355
- });
6356
- }
6357
- if (contigAfter2 !== contigBefore) this.persistSeq(ns);
6358
- return;
6359
- }
6360
- 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);
6361
- const contigAfter = client._seqTracker.getContiguousSeq(ns);
6362
- const needPull = seqNeedsPull && !published;
6363
- if (needPull && client._sessionOptions?.background_sync !== false) {
6364
- client._safeAsync(this.fillGroupGap(groupId));
6365
- }
6366
- const contig = client._seqTracker.getContiguousSeq(ns);
6367
- if (contig > 0) {
6368
- const ackSeq = this.clampAckSeq("group.ack_messages", "msg_seq", ns, contig);
6369
- client._transport.call("group.ack_messages", {
6370
- group_id: groupId,
6371
- msg_seq: ackSeq,
6372
- device_id: client._deviceId,
6373
- slot_id: client._slotId,
6374
- _rpc_background: true
6375
- }).catch((e) => {
6376
- client._clientLog.warn("group message auto-ack failed: group=" + groupId, e);
6377
- });
6378
- }
6379
- if (contigAfter !== contigBefore) this.persistSeq(ns);
6380
- if (encryptedPush) return;
6381
- } else {
6382
- if (encryptedPush) {
6383
- await client._publishEncryptedPushMessage("group.message_created", "group.message_undecryptable", "", seq2 ?? 0, msg, true);
6384
- return;
6385
- }
6386
- 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;
6387
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");
6388
6610
  } catch (exc) {
6389
6611
  client._clientLog.warn(`group push processing failed:${String(exc)}`);
6390
6612
  if (isJsonObject(data)) {
@@ -6398,7 +6620,7 @@ var MessageDeliveryEngine = class {
6398
6620
  _decrypt_error: String(exc)
6399
6621
  };
6400
6622
  client._attachV2EnvelopeMetadataFromSource(safeEvent, data);
6401
- await client._publishAppEvent("group.message_undecryptable", safeEvent);
6623
+ await client._publishAppEvent("group.message_undecryptable", safeEvent, "push");
6402
6624
  }
6403
6625
  }
6404
6626
  }
@@ -6406,7 +6628,7 @@ var MessageDeliveryEngine = class {
6406
6628
  const client = this.runtime.client;
6407
6629
  const groupId = notification.group_id ?? "";
6408
6630
  if (!groupId) {
6409
- await client._publishAppEvent("group.message_created", notification);
6631
+ await client._publishAppEvent("group.message_created", notification, "push");
6410
6632
  return;
6411
6633
  }
6412
6634
  if (client._sessionOptions?.background_sync === false) {
@@ -6426,7 +6648,7 @@ var MessageDeliveryEngine = class {
6426
6648
  return;
6427
6649
  } catch (exc) {
6428
6650
  client._clientLog.warn(`auto pull group message failed:${String(exc)}`);
6429
- await client._publishAppEvent("group.message_created", notification);
6651
+ await client._publishAppEvent("group.message_created", notification, "push");
6430
6652
  return;
6431
6653
  }
6432
6654
  }
@@ -6453,7 +6675,7 @@ var MessageDeliveryEngine = class {
6453
6675
  } catch (exc) {
6454
6676
  client._clientLog.warn(`auto pull group message commit/ack failed:${String(exc)}`);
6455
6677
  if (!rawCompleted) {
6456
- await client._publishAppEvent("group.message_created", notification);
6678
+ await client._publishAppEvent("group.message_created", notification, "push");
6457
6679
  }
6458
6680
  }
6459
6681
  }
@@ -6581,6 +6803,8 @@ var MessageDeliveryEngine = class {
6581
6803
  client._gapFillDone.add(dedupKey);
6582
6804
  this.runtime.delivery.setGapFillActive(true);
6583
6805
  let continuationAfterSeq = 0;
6806
+ let pendingAckSeq = 0;
6807
+ let failed = false;
6584
6808
  try {
6585
6809
  let nextAfterSeq = afterSeq;
6586
6810
  const maxPages = singlePage ? 1 : 100;
@@ -6600,9 +6824,9 @@ var MessageDeliveryEngine = class {
6600
6824
  if (!Array.isArray(events)) return;
6601
6825
  const pageContigBefore = client._seqTracker.getContiguousSeq(ns);
6602
6826
  const eventObjects = events.filter((evt) => isJsonObject(evt));
6603
- if (eventObjects.length > 0) {
6604
- client._seqTracker.onPullResult(ns, eventObjects, nextAfterSeq);
6605
- }
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);
6606
6830
  const cursor = isJsonObject(result.cursor) ? result.cursor : null;
6607
6831
  const retentionFloor = Math.max(
6608
6832
  0,
@@ -6628,6 +6852,7 @@ var MessageDeliveryEngine = class {
6628
6852
  }
6629
6853
  const eventSeqs = [];
6630
6854
  let hasDissolvedEvent = false;
6855
+ let publishedEventCount = 0;
6631
6856
  for (const evt of eventObjects) {
6632
6857
  const eventSeq = Number(evt.event_seq ?? 0);
6633
6858
  if (Number.isFinite(eventSeq) && eventSeq > 0) eventSeqs.push(eventSeq);
@@ -6645,7 +6870,8 @@ var MessageDeliveryEngine = class {
6645
6870
  }
6646
6871
  }
6647
6872
  if (Number.isFinite(eventSeq) && eventSeq > 0 && !client._pushedSeqs.get(ns)?.has(eventSeq)) {
6648
- this.enqueueOrderedMessage(ns, "group.changed", eventSeq, evt);
6873
+ this.enqueueOrderedMessage(ns, "group.changed", eventSeq, evt, "pull");
6874
+ publishedEventCount += 1;
6649
6875
  }
6650
6876
  }
6651
6877
  const ackContig = client._seqTracker.getContiguousSeq(ns);
@@ -6654,37 +6880,49 @@ var MessageDeliveryEngine = class {
6654
6880
  this.persistSeq(ns);
6655
6881
  }
6656
6882
  if (eventObjects.length > 0 && ackContig > 0 && ackContig !== pageContigBefore) {
6657
- try {
6658
- const ackPromise = client._transport.call("group.ack_events", {
6659
- group_id: groupId,
6660
- event_seq: ackContig,
6661
- device_id: client._deviceId,
6662
- slot_id: client._slotId,
6663
- _rpc_background: true
6664
- });
6665
- if (singlePage) await ackPromise;
6666
- else ackPromise.catch((e) => {
6667
- client._clientLog.warn("group event auto-ack failed: group=" + groupId, e);
6668
- });
6669
- } catch (e) {
6670
- client._clientLog.warn("group event auto-ack failed: group=" + groupId, e);
6671
- }
6883
+ pendingAckSeq = Math.max(pendingAckSeq, ackContig);
6672
6884
  }
6885
+ this.onPullPage(
6886
+ ns,
6887
+ publishedEventCount,
6888
+ result.has_more === true,
6889
+ eventObjects.length > 0 ? Number(eventObjects[eventObjects.length - 1].event_seq ?? 0) : void 0,
6890
+ typeof result.remaining === "number" && Number.isSafeInteger(result.remaining) && result.remaining >= 0 ? result.remaining : null,
6891
+ eventObjects.length,
6892
+ ackContig,
6893
+ eventObjects.length > 0 ? Number(eventObjects[eventObjects.length - 1].event_seq ?? 0) : void 0
6894
+ );
6673
6895
  const nextAfter = Math.max(eventSeqs.length > 0 ? Math.max(...eventSeqs) : nextAfterSeq, nextAfterSeq);
6674
- 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) {
6675
6898
  continuationAfterSeq = nextAfter;
6676
6899
  }
6677
- if (eventObjects.length === 0 || nextAfter <= nextAfterSeq || result.has_more !== true) break;
6900
+ if (eventObjects.length === 0 || nextAfter <= nextAfterSeq || !needsMore) break;
6678
6901
  nextAfterSeq = nextAfter;
6679
6902
  }
6680
6903
  if (pageCount >= maxPages) {
6681
6904
  client._clientLog.warn(`group event gap fill reached max_pages=${maxPages} group=${groupId} after_seq=${nextAfterSeq}`);
6682
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
+ }
6683
6919
  } catch (exc) {
6920
+ failed = true;
6684
6921
  client._clientLog.warn(`group event gap-fill failed:${String(exc)}`);
6685
6922
  } finally {
6686
6923
  client._gapFillDone.delete(dedupKey);
6687
6924
  this.runtime.delivery.setGapFillActive(false);
6925
+ if (!client._rpcPipeline) this.syncStopped(failed ? "aborted" : "pull_drained");
6688
6926
  if (singlePage && continuationAfterSeq > afterSeq) {
6689
6927
  this.enqueueOnlineUnreadEventHint({
6690
6928
  group_id: groupId,
@@ -6713,41 +6951,15 @@ var MessageDeliveryEngine = class {
6713
6951
  this.enqueueOnlineUnreadEventHint(data);
6714
6952
  return;
6715
6953
  }
6716
- if (this.isSelfJoinGroupChanged(data)) {
6717
- const contig = client._seqTracker.getContiguousSeq(ns);
6718
- const maxSeen = client._seqTracker.getMaxSeenSeq(ns);
6719
- if (contig === 0 && maxSeen === 0 && eventSeq > 1) {
6720
- client._clientLog.debug(`group.changed self-join baseline: group=${groupId}, event_seq=${eventSeq}, baseline=${eventSeq - 1}`);
6721
- client._seqTracker.forceContiguousSeq(ns, eventSeq - 1);
6722
- }
6723
- }
6724
6954
  const contigBefore = client._seqTracker.getContiguousSeq(ns);
6955
+ client._seqTracker.updateMaxSeen(ns, eventSeq);
6725
6956
  const publishedDuplicate = client._pushedSeqs.get(ns)?.has(eventSeq) === true;
6726
6957
  if (eventSeq <= contigBefore || publishedDuplicate) {
6727
6958
  client._clientLog.debug(`group.changed skipped duplicate/stale: group=${groupId}, event_seq=${eventSeq}, contiguous=${contigBefore}`);
6728
- if (eventSeq <= contigBefore) {
6729
- this.fireGroupEventAck(groupId, ns, eventSeq, "group event covered push ack");
6730
- } else if (contigBefore > 0) {
6731
- this.fireGroupEventAck(groupId, ns, contigBefore, "group event covered push ack");
6732
- }
6733
6959
  return;
6734
6960
  }
6735
6961
  this.enqueueOrderedMessage(ns, "group.changed", eventSeq, data);
6736
- needPull = client._seqTracker.onMessageSeq(ns, eventSeq);
6737
- const ackContig = client._seqTracker.getContiguousSeq(ns);
6738
- await this.drainOrderedMessages(ns);
6739
- if (ackContig > 0 && ackContig !== contigBefore) {
6740
- if (data.action !== "dissolved") this.persistSeq(ns);
6741
- client._transport.call("group.ack_events", {
6742
- group_id: groupId,
6743
- event_seq: ackContig,
6744
- device_id: client._deviceId,
6745
- slot_id: client._slotId,
6746
- _rpc_background: true
6747
- }).catch((e) => {
6748
- client._clientLog.warn("group event push auto-ack failed: group=" + groupId, e);
6749
- });
6750
- }
6962
+ needPull = eventSeq > contigBefore;
6751
6963
  if (needPull && groupId && !data._from_gap_fill && client._sessionOptions?.background_sync !== false) {
6752
6964
  client._safeAsync(this.fillGroupEventGap(groupId));
6753
6965
  }
@@ -7180,16 +7392,18 @@ var MessageDeliveryEngine = class {
7180
7392
  };
7181
7393
  if (forceTail || order[0] === "tail") result = await run(true, false, void 0, false, forceTail);
7182
7394
  const state = this.syncState(ns);
7183
- const tailMissedDuringPull = tailCompleted && Number(client._seqTracker.getMaxSeenSeq(ns) || 0) > state.head;
7184
7395
  const maxSeen = Number(client._seqTracker.getMaxSeenSeq(ns) || 0);
7185
- const pendingThroughSeq = tailMissedDuringPull ? maxSeen : 0;
7186
- 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);
7187
7401
  const backgroundWindowForward = order[0] === "forward" && !headForward;
7188
7402
  if (headForward || tailForward || client._sessionOptions?.background_sync !== false && backgroundWindowForward) {
7189
7403
  const forwardTarget = Math.max(
7190
7404
  headForward ? Math.max(pushSeq, maxSeen) : 0,
7191
7405
  tailForward || backgroundWindowForward ? state.tail - 1 : 0,
7192
- tailMissedDuringPull ? maxSeen : 0
7406
+ tailMissedDuringPull ? observedUpper : 0
7193
7407
  );
7194
7408
  const forwardMaxPages = this.forwardMaxPages(state.ack, forwardTarget, pageLimit);
7195
7409
  result = await run(
@@ -7248,16 +7462,18 @@ var MessageDeliveryEngine = class {
7248
7462
  };
7249
7463
  if (forceTail || order[0] === "tail") result = await run(true, false, void 0, false, forceTail);
7250
7464
  const state = this.syncState(ns);
7251
- const tailMissedDuringPull = tailCompleted && Number(client._seqTracker.getMaxSeenSeq(ns) || 0) > state.head;
7252
7465
  const maxSeen = Number(client._seqTracker.getMaxSeenSeq(ns) || 0);
7253
- const pendingThroughSeq = tailMissedDuringPull ? maxSeen : 0;
7254
- 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);
7255
7471
  const backgroundWindowForward = order[0] === "forward" && !headForward;
7256
7472
  if (headForward || tailForward || client._sessionOptions?.background_sync !== false && backgroundWindowForward) {
7257
7473
  const forwardTarget = Math.max(
7258
7474
  headForward ? Math.max(pushSeq, maxSeen) : 0,
7259
7475
  tailForward || backgroundWindowForward ? state.tail - 1 : 0,
7260
- tailMissedDuringPull ? maxSeen : 0
7476
+ tailMissedDuringPull ? observedUpper : 0
7261
7477
  );
7262
7478
  const forwardMaxPages = this.forwardMaxPages(state.ack, forwardTarget, pageLimit);
7263
7479
  result = await run(
@@ -7309,13 +7525,13 @@ var MessageDeliveryEngine = class {
7309
7525
  if (!row) return;
7310
7526
  const groupId = typeof row.group_id === "string" ? row.group_id.trim() : "";
7311
7527
  if (!groupId) return;
7312
- const seq2 = typeof row.seq === "number" ? row.seq : 0;
7313
7528
  const eventKind = String(row.kind ?? "").trim();
7314
7529
  const ns = this.resolvePendingGroupInlineAckNamespace(`group:${groupId}`);
7315
- 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) {
7316
7532
  client._seqTracker.updateMaxSeen(ns, seq2);
7317
7533
  }
7318
- const hasInlineObject = isJsonObject(row.inline_message) && eventKind !== "group.online_unread_hint";
7534
+ const hasInlineObject = hasNonEmptyInlineMessage(row.inline_message) && eventKind !== "group.online_unread_hint";
7319
7535
  const inlineGeneration = hasInlineObject ? this.captureInlineGeneration() : void 0;
7320
7536
  const operation = () => this.processGroupV2MessageCreated(data, "v2", inlineGeneration);
7321
7537
  if (hasInlineObject) await this.runPushProcessSerialized(ns, operation, inlineGeneration);
@@ -7327,7 +7543,6 @@ var MessageDeliveryEngine = class {
7327
7543
  if (!isJsonObject(data) || !client._v2Session) return;
7328
7544
  const groupId = typeof data.group_id === "string" ? data.group_id.trim() : "";
7329
7545
  const seq2 = positiveSafeSequenceHint(data.seq);
7330
- const strictSeq = typeof data.seq === "number" && Number.isSafeInteger(data.seq) && data.seq > 0;
7331
7546
  if (!groupId) return;
7332
7547
  const eventKind = String(data.kind ?? "").trim();
7333
7548
  if (eventKind === "group.online_unread_hint") {
@@ -7336,11 +7551,12 @@ var MessageDeliveryEngine = class {
7336
7551
  return;
7337
7552
  }
7338
7553
  const ns = this.resolvePendingGroupInlineAckNamespace(`group:${groupId}`);
7339
- const inlinePresent = Object.prototype.hasOwnProperty.call(data, "inline_message");
7554
+ const inlinePresent = hasNonEmptyInlineMessage(data.inline_message);
7340
7555
  if (seq2 <= 0) {
7341
7556
  if (inlinePresent) await this.recoverInvalidGroupRealtimeHead(groupId, ns, data.seq);
7342
7557
  return;
7343
7558
  }
7559
+ client._seqTracker.updateMaxSeen(ns, seq2);
7344
7560
  const inline = this.inlineMessage(data);
7345
7561
  const inlineMatches = inline !== null && this.inlineGroupBindingMatches(data, inline, groupId, seq2, expectedInlineVersion);
7346
7562
  const pendingInline = this.pendingGroupInlineAcks?.get(ns);
@@ -7361,13 +7577,12 @@ var MessageDeliveryEngine = class {
7361
7577
  }
7362
7578
  if (inlineMatches && !this.isInlineGenerationCurrent(inlineGeneration)) return;
7363
7579
  const before = this.syncState(ns);
7364
- if (strictSeq || !inlinePresent) client._seqTracker.updateMaxSeen(ns, seq2);
7365
7580
  if (seq2 <= before.ack && before.ack >= before.tail - 1) {
7366
7581
  const canonicalNs = this.resolvePendingGroupInlineAckNamespace(ns);
7367
7582
  const pending = this.pendingGroupInlineAcks?.get(canonicalNs);
7368
- if (pending && seq2 >= pending.seq) {
7583
+ if (inlinePresent && pending && seq2 >= pending.seq) {
7369
7584
  this.retryPendingGroupInlineAck(canonicalNs, pending.seq, "covered inline Group ack", seq2);
7370
- } else if (!inlinePresent || inlineMatches) {
7585
+ } else if (inlineMatches) {
7371
7586
  this.fireGroupV2Ack(
7372
7587
  groupId,
7373
7588
  ns,
@@ -7406,7 +7621,7 @@ var MessageDeliveryEngine = class {
7406
7621
  if (client._v2Session && Number.isSafeInteger(pushSeq) && pushSeq > 0) {
7407
7622
  client._seqTracker.updateMaxSeen(ns, pushSeq);
7408
7623
  }
7409
- const hasInlineObject = isJsonObject(data) && isJsonObject(data.inline_message);
7624
+ const hasInlineObject = isJsonObject(data) && hasNonEmptyInlineMessage(data.inline_message);
7410
7625
  const inlineGeneration = hasInlineObject ? this.captureInlineGeneration() : void 0;
7411
7626
  const operation = () => this.processV2P2PNotification(data, "v2", inlineGeneration);
7412
7627
  if (hasInlineObject) await this.runPushProcessSerialized(ns, operation, inlineGeneration);
@@ -7417,10 +7632,10 @@ var MessageDeliveryEngine = class {
7417
7632
  const client = this.runtime.client;
7418
7633
  if (!client._v2Session) return;
7419
7634
  const pushSeq = isJsonObject(data) ? positiveSafeSequenceHint(data.seq) : 0;
7420
- const strictSeq = isJsonObject(data) && typeof data.seq === "number" && Number.isSafeInteger(data.seq) && data.seq > 0;
7421
7635
  const ns = client._aid ? `p2p:${client._aid}` : "";
7422
7636
  if (!ns) return;
7423
- 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);
7424
7639
  if (pushSeq <= 0) {
7425
7640
  if (inlinePresent) await this.recoverInvalidP2PRealtimeHead(ns, isJsonObject(data) ? data.seq : void 0);
7426
7641
  return;
@@ -7443,10 +7658,9 @@ var MessageDeliveryEngine = class {
7443
7658
  }
7444
7659
  if (inlineMatches && !this.isInlineGenerationCurrent(inlineGeneration)) return;
7445
7660
  const before = this.syncState(ns);
7446
- if (strictSeq || !inlinePresent) client._seqTracker.updateMaxSeen(ns, pushSeq);
7447
7661
  if (pushSeq <= before.ack && before.ack >= before.tail - 1) {
7448
7662
  const pending = this.pendingP2PInlineAcks?.get(ns);
7449
- if (pending && pushSeq >= pending.seq) {
7663
+ if (inlinePresent && pending && pushSeq >= pending.seq) {
7450
7664
  this.retryPendingP2PInlineAck(ns, pending.seq, "covered inline P2P ack", pushSeq);
7451
7665
  } else if (inlineMatches) {
7452
7666
  this.fireP2PV2Ack(pushSeq, "P2P inline covered push ack", inlineGeneration);
@@ -7919,7 +8133,7 @@ var MessageDeliveryEngine = class {
7919
8133
  if (event === "message.recalled") {
7920
8134
  const published = await client._withPullResponseProcessing(
7921
8135
  ns,
7922
- () => this.publishMessageRecallTombstone(seq2, payload)
8136
+ () => this.publishMessageRecallTombstone(seq2, payload, source)
7923
8137
  );
7924
8138
  this.ensurePullOperationCurrent();
7925
8139
  return published;
@@ -7941,7 +8155,7 @@ var MessageDeliveryEngine = class {
7941
8155
  if (event === "message.recalled") {
7942
8156
  const published = await client._withPullResponseProcessing(
7943
8157
  ns,
7944
- () => this.publishMessageRecallTombstone(seqNum, payload)
8158
+ () => this.publishMessageRecallTombstone(seqNum, payload, source)
7945
8159
  );
7946
8160
  this.ensurePullOperationCurrent();
7947
8161
  this.markPublishedSeq(ns, seqNum);
@@ -15389,6 +15603,11 @@ var SIGNED_METHODS = /* @__PURE__ */ new Set([
15389
15603
  "group.v2.get_proposal",
15390
15604
  "group.kick",
15391
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",
15392
15611
  "group.leave",
15393
15612
  "group.remove_member",
15394
15613
  "group.update",
@@ -15541,7 +15760,9 @@ var NON_IDEMPOTENT_METHODS = /* @__PURE__ */ new Set([
15541
15760
  "mail.send",
15542
15761
  "group.send",
15543
15762
  "group.create",
15544
- "group.invite",
15763
+ "group.accept_invite",
15764
+ "group.reject_invite",
15765
+ "group.revoke_invite",
15545
15766
  "group.kick",
15546
15767
  "group.remove_member",
15547
15768
  "group.leave",
@@ -16301,9 +16522,13 @@ var RpcPipeline = class {
16301
16522
  throw new Error("pull invalidated");
16302
16523
  }
16303
16524
  if (!key) {
16525
+ this.runtime.client._delivery?.onPullStarted?.("");
16304
16526
  const releasePullWork = this.reservePullWork();
16305
16527
  try {
16306
16528
  return await this.executePullOperation(operation, background);
16529
+ } catch (error) {
16530
+ this.runtime.client._delivery?.onPullAborted?.();
16531
+ throw error;
16307
16532
  } finally {
16308
16533
  releasePullWork();
16309
16534
  }
@@ -16428,6 +16653,7 @@ var RpcPipeline = class {
16428
16653
  const job = takeRunnable(state.foreground) ?? takeRunnable(state.background);
16429
16654
  if (!job) return;
16430
16655
  state.active = job;
16656
+ this.runtime.client._delivery?.onPullStarted?.(job.namespace);
16431
16657
  if (job.timer) clearTimeout(job.timer);
16432
16658
  job.pullingStartedAt = Date.now();
16433
16659
  job.timedOut = false;
@@ -16441,11 +16667,13 @@ var RpcPipeline = class {
16441
16667
  return;
16442
16668
  }
16443
16669
  job.running = true;
16670
+ let failed = false;
16444
16671
  void this.executePullOperation(job.operation, job.background).then(
16445
16672
  (value) => {
16446
16673
  if (!job.timedOut && !job.invalidated) job.resolve(value);
16447
16674
  },
16448
16675
  (error) => {
16676
+ failed = true;
16449
16677
  if (!job.timedOut && !job.invalidated) job.reject(error);
16450
16678
  }
16451
16679
  ).finally(() => {
@@ -16457,6 +16685,7 @@ var RpcPipeline = class {
16457
16685
  job.settledResolve?.();
16458
16686
  job.settledResolve = null;
16459
16687
  if (!job.invalidated) this.runtime.client._delivery?.onPullGateIdle?.(job.namespace);
16688
+ if (failed || job.invalidated) this.runtime.client._delivery?.onPullAborted?.();
16460
16689
  this.runtime.client._delivery?.onPullWorkSettled?.();
16461
16690
  });
16462
16691
  }
@@ -16559,6 +16788,8 @@ var RpcPipeline = class {
16559
16788
  }
16560
16789
  postprocessMessagePull(params2, result) {
16561
16790
  const client = this.runtime.client;
16791
+ const isTail = String(result.window_mode ?? params2.window_mode ?? "").trim().toLowerCase() === "tail";
16792
+ if (isTail) return;
16562
16793
  const messages = result.messages;
16563
16794
  const rawMessages = (Array.isArray(messages) ? messages : []).filter(isJsonObject);
16564
16795
  if (!client._aid || !client._seqTracker) {
@@ -16566,9 +16797,9 @@ var RpcPipeline = class {
16566
16797
  }
16567
16798
  const ns = `p2p:${client._aid}`;
16568
16799
  const contigBefore = client._seqTracker.getContiguousSeq(ns);
16569
- if (rawMessages.length) {
16570
- client._seqTracker.onPullResult(ns, rawMessages, Number(params2.after_seq ?? 0) || 0);
16571
- }
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);
16572
16803
  const retentionFloor = Math.max(0, Number(result.retention_floor_seq ?? 0));
16573
16804
  if (retentionFloor > 0) {
16574
16805
  const contig = client._seqTracker.getContiguousSeq(ns);
@@ -16587,6 +16818,8 @@ var RpcPipeline = class {
16587
16818
  if (Array.isArray(result.messages)) {
16588
16819
  result.messages = result.messages.map((message) => normalizeGroupMentionMode(message));
16589
16820
  }
16821
+ const isTail = String(result.window_mode ?? params2.window_mode ?? "").trim().toLowerCase() === "tail";
16822
+ if (isTail) return;
16590
16823
  const gid = String(params2.group_id ?? params2.group_aid ?? "").trim();
16591
16824
  if (!gid || !client._seqTracker) {
16592
16825
  return;
@@ -16595,17 +16828,17 @@ var RpcPipeline = class {
16595
16828
  const rawMessages = (Array.isArray(messages) ? messages : []).filter(isJsonObject);
16596
16829
  const ns = `group:${gid}`;
16597
16830
  const contigBefore = client._seqTracker.getContiguousSeq(ns);
16598
- if (rawMessages.length) {
16599
- client._seqTracker.onPullResult(ns, rawMessages, Number(params2.after_message_seq ?? params2.after_seq ?? 0) || 0);
16600
- }
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);
16601
16835
  const cursor = isJsonObject(result.cursor) ? result.cursor : null;
16602
16836
  const retentionFloor = Math.max(
16603
16837
  0,
16604
16838
  Number(result.retention_floor_message_seq ?? result.retention_floor_seq ?? 0),
16605
16839
  Number(cursor?.retention_floor_seq ?? 0)
16606
16840
  );
16607
- const isTail = String(result.window_mode ?? params2.window_mode ?? "").trim().toLowerCase() === "tail";
16608
- const visibilityFloor = isTail ? 0 : Math.max(
16841
+ const visibilityFloor = Math.max(
16609
16842
  0,
16610
16843
  Math.max(0, Number(result.earliest_available_message_seq ?? 1) - 1),
16611
16844
  Number(cursor?.join_seq ?? 0)
@@ -16614,7 +16847,7 @@ var RpcPipeline = class {
16614
16847
  if (effectiveFloor > 0) {
16615
16848
  const contig = client._seqTracker.getContiguousSeq(ns);
16616
16849
  if (contig < effectiveFloor) {
16617
- 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`);
16618
16851
  client._seqTracker.forceContiguousSeq(ns, effectiveFloor);
16619
16852
  }
16620
16853
  }
@@ -18147,6 +18380,21 @@ var MessageFacade = class extends RpcFacade {
18147
18380
  return this.call("message.query_online", params2);
18148
18381
  }
18149
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
+ }
18150
18398
  var MailFacade = class extends RpcFacade {
18151
18399
  send(params2) {
18152
18400
  return this.call("mail.send", params2);
@@ -18259,6 +18507,32 @@ var GroupFacade = class extends RpcFacade {
18259
18507
  addMember(params2) {
18260
18508
  return this.call("group.add_member", params2);
18261
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
+ }
18262
18536
  getMembers(params2) {
18263
18537
  return this.call("group.get_members", params2);
18264
18538
  }
@@ -23646,6 +23920,22 @@ function deduplicateForwardPageMessages(value, afterSeq) {
23646
23920
  }
23647
23921
  return [...bySeq.entries()].sort(([a], [b]) => a - b).map(([, row]) => row);
23648
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
+ }
23649
23939
  function shouldContinueForwardPage({
23650
23940
  rawCount,
23651
23941
  nextAfterSeq,
@@ -23654,10 +23944,11 @@ function shouldContinueForwardPage({
23654
23944
  hasMoreField,
23655
23945
  hasMore,
23656
23946
  serverHead,
23657
- pendingAckSeq
23947
+ pendingAckSeq,
23948
+ observedUpper
23658
23949
  }) {
23659
23950
  if (rawCount <= 0 || nextAfter <= nextAfterSeq) return false;
23660
- if (hasMore || serverHead > nextAfter || pendingAckSeq > 0) return true;
23951
+ if (hasMore || serverHead > nextAfter || pendingAckSeq > 0 || observedUpper > nextAfter) return true;
23661
23952
  return !hasMoreField && limit > 0 && rawCount >= limit;
23662
23953
  }
23663
23954
  function validateTailPage(result, afterSeq, pageLimit) {
@@ -24396,7 +24687,7 @@ var V2E2EECoordinator = class {
24396
24687
  client._clientLog.warn(`V2 sender IK pending group bootstrap failed group=${groupId}: ${String(formatE2EEError(exc))}`);
24397
24688
  }
24398
24689
  }
24399
- if (!session.getPeerIK(fromAid, senderDeviceId)) {
24690
+ if (typeof session.getPeerIK === "function" && !session.getPeerIK(fromAid, senderDeviceId)) {
24400
24691
  await this.getV2SenderPubDer(fromAid, senderDeviceId);
24401
24692
  }
24402
24693
  }
@@ -24407,7 +24698,7 @@ var V2E2EECoordinator = class {
24407
24698
  let plaintext = null;
24408
24699
  const retryStatus = {};
24409
24700
  try {
24410
- plaintext = await client._decryptV2Message(entry.msg, false, true, true, true, retryStatus);
24701
+ plaintext = await client._decryptV2Message(entry.msg, false, true, true, true, retryStatus, false, "pending_retry");
24411
24702
  } catch (exc) {
24412
24703
  if (!generationCurrent()) return;
24413
24704
  client._clientLog.warn(`V2 sender IK pending retry raised: key=${key} err=${String(formatE2EEError(exc))}`);
@@ -24432,11 +24723,12 @@ var V2E2EECoordinator = class {
24432
24723
  client._v2SenderIKPending.delete(key);
24433
24724
  const seq2 = Number(entry.msg.seq ?? 0);
24434
24725
  if (entry.groupId) {
24726
+ if (client._delivery.isSelfSentGroupMessage?.(entry.msg)) continue;
24435
24727
  plaintext = normalizeGroupMentionMode(plaintext, entry.msg, entry.msg.envelope_json);
24436
24728
  plaintext.group_id = entry.groupId;
24437
- await client._publishPulledMessage("group.message_created", `group:${entry.groupId}`, seq2, plaintext);
24729
+ await client._publishPulledMessage("group.message_created", `group:${entry.groupId}`, seq2, plaintext, false, "pending_retry");
24438
24730
  } else {
24439
- await client._publishPulledMessage("message.received", `p2p:${client._aid ?? ""}`, seq2, plaintext);
24731
+ await client._publishPulledMessage("message.received", `p2p:${client._aid ?? ""}`, seq2, plaintext, false, "pending_retry");
24440
24732
  }
24441
24733
  client._clientLog.debug(`V2 sender IK pending retry delivered: key=${key}`);
24442
24734
  }
@@ -24513,9 +24805,9 @@ var V2E2EECoordinator = class {
24513
24805
  let plaintext = null;
24514
24806
  if (String(msg.version ?? "") === "v1") {
24515
24807
  const legacy = isJsonObject(msg.legacy_v1) ? msg.legacy_v1 : {};
24516
- const payload = legacy.payload;
24808
+ const payload = legacy.payload !== void 0 ? legacy.payload : msg.payload;
24517
24809
  const payloadType = isJsonObject(payload) ? String(payload.type ?? "").trim() : "";
24518
- if (payload !== void 0 && payload !== null && !["e2ee.encrypted", "e2ee.group_encrypted"].includes(payloadType)) {
24810
+ if (payload !== void 0 && payload !== null && !["e2ee.encrypted", "e2ee.group_encrypted", "e2ee.p2p_encrypted"].includes(payloadType)) {
24519
24811
  plaintext = {
24520
24812
  message_id: String(msg.message_id ?? ""),
24521
24813
  from: String(msg.from_aid ?? ""),
@@ -24527,6 +24819,9 @@ var V2E2EECoordinator = class {
24527
24819
  encrypted: false
24528
24820
  };
24529
24821
  attachGatewayProximity(plaintext, msg);
24822
+ } else if (isJsonObject(payload) && ["e2ee.p2p_encrypted", "e2ee.encrypted"].includes(payloadType)) {
24823
+ msg = { ...msg, envelope_json: JSON.stringify(payload) };
24824
+ plaintext = await client._decryptV2Message(msg, false, !history, true, true, void 0, false, source);
24530
24825
  }
24531
24826
  } else {
24532
24827
  plaintext = source === "inline_push" ? await client._decryptV2Message(msg, false, false, false, false) : await client._decryptV2Message(msg, false);
@@ -24558,29 +24853,53 @@ var V2E2EECoordinator = class {
24558
24853
  let plaintext = null;
24559
24854
  if (String(msg.version ?? "") === "v1") {
24560
24855
  const payload = msg.payload;
24856
+ const recall = typeof client._delivery?.recallEventFromGroupMessage === "function" ? client._delivery.recallEventFromGroupMessage(msg) : null;
24857
+ if (recall) {
24858
+ const recallPayload = {
24859
+ ...recall,
24860
+ group_id: groupId,
24861
+ group_aid: groupAid,
24862
+ seq: seq2
24863
+ };
24864
+ if (publish) {
24865
+ if (orderedPublish) {
24866
+ await client._delivery.publishOrderedMessage(
24867
+ "group.message_recalled",
24868
+ `group:${groupId}`,
24869
+ seq2,
24870
+ recallPayload,
24871
+ source
24872
+ );
24873
+ } else {
24874
+ await client._publishAppEvent("group.message_recalled", recallPayload, source);
24875
+ }
24876
+ }
24877
+ return recallPayload;
24878
+ }
24561
24879
  if (payload === void 0 || payload === null) {
24562
24880
  if (history) return this.historyDecryptFailure(msg, "legacy payload is missing");
24563
24881
  client._clientLog.warn(`Group Tail \u7F3A\u5C11 payload \u7684 V1 \u884C\u5DF2\u8DF3\u8FC7\uFF0C\u8FDE\u7EED\u6027\u8BC1\u660E\u4ECD\u4FDD\u7559: group=${groupId}, seq=${seq2}`);
24564
24882
  return null;
24565
24883
  }
24566
- if (isJsonObject(payload) && ["e2ee.encrypted", "e2ee.group_encrypted"].includes(String(payload.type ?? ""))) {
24567
- if (history) return this.historyDecryptFailure(msg, "unsupported legacy encrypted envelope");
24568
- client._clientLog.warn(`Group Tail \u4E0D\u652F\u6301\u7684\u65E7\u52A0\u5BC6\u6D88\u606F\u5DF2\u8DF3\u8FC7\uFF0C\u8FDE\u7EED\u6027\u8BC1\u660E\u4ECD\u4FDD\u7559: group=${groupId}, seq=${seq2}`);
24569
- return null;
24884
+ const payloadType = isJsonObject(payload) ? String(payload.type ?? "").trim() : "";
24885
+ if (isJsonObject(payload) && ["e2ee.group_encrypted", "e2ee.p2p_encrypted", "e2ee.encrypted"].includes(payloadType)) {
24886
+ msg = { ...msg, envelope_json: JSON.stringify(payload) };
24887
+ plaintext = await client._decryptV2Message(msg, false, !history, true, true, void 0, false, source);
24888
+ } else {
24889
+ plaintext = {
24890
+ message_id: String(msg.message_id ?? ""),
24891
+ from: String(msg.from_aid ?? ""),
24892
+ group_id: groupId,
24893
+ group_aid: groupAid,
24894
+ seq: seq2,
24895
+ type: String(msg.type ?? ""),
24896
+ timestamp: msg.t_server,
24897
+ payload,
24898
+ encrypted: false
24899
+ };
24900
+ plaintext = normalizeGroupMentionMode(plaintext, msg, msg.envelope_json);
24901
+ attachGatewayProximity(plaintext, msg);
24570
24902
  }
24571
- plaintext = {
24572
- message_id: String(msg.message_id ?? ""),
24573
- from: String(msg.from_aid ?? ""),
24574
- group_id: groupId,
24575
- group_aid: groupAid,
24576
- seq: seq2,
24577
- type: String(msg.type ?? ""),
24578
- timestamp: msg.t_server,
24579
- payload,
24580
- encrypted: false
24581
- };
24582
- plaintext = normalizeGroupMentionMode(plaintext, msg, msg.envelope_json);
24583
- attachGatewayProximity(plaintext, msg);
24584
24903
  } else {
24585
24904
  plaintext = source === "inline_push" ? await client._decryptV2Message(msg, false, false, false, false) : await client._decryptV2Message(msg, false);
24586
24905
  if (plaintext) {
@@ -24597,6 +24916,9 @@ var V2E2EECoordinator = class {
24597
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}`);
24598
24917
  return null;
24599
24918
  }
24919
+ if (publish && !history && client._delivery.isSelfSentGroupMessage?.(msg)) {
24920
+ return plaintext;
24921
+ }
24600
24922
  if (publish) {
24601
24923
  if (orderedPublish) {
24602
24924
  await client._delivery.publishOrderedMessage(
@@ -24618,13 +24940,6 @@ var V2E2EECoordinator = class {
24618
24940
  async decodeInlineGroupMessage(msg, groupId, groupAid) {
24619
24941
  return await this.decodeGroupWindowMessage(msg, groupId, groupAid, false, false, "inline_push");
24620
24942
  }
24621
- async applyTailFloor(ns, floor) {
24622
- const client = this.client;
24623
- if (!ns || floor <= 0 || client._seqTracker.getContiguousSeq(ns) >= floor) return;
24624
- client._seqTracker.forceContiguousSeq(ns, floor);
24625
- client._persistSeq(ns);
24626
- await client._saveSeqTrackerState?.();
24627
- }
24628
24943
  async pullV2TailInternal(params2, deferPublish = false) {
24629
24944
  const client = this.client;
24630
24945
  if (params2.window_mode !== "tail") throw new ValidationError("window_mode must equal tail");
@@ -24632,10 +24947,8 @@ var V2E2EECoordinator = class {
24632
24947
  const afterSeq = strictWindowSeq(params2.after_seq ?? 0, "after_seq");
24633
24948
  const limit = strictWindowLimit(params2.limit ?? V2_PULL_DEFAULT_PAGE_LIMIT);
24634
24949
  const ns = client._aid ? `p2p:${client._aid}` : "";
24950
+ if (ns) client._delivery.onPullStarted?.(ns);
24635
24951
  const result = await client._callRawV2Rpc("message.v2.pull", { window_mode: "tail", after_seq: afterSeq, limit, _rpc_foreground: true });
24636
- const floor = Number(result.retention_floor_seq ?? 0);
24637
- const previous = ns ? client._seqTracker.getSyncState(ns) : null;
24638
- await this.applyTailFloor(ns, Number.isSafeInteger(floor) ? floor : 0);
24639
24952
  const page = validateTailPage(result, afterSeq, limit);
24640
24953
  if (ns) {
24641
24954
  client._seqTracker.commitTailWindow(ns, {
@@ -24653,7 +24966,17 @@ var V2E2EECoordinator = class {
24653
24966
  if (plaintext) decoded.push(plaintext);
24654
24967
  }
24655
24968
  const ack = ns ? client._seqTracker.getContiguousSeq(ns) : 0;
24656
- if (previous && ack > previous.ack) await this.ackV2(ack);
24969
+ if (ns) client._delivery.onPullPage?.(
24970
+ ns,
24971
+ decoded.length,
24972
+ result.has_more === true,
24973
+ page.head,
24974
+ typeof result.remaining === "number" && Number.isSafeInteger(result.remaining) && result.remaining >= 0 ? result.remaining : null,
24975
+ page.messages.length,
24976
+ ack,
24977
+ page.head
24978
+ );
24979
+ client._delivery.onPullWorkSettled?.();
24657
24980
  return { ...result, messages: decoded, raw_count: page.messages.length };
24658
24981
  }
24659
24982
  async historyV2Internal(params2) {
@@ -24668,10 +24991,24 @@ var V2E2EECoordinator = class {
24668
24991
  }
24669
24992
  return { ...result, messages: decoded };
24670
24993
  }
24994
+ async runPullWithLifecycle(ns, operation) {
24995
+ if (ns) this.client._delivery.onPullStarted?.(ns);
24996
+ try {
24997
+ return await operation();
24998
+ } catch (error) {
24999
+ this.client._delivery.onPullAborted?.();
25000
+ throw error;
25001
+ }
25002
+ }
24671
25003
  async pullV2(afterSeq = 0, limit = 50, opts) {
25004
+ const ns = this.client._aid ? `p2p:${this.client._aid}` : "";
25005
+ return await this.runPullWithLifecycle(ns, () => this.pullV2Impl(afterSeq, limit, opts));
25006
+ }
25007
+ async pullV2Impl(afterSeq = 0, limit = 50, opts) {
24672
25008
  const client = this.client;
24673
25009
  await client._ensureV2SessionReady("message.pull");
24674
25010
  const ns = client._aid ? `p2p:${client._aid}` : "";
25011
+ if (ns) client._delivery.onPullStarted?.(ns);
24675
25012
  if (opts?.windowMode === "tail") {
24676
25013
  if (!opts.gateLocked) {
24677
25014
  const key = pullGateKeyForClient(client, "message.v2.pull", {
@@ -24679,7 +25016,7 @@ var V2E2EECoordinator = class {
24679
25016
  after_seq: afterSeq,
24680
25017
  limit
24681
25018
  }, `${ns}|tail|after=${afterSeq}|limit=${limit}`);
24682
- return await client._runPullSerialized(key, () => this.pullV2(afterSeq, limit, {
25019
+ return await client._runPullSerialized(key, () => this.pullV2Impl(afterSeq, limit, {
24683
25020
  ...opts ?? {},
24684
25021
  gateLocked: true
24685
25022
  }), false);
@@ -24693,7 +25030,7 @@ var V2E2EECoordinator = class {
24693
25030
  force: opts?.force === true,
24694
25031
  limit
24695
25032
  }, `${ns}|forward|after=${afterSeq}|force=${String(Boolean(opts?.force))}|limit=${limit}`);
24696
- return await client._runPullSerialized(key, () => this.pullV2(afterSeq, limit, {
25033
+ return await client._runPullSerialized(key, () => this.pullV2Impl(afterSeq, limit, {
24697
25034
  ...opts ?? {},
24698
25035
  gateLocked: true
24699
25036
  }), true);
@@ -24748,17 +25085,23 @@ var V2E2EECoordinator = class {
24748
25085
  const pageContigBefore = ns ? client._seqTracker.getContiguousSeq(ns) : 0;
24749
25086
  const pageTrackerSnapshot = ns && typeof client._seqTracker.snapshotNamespace === "function" ? client._seqTracker.snapshotNamespace(ns) : null;
24750
25087
  const pageMaxSeq = seqs.length > 0 ? Math.max(...seqs) : nextAfterSeq;
25088
+ const pageContiguousSeq = contiguousPullSeq(
25089
+ pageContigBefore,
25090
+ seqs,
25091
+ result.contiguous_seq
25092
+ );
24751
25093
  const deferredKeyFetches = /* @__PURE__ */ new Map();
24752
25094
  let blockedSeq = 0;
25095
+ const pageDecryptedBefore = decrypted.length;
24753
25096
  for (const msg of messages) {
24754
25097
  const seq2 = Number(msg.seq ?? 0);
24755
25098
  if (!Number.isFinite(seq2) || seq2 <= 0) continue;
24756
25099
  const version = String(msg.version ?? "v2");
24757
25100
  if (version === "v1") {
24758
25101
  const legacy = isJsonObject(msg.legacy_v1) ? msg.legacy_v1 : {};
24759
- const legacyPayload = legacy.payload;
25102
+ const legacyPayload = legacy.payload !== void 0 ? legacy.payload : msg.payload;
24760
25103
  const payloadType = isJsonObject(legacyPayload) ? String(legacyPayload.type ?? "").trim() : "";
24761
- if (legacyPayload !== void 0 && legacyPayload !== null && payloadType !== "e2ee.encrypted" && payloadType !== "e2ee.group_encrypted") {
25104
+ if (legacyPayload !== void 0 && legacyPayload !== null && !["e2ee.encrypted", "e2ee.group_encrypted", "e2ee.p2p_encrypted"].includes(payloadType)) {
24762
25105
  const v1Msg = {
24763
25106
  message_id: String(msg.message_id ?? ""),
24764
25107
  from: String(msg.from_aid ?? ""),
@@ -24774,6 +25117,30 @@ var V2E2EECoordinator = class {
24774
25117
  if (ns) await client._publishPulledMessage(appEvent.event, ns, seq2, appEvent.payload, false);
24775
25118
  else await client._publishAppEvent(appEvent.event, appEvent.payload, "pull");
24776
25119
  decrypted.push(v1Msg);
25120
+ } else if (isJsonObject(legacyPayload) && ["e2ee.p2p_encrypted", "e2ee.encrypted"].includes(payloadType)) {
25121
+ const deferStatus2 = {};
25122
+ const plaintext2 = await client._decryptV2Message(
25123
+ { ...msg, envelope_json: JSON.stringify(legacyPayload) },
25124
+ true,
25125
+ true,
25126
+ true,
25127
+ true,
25128
+ deferStatus2,
25129
+ true,
25130
+ "pull"
25131
+ );
25132
+ if (deferStatus2.deferred) {
25133
+ blockedSeq = blockedSeq > 0 ? Math.min(blockedSeq, seq2) : seq2;
25134
+ if (deferStatus2.fromAid) {
25135
+ const key = `${deferStatus2.fromAid}\0${deferStatus2.senderDeviceId ?? ""}\0${deferStatus2.groupId ?? ""}`;
25136
+ deferredKeyFetches.set(key, { fromAid: deferStatus2.fromAid, senderDeviceId: deferStatus2.senderDeviceId ?? "", groupId: deferStatus2.groupId ?? "" });
25137
+ }
25138
+ }
25139
+ if (plaintext2) {
25140
+ if (ns) await client._publishPulledMessage("message.received", ns, seq2, plaintext2, false);
25141
+ else await client._publishAppEvent("message.received", plaintext2, "pull");
25142
+ decrypted.push(plaintext2);
25143
+ }
24777
25144
  } else {
24778
25145
  client._clientLog.debug(`message.v2.pull skipping V1 envelope seq=${seq2} payload_type=${payloadType || "<none>"} (V1 E2EE removed)`);
24779
25146
  }
@@ -24788,7 +25155,7 @@ var V2E2EECoordinator = class {
24788
25155
  client._v2Session.trackOldSPKMaxSeq(msgSpkId, seq2);
24789
25156
  }
24790
25157
  const deferStatus = {};
24791
- const plaintext = await client._decryptV2Message(msg, true, true, true, true, deferStatus, true);
25158
+ const plaintext = await client._decryptV2Message(msg, true, true, true, true, deferStatus, true, "pull");
24792
25159
  if (deferStatus.deferred) {
24793
25160
  blockedSeq = blockedSeq > 0 ? Math.min(blockedSeq, seq2) : seq2;
24794
25161
  }
@@ -24814,11 +25181,12 @@ var V2E2EECoordinator = class {
24814
25181
  const serverAckSeq = parsedServerAckSeq ?? 0;
24815
25182
  const retentionFloor = Math.max(0, Number(result.retention_floor_seq ?? 0));
24816
25183
  if (ns) {
24817
- const commitTarget = blockedSeq > 0 ? pageContigBefore : Math.max(
25184
+ const commitTarget = Math.max(
24818
25185
  pageContigBefore,
24819
25186
  Number.isFinite(retentionFloor) ? retentionFloor : 0,
24820
25187
  pageCount === 1 ? deferredServerCursor : 0,
24821
- seqs.length > 0 ? pageMaxSeq : pageContigBefore
25188
+ serverAckSeq,
25189
+ blockedSeq > 0 ? blockedSeq - 1 : pageContiguousSeq
24822
25190
  );
24823
25191
  let ackSeq = pageContigBefore;
24824
25192
  let contigAdvanced = false;
@@ -24856,11 +25224,11 @@ var V2E2EECoordinator = class {
24856
25224
  this.recordForwardCursor(ns, serverAckSeq, ackSeq);
24857
25225
  }
24858
25226
  const knownServerAckSeq = hasServerAckSeq ? serverAckSeq : pageCount === 1 ? deferredServerCursor : 0;
24859
- 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);
24860
25228
  if (ackNeeded) {
24861
25229
  this.recordForwardAck(ns, ackSeq);
24862
- const nextAfter2 = Math.max(pageMaxSeq, nextAfterSeq);
24863
- const canContinuePage = messages.length > 0 && nextAfter2 > nextAfterSeq;
25230
+ const nextAfter2 = Math.max(pageContiguousSeq, nextAfterSeq);
25231
+ const canContinuePage = blockedSeq <= 0 && messages.length > 0 && nextAfter2 > nextAfterSeq;
24864
25232
  if (canContinuePage) {
24865
25233
  pendingAckSeq = Math.max(pendingAckSeq, ackSeq);
24866
25234
  lastAutoAckSeq = Math.max(lastAutoAckSeq, ackSeq);
@@ -24870,7 +25238,17 @@ var V2E2EECoordinator = class {
24870
25238
  }
24871
25239
  }
24872
25240
  }
24873
- const nextAfter = Math.max(pageMaxSeq, nextAfterSeq);
25241
+ if (ns) client._delivery.onPullPage?.(
25242
+ ns,
25243
+ decrypted.length - pageDecryptedBefore,
25244
+ hasMore,
25245
+ pageMaxSeq,
25246
+ typeof result.remaining === "number" && Number.isSafeInteger(result.remaining) && result.remaining >= 0 ? result.remaining : null,
25247
+ messages.length,
25248
+ client._seqTracker.getContiguousSeq(ns),
25249
+ pageMaxSeq
25250
+ );
25251
+ const nextAfter = Math.max(pageContiguousSeq, nextAfterSeq);
24874
25252
  const rawCount = messages.length;
24875
25253
  if (blockedSeq > 0) break;
24876
25254
  const shouldContinue = shouldContinueForwardPage({
@@ -24881,7 +25259,8 @@ var V2E2EECoordinator = class {
24881
25259
  hasMoreField,
24882
25260
  hasMore,
24883
25261
  serverHead,
24884
- pendingAckSeq
25262
+ pendingAckSeq,
25263
+ observedUpper: typeof client._seqTracker.getMaxSeenSeq === "function" ? client._seqTracker.getMaxSeenSeq(ns) : nextAfter
24885
25264
  });
24886
25265
  if (!shouldContinue) break;
24887
25266
  if (pullGateKey && opts?.gateLocked && client._rpcPipeline?.yieldPullGate) {
@@ -24904,7 +25283,8 @@ var V2E2EECoordinator = class {
24904
25283
  pendingAckSeq = 0;
24905
25284
  }
24906
25285
  }
24907
- return decrypted;
25286
+ client._delivery.onPullWorkSettled?.();
25287
+ return deduplicatePulledResults(decrypted);
24908
25288
  }
24909
25289
  async confirmForwardP2PAck(upToSeq, inlineGeneration) {
24910
25290
  const ns = this.client._aid ? `p2p:${this.client._aid}` : "";
@@ -24990,10 +25370,7 @@ var V2E2EECoordinator = class {
24990
25370
  if (isJsonObject(result)) {
24991
25371
  const seq2 = Number(result.seq ?? 0);
24992
25372
  if (seq2 > 0) {
24993
- const ns = `group:${gid}`;
24994
- client._seqTracker.onMessageSeq(ns, seq2);
24995
- client._markPublishedSeq(ns, seq2);
24996
- await client._saveSeqTrackerState();
25373
+ client._markPublishedSeq(`group:${gid}`, seq2);
24997
25374
  }
24998
25375
  }
24999
25376
  return client._delivery.attachSendResultEnvelope("group.send", withExplicitGroupAid({
@@ -25013,10 +25390,7 @@ var V2E2EECoordinator = class {
25013
25390
  if (isJsonObject(result)) {
25014
25391
  const seq2 = Number(result.seq ?? 0);
25015
25392
  if (seq2 > 0) {
25016
- const ns = `group:${gid}`;
25017
- client._seqTracker.onMessageSeq(ns, seq2);
25018
- client._markPublishedSeq(ns, seq2);
25019
- await client._saveSeqTrackerState();
25393
+ client._markPublishedSeq(`group:${gid}`, seq2);
25020
25394
  }
25021
25395
  }
25022
25396
  return client._delivery.attachSendResultEnvelope("group.send", withExplicitGroupAid({
@@ -25041,6 +25415,11 @@ var V2E2EECoordinator = class {
25041
25415
  const afterSeq = strictWindowSeq(params2.after_seq ?? 0, "after_seq");
25042
25416
  const limit = strictWindowLimit(params2.limit ?? V2_PULL_DEFAULT_PAGE_LIMIT);
25043
25417
  const ns = `group:${groupId}`;
25418
+ const cursorParams = isJsonObject(params2._group_cursor_params) ? params2._group_cursor_params : params2;
25419
+ const requestDeviceId = String(cursorParams.device_id ?? "").trim();
25420
+ const requestSlotId = String(cursorParams.slot_id ?? "").trim();
25421
+ const ownsCursor = (!requestDeviceId || requestDeviceId === String(client._deviceId ?? "")) && (!requestSlotId || requestSlotId === String(client._slotId ?? ""));
25422
+ if (ownsCursor) client._delivery.onPullStarted?.(ns);
25044
25423
  const result = await client._callRawV2Rpc("group.v2.pull", withExplicitGroupAid({
25045
25424
  group_id: groupId,
25046
25425
  window_mode: "tail",
@@ -25050,9 +25429,6 @@ var V2E2EECoordinator = class {
25050
25429
  }, groupAid));
25051
25430
  const resultAid = String(result.group_aid ?? result.groupAid ?? "").trim();
25052
25431
  if (resultAid) groupAid = resultAid;
25053
- const floor = Number(result.retention_floor_message_seq ?? result.retention_floor_seq ?? 0);
25054
- const previous = client._seqTracker.getSyncState(ns);
25055
- await this.applyTailFloor(ns, Number.isSafeInteger(floor) ? floor : 0);
25056
25432
  const page = validateTailPage(result, afterSeq, limit);
25057
25433
  client._seqTracker.commitTailWindow(ns, {
25058
25434
  windowStartSeq: page.windowStart,
@@ -25069,7 +25445,17 @@ var V2E2EECoordinator = class {
25069
25445
  if (plaintext) decoded.push(plaintext);
25070
25446
  }
25071
25447
  const ack = client._seqTracker.getContiguousSeq(ns);
25072
- if (ack > previous.ack) await this.ackGroupV2(groupId, ack, groupAid);
25448
+ if (ownsCursor) client._delivery.onPullPage?.(
25449
+ ns,
25450
+ decoded.length,
25451
+ result.has_more === true,
25452
+ page.head,
25453
+ typeof result.remaining === "number" && Number.isSafeInteger(result.remaining) && result.remaining >= 0 ? result.remaining : null,
25454
+ page.messages.length,
25455
+ ack,
25456
+ page.head
25457
+ );
25458
+ client._delivery.onPullWorkSettled?.();
25073
25459
  return { ...result, messages: decoded, raw_count: page.messages.length };
25074
25460
  }
25075
25461
  async historyGroupV2Internal(params2) {
@@ -25102,6 +25488,13 @@ var V2E2EECoordinator = class {
25102
25488
  });
25103
25489
  }
25104
25490
  async pullGroupV2(groupId, afterSeq = 0, limit = 50, opts) {
25491
+ const lifecycleNs = `group:${String(groupId ?? "").trim()}`;
25492
+ if (opts?.ownsCursor === false) {
25493
+ return await this.pullGroupV2Impl(groupId, afterSeq, limit, opts);
25494
+ }
25495
+ return await this.runPullWithLifecycle(lifecycleNs, () => this.pullGroupV2Impl(groupId, afterSeq, limit, opts));
25496
+ }
25497
+ async pullGroupV2Impl(groupId, afterSeq = 0, limit = 50, opts) {
25105
25498
  const client = this.client;
25106
25499
  await client._ensureV2SessionReady("group.pull");
25107
25500
  const gid = String(groupId ?? "").trim();
@@ -25117,12 +25510,13 @@ var V2E2EECoordinator = class {
25117
25510
  limit,
25118
25511
  _group_cursor_params: opts?.cursorParams
25119
25512
  }, `${ns}|tail|after=${afterSeq}|limit=${limit}`);
25120
- return await client._runPullSerialized(key, () => this.pullGroupV2(gid, afterSeq, limit, {
25513
+ return await client._runPullSerialized(key, () => this.pullGroupV2Impl(gid, afterSeq, limit, {
25121
25514
  ...opts ?? {},
25122
25515
  gateLocked: true
25123
25516
  }), false);
25124
25517
  }
25125
25518
  const result = await this.pullGroupV2TailInternal({
25519
+ ...opts?.cursorParams ?? {},
25126
25520
  group_id: String(opts.wireGroupId ?? gid),
25127
25521
  group_aid: groupAid || void 0,
25128
25522
  window_mode: "tail",
@@ -25139,7 +25533,7 @@ var V2E2EECoordinator = class {
25139
25533
  limit,
25140
25534
  _group_cursor_params: opts?.cursorParams
25141
25535
  }, `${ns}|forward|after=${afterSeq}|force=${String(Boolean(opts?.force))}|limit=${limit}`);
25142
- return await client._runPullSerialized(key, () => this.pullGroupV2(gid, afterSeq, limit, {
25536
+ return await client._runPullSerialized(key, () => this.pullGroupV2Impl(gid, afterSeq, limit, {
25143
25537
  ...opts ?? {},
25144
25538
  gateLocked: true
25145
25539
  }), true);
@@ -25148,6 +25542,7 @@ var V2E2EECoordinator = class {
25148
25542
  const wireGroupId = String(opts?.wireGroupId ?? groupId ?? "").trim() || gid;
25149
25543
  const cursorParams = opts?.cursorParams ?? {};
25150
25544
  const ownsCursor = opts?.ownsCursor !== false;
25545
+ if (ownsCursor) client._delivery.onPullStarted?.(ns);
25151
25546
  let pullGateKey = ownsCursor ? pullGateKeyForClient(client, "group.v2.pull", {
25152
25547
  group_id: gid,
25153
25548
  after_seq: afterSeq,
@@ -25206,28 +25601,35 @@ var V2E2EECoordinator = class {
25206
25601
  const pageContigBefore = client._seqTracker.getContiguousSeq(ns);
25207
25602
  const pageTrackerSnapshot = typeof client._seqTracker.snapshotNamespace === "function" ? client._seqTracker.snapshotNamespace(ns) : null;
25208
25603
  const pageMaxSeq = seqs.length > 0 ? Math.max(...seqs) : nextAfterSeq;
25604
+ const pageContiguousSeq = contiguousPullSeq(
25605
+ pageContigBefore,
25606
+ seqs,
25607
+ result.contiguous_seq
25608
+ );
25209
25609
  const deferredKeyFetches = /* @__PURE__ */ new Map();
25210
25610
  let blockedSeq = 0;
25611
+ const pageDecryptedBefore = decrypted.length;
25211
25612
  for (const msg of messages) {
25212
25613
  const seq2 = Number(msg.seq ?? 0);
25213
25614
  if (!Number.isFinite(seq2) || seq2 <= 0) continue;
25214
25615
  const version = String(msg.version ?? "v2");
25215
25616
  if (version === "v1") {
25216
- const payload = msg.payload;
25617
+ const legacy = isJsonObject(msg.legacy_v1) ? msg.legacy_v1 : {};
25618
+ const payload = msg.payload !== void 0 ? msg.payload : legacy.payload;
25217
25619
  const payloadObj = isJsonObject(payload) ? payload : null;
25218
- if (client._delivery.recallEventFromGroupMessage(msg)) {
25620
+ if (typeof client._delivery?.recallEventFromGroupMessage === "function" && client._delivery.recallEventFromGroupMessage(msg)) {
25219
25621
  await client._delivery.publishGroupRecallTombstone(gid, seq2, {
25220
25622
  ...msg,
25221
25623
  group_id: gid,
25222
25624
  group_aid: eventGroupAid
25223
- });
25625
+ }, "pull");
25224
25626
  client._markPublishedSeq(ns, seq2);
25225
25627
  client._clientLog.debug(`group.v2.pull recall tombstone delivered: group=${gid}, seq=${seq2}`);
25226
25628
  continue;
25227
25629
  }
25228
25630
  if (payloadObj) {
25229
25631
  const payloadType = String(payloadObj.type ?? "").trim();
25230
- if (payloadType !== "e2ee.encrypted" && payloadType !== "e2ee.group_encrypted") {
25632
+ if (payloadType !== "e2ee.encrypted" && payloadType !== "e2ee.group_encrypted" && payloadType !== "e2ee.p2p_encrypted") {
25231
25633
  let v1Msg = {
25232
25634
  message_id: String(msg.message_id ?? ""),
25233
25635
  from: String(msg.from_aid ?? ""),
@@ -25241,7 +25643,11 @@ var V2E2EECoordinator = class {
25241
25643
  };
25242
25644
  v1Msg = normalizeGroupMentionMode(v1Msg, msg, msg.envelope_json);
25243
25645
  attachGatewayProximity(v1Msg, msg);
25244
- 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
+ }
25245
25651
  decrypted.push(v1Msg);
25246
25652
  continue;
25247
25653
  }
@@ -25263,6 +25669,36 @@ var V2E2EECoordinator = class {
25263
25669
  decrypted.push(v1Msg);
25264
25670
  continue;
25265
25671
  }
25672
+ if (payloadObj && ["e2ee.group_encrypted", "e2ee.p2p_encrypted", "e2ee.encrypted"].includes(String(payloadObj.type ?? "").trim())) {
25673
+ const deferStatus2 = {};
25674
+ const plaintext2 = await client._decryptV2Message(
25675
+ { ...msg, envelope_json: JSON.stringify(payloadObj) },
25676
+ true,
25677
+ true,
25678
+ true,
25679
+ true,
25680
+ deferStatus2,
25681
+ true,
25682
+ "pull"
25683
+ );
25684
+ if (deferStatus2.deferred) {
25685
+ blockedSeq = blockedSeq > 0 ? Math.min(blockedSeq, seq2) : seq2;
25686
+ if (deferStatus2.fromAid) {
25687
+ const key = `${deferStatus2.fromAid}\0${deferStatus2.senderDeviceId ?? ""}\0${deferStatus2.groupId ?? ""}`;
25688
+ deferredKeyFetches.set(key, { fromAid: deferStatus2.fromAid, senderDeviceId: deferStatus2.senderDeviceId ?? "", groupId: deferStatus2.groupId ?? "" });
25689
+ }
25690
+ }
25691
+ if (plaintext2) {
25692
+ plaintext2.group_id = gid;
25693
+ plaintext2.group_aid = eventGroupAid;
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
+ }
25699
+ decrypted.push(plaintext2);
25700
+ }
25701
+ }
25266
25702
  client._clientLog.debug(`group.v2.pull skipping V1 envelope group=${gid} seq=${seq2} payload_type=${payloadObj ? String(payloadObj.type ?? "") : "<none>"} (V1 E2EE removed)`);
25267
25703
  continue;
25268
25704
  }
@@ -25271,7 +25707,7 @@ var V2E2EECoordinator = class {
25271
25707
  continue;
25272
25708
  }
25273
25709
  const deferStatus = {};
25274
- let plaintext = await client._decryptV2Message(msg, true, true, true, true, deferStatus, true);
25710
+ let plaintext = await client._decryptV2Message(msg, true, true, true, true, deferStatus, true, "pull");
25275
25711
  if (deferStatus.deferred) {
25276
25712
  blockedSeq = blockedSeq > 0 ? Math.min(blockedSeq, seq2) : seq2;
25277
25713
  }
@@ -25287,7 +25723,11 @@ var V2E2EECoordinator = class {
25287
25723
  plaintext = normalizeGroupMentionMode(plaintext, msg, msg.envelope_json);
25288
25724
  plaintext.group_id = gid;
25289
25725
  plaintext.group_aid = eventGroupAid;
25290
- 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
+ }
25291
25731
  decrypted.push(plaintext);
25292
25732
  }
25293
25733
  const hasServerCursor = cursor !== null && Object.prototype.hasOwnProperty.call(cursor, "current_seq");
@@ -25303,11 +25743,12 @@ var V2E2EECoordinator = class {
25303
25743
  Number(cursor?.join_seq ?? 0)
25304
25744
  );
25305
25745
  const effectiveFloor = Math.max(retentionFloor, visibilityFloor);
25306
- const commitTarget = blockedSeq > 0 ? pageContigBefore : Math.max(
25746
+ const commitTarget = Math.max(
25307
25747
  pageContigBefore,
25308
25748
  Number.isFinite(effectiveFloor) ? effectiveFloor : 0,
25309
25749
  ownsCursor && pageCount === 1 ? deferredServerCursor : 0,
25310
- seqs.length > 0 ? pageMaxSeq : pageContigBefore
25750
+ cursorCurrentSeq,
25751
+ blockedSeq > 0 ? blockedSeq - 1 : pageContiguousSeq
25311
25752
  );
25312
25753
  let ackSeq = pageContigBefore;
25313
25754
  let contigAdvanced = false;
@@ -25343,15 +25784,25 @@ var V2E2EECoordinator = class {
25343
25784
  for (const fetch2 of deferredKeyFetches.values()) {
25344
25785
  this.scheduleSenderIKFetch(fetch2.fromAid, fetch2.senderDeviceId, fetch2.groupId);
25345
25786
  }
25787
+ client._delivery.onPullPage?.(
25788
+ ns,
25789
+ decrypted.length - pageDecryptedBefore,
25790
+ hasMore,
25791
+ pageMaxSeq,
25792
+ typeof result.remaining === "number" && Number.isSafeInteger(result.remaining) && result.remaining >= 0 ? result.remaining : null,
25793
+ messages.length,
25794
+ client._seqTracker.getContiguousSeq(ns),
25795
+ pageMaxSeq
25796
+ );
25346
25797
  if (ownsCursor && parsedCursorCurrentSeq !== null && cursorCurrentSeq > ackSeq) {
25347
25798
  this.recordForwardCursor(ns, cursorCurrentSeq, ackSeq);
25348
25799
  }
25349
25800
  const knownServerCursorSeq = hasServerCursor ? cursorCurrentSeq : ownsCursor && pageCount === 1 ? deferredServerCursor : 0;
25350
- 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);
25351
25802
  if (ackNeeded) {
25352
25803
  this.recordForwardAck(ns, ackSeq);
25353
- const nextAfter2 = Math.max(pageMaxSeq, nextAfterSeq);
25354
- const canContinuePage = messages.length > 0 && nextAfter2 > nextAfterSeq && ownsCursor;
25804
+ const nextAfter2 = Math.max(pageContiguousSeq, nextAfterSeq);
25805
+ const canContinuePage = blockedSeq <= 0 && messages.length > 0 && nextAfter2 > nextAfterSeq && ownsCursor;
25355
25806
  if (canContinuePage) {
25356
25807
  pendingAckSeq = Math.max(pendingAckSeq, ackSeq);
25357
25808
  lastAutoAckSeq = Math.max(lastAutoAckSeq, ackSeq);
@@ -25360,7 +25811,7 @@ var V2E2EECoordinator = class {
25360
25811
  lastAutoAckSeq = Math.max(lastAutoAckSeq, ackSeq);
25361
25812
  }
25362
25813
  }
25363
- const nextAfter = Math.max(pageMaxSeq, nextAfterSeq);
25814
+ const nextAfter = Math.max(pageContiguousSeq, nextAfterSeq);
25364
25815
  if (!ownsCursor) break;
25365
25816
  const rawCount = messages.length;
25366
25817
  if (blockedSeq > 0) break;
@@ -25372,7 +25823,8 @@ var V2E2EECoordinator = class {
25372
25823
  hasMoreField,
25373
25824
  hasMore,
25374
25825
  serverHead,
25375
- pendingAckSeq
25826
+ pendingAckSeq,
25827
+ observedUpper: typeof client._seqTracker.getMaxSeenSeq === "function" ? client._seqTracker.getMaxSeenSeq(ns) : nextAfter
25376
25828
  });
25377
25829
  if (!shouldContinue) break;
25378
25830
  if (pullGateKey && opts?.gateLocked && client._rpcPipeline?.yieldPullGate) {
@@ -25397,7 +25849,8 @@ var V2E2EECoordinator = class {
25397
25849
  pendingAckSeq = 0;
25398
25850
  }
25399
25851
  }
25400
- return decrypted;
25852
+ client._delivery.onPullWorkSettled?.();
25853
+ return deduplicatePulledResults(decrypted);
25401
25854
  }
25402
25855
  async confirmForwardGroupAck(groupId, upToSeq, groupAid) {
25403
25856
  const ns = `group:${String(groupId ?? "").trim()}`;
@@ -25906,7 +26359,7 @@ var V2E2EECoordinator = class {
25906
26359
  }
25907
26360
  async decryptV2PushMessage(data) {
25908
26361
  if (!isJsonObject(data)) return null;
25909
- return await this.client._decryptV2Message(data);
26362
+ return await this.client._decryptV2Message(data, false, false, false, false, void 0, false, "inline_push");
25910
26363
  }
25911
26364
  async buildV2P2PEnvelope(opts) {
25912
26365
  const client = this.client;
@@ -26234,7 +26687,8 @@ var MEMBERSHIP_MUTATION_METHODS = /* @__PURE__ */ new Set([
26234
26687
  "group.review_join_request",
26235
26688
  "group.batch_review_join_request",
26236
26689
  "group.use_invite_code",
26237
- "group.request_join"
26690
+ "group.request_join",
26691
+ "group.accept_invite"
26238
26692
  ]);
26239
26693
  var SPK_REGISTRATION_MUTATION_METHODS = /* @__PURE__ */ new Set([
26240
26694
  "group.create",
@@ -28963,6 +29417,7 @@ var RECONNECT_MIN_BASE_DELAY_SECONDS = 1;
28963
29417
  var RECONNECT_MAX_BASE_DELAY_SECONDS = 64;
28964
29418
  var TOKEN_REFRESH_CHECK_INTERVAL_MS = 3e4;
28965
29419
  var MAX_NOTIFY_PAYLOAD_SIZE = 64 * 1024;
29420
+ var GROUP_INVITE_METADATA_KEY = "group_invite_pending_v1";
28966
29421
  function clampReconnectDelaySeconds(value, fallback, upper = RECONNECT_MAX_BASE_DELAY_SECONDS) {
28967
29422
  const parsed = Number(value);
28968
29423
  const seconds = Number.isFinite(parsed) ? parsed : fallback;
@@ -29355,6 +29810,9 @@ var _AUNClient = class _AUNClient {
29355
29810
  __publicField(this, "_mailFacade");
29356
29811
  __publicField(this, "_groupFacade");
29357
29812
  __publicField(this, "_streamFacade");
29813
+ __publicField(this, "_groupInvitePending", /* @__PURE__ */ new Map());
29814
+ __publicField(this, "_groupInviteStateAid", null);
29815
+ __publicField(this, "_groupInviteStateLoad", null);
29358
29816
  /**
29359
29817
  * 处理 V2 push 通知:自动 pull + decrypt + emit。
29360
29818
  */
@@ -29478,6 +29936,15 @@ var _AUNClient = class _AUNClient {
29478
29936
  this._dispatcher.subscribe("_raw.group.changed", (data) => {
29479
29937
  this._onRawGroupChanged(data);
29480
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
+ });
29481
29948
  this._dispatcher.subscribe("_raw.peer.v2.message_received", (data) => {
29482
29949
  this._safeAsync(this._onV2PushNotification(data));
29483
29950
  });
@@ -29952,6 +30419,53 @@ var _AUNClient = class _AUNClient {
29952
30419
  await this._agentMdManager.upload(await this._agentMdManager.readContent(target) ?? buildDefaultAgentMd(target));
29953
30420
  return true;
29954
30421
  }
30422
+ async _checkIdentityAdmissionAfterConnect() {
30423
+ const aid = String(this._aid ?? this._currentAid?.aid ?? "").trim();
30424
+ if (!aid) return;
30425
+ try {
30426
+ const checked = await this._agentMdManager.check(aid);
30427
+ if (!checked.remote_found) {
30428
+ const content = await this._agentMdManager.readContent(aid) ?? buildDefaultAgentMd(aid);
30429
+ await this._agentMdManager.upload(content);
30430
+ }
30431
+ } catch (exc) {
30432
+ this._clientLog.warn(`post-connect agent.md check failed: ${String(exc)}`);
30433
+ }
30434
+ const store = this._aidStore;
30435
+ if (!store) return;
30436
+ let listed;
30437
+ try {
30438
+ listed = await this.call("group.list_my", {});
30439
+ } catch (exc) {
30440
+ this._clientLog.warn(`post-connect group identity check failed: ${String(exc)}`);
30441
+ return;
30442
+ }
30443
+ const result = isJsonObject(listed) ? listed : {};
30444
+ const groups = Array.isArray(result.groups) ? result.groups : Array.isArray(result.items) ? result.items : [];
30445
+ for (const raw of groups) {
30446
+ if (!isJsonObject(raw)) continue;
30447
+ const role = String(raw.role ?? raw.my_role ?? "").trim().toLowerCase();
30448
+ if (role !== "owner") continue;
30449
+ const groupId = String(raw.group_id ?? raw.groupId ?? raw.group_aid ?? raw.groupAid ?? "").trim();
30450
+ const groupAid = String(raw.group_aid ?? raw.groupAid ?? groupId).trim();
30451
+ if (!groupId || !groupAid) continue;
30452
+ try {
30453
+ await this._runGroupIdentityOperation(groupId, async () => {
30454
+ const loaded = await store.load(groupAid);
30455
+ if (!loaded.ok || !loaded.data?.aid?.isPrivateKeyValid()) {
30456
+ await this.bindGroupAid({ group_id: groupId, group_aid: groupAid }, { aidStore: store });
30457
+ return;
30458
+ }
30459
+ const checked = await this._agentMdManager.check(groupAid);
30460
+ if (!checked.remote_found) {
30461
+ await this._uploadGroupAgentMd(store, groupAid, {}, {}, aid);
30462
+ }
30463
+ });
30464
+ } catch (exc) {
30465
+ this._clientLog.warn(`post-connect group identity check failed: group=${groupId} err=${String(exc)}`);
30466
+ }
30467
+ }
30468
+ }
29955
30469
  async _runGroupIdentityOperation(groupId, operation) {
29956
30470
  const aid = String(this._aid ?? this._currentAid?.aid ?? "").trim();
29957
30471
  const dot = aid.indexOf(".");
@@ -29978,6 +30492,8 @@ var _AUNClient = class _AUNClient {
29978
30492
  }
29979
30493
  this._aidStore = store;
29980
30494
  const payload = { ...params2 };
30495
+ const initialMembers = payload.members;
30496
+ delete payload.members;
29981
30497
  if (payload.group_name === void 0 && payload.groupName !== void 0) {
29982
30498
  payload.group_name = payload.groupName;
29983
30499
  delete payload.groupName;
@@ -30007,7 +30523,8 @@ var _AUNClient = class _AUNClient {
30007
30523
  const result2 = { ...createdMap, ...boundMap, group: { ...createdGroup, ...boundGroup } };
30008
30524
  const postprocessParams2 = { ...payload };
30009
30525
  delete postprocessParams2._defer_group_ready_postprocess;
30010
- 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);
30011
30528
  }
30012
30529
  const pendingKey = `create:${String(payload.group_name).trim().toLowerCase()}`;
30013
30530
  const keystore = store._keystore;
@@ -30057,7 +30574,29 @@ var _AUNClient = class _AUNClient {
30057
30574
  }
30058
30575
  const postprocessParams = { ...payload };
30059
30576
  delete postprocessParams._defer_group_ready_postprocess;
30060
- 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 };
30061
30600
  }
30062
30601
  _groupAgentMdContent(groupAid, params2 = {}, group = {}) {
30063
30602
  const explicit = params2.group_agent_md ?? params2.groupAgentMd ?? params2.content;
@@ -30585,7 +31124,7 @@ var _AUNClient = class _AUNClient {
30585
31124
  _markPublishedSeq(ns, seq2) {
30586
31125
  this._delivery.markPublishedSeq(ns, seq2);
30587
31126
  }
30588
- async _publishAppEvent(event, payload, source = "", ns = "", batch) {
31127
+ async _publishAppEvent(event, payload, source = "direct", ns = "", batch) {
30589
31128
  await this._delivery.publishAppEvent(event, payload, source, ns, batch);
30590
31129
  }
30591
31130
  _echoTimestamp() {
@@ -30639,6 +31178,239 @@ var _AUNClient = class _AUNClient {
30639
31178
  const member = isJsonObject(result.member) ? result.member : null;
30640
31179
  return member ? String(member.group_id ?? member.groupId ?? member.group_aid ?? member.groupAid ?? "") : "";
30641
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
+ }
30642
31414
  async _onRawGroupChanged(data) {
30643
31415
  const tStart = Date.now();
30644
31416
  const action = String(data?.action ?? "");
@@ -30969,6 +31741,7 @@ var _AUNClient = class _AUNClient {
30969
31741
  }
30970
31742
  this._lifecycle.assertConnectionAttemptOwner(connectionOwner);
30971
31743
  this._startBackgroundTasks();
31744
+ this._safeAsync(this.restoreGroupInvitesAfterConnect());
30972
31745
  const connectionKind = String(params2.connection_kind ?? "long");
30973
31746
  const isShortConnection = connectionKind === "short";
30974
31747
  const hasExplicitBackgroundSync = Object.prototype.hasOwnProperty.call(params2, "background_sync");
@@ -30983,6 +31756,7 @@ var _AUNClient = class _AUNClient {
30983
31756
  if (backgroundSyncEnabled) {
30984
31757
  this._safeAsync(this._fillP2pGap());
30985
31758
  }
31759
+ this._safeAsync(this._checkIdentityAdmissionAfterConnect());
30986
31760
  this._clientLog.debug(`_connectOnce exit: elapsed=${Date.now() - tStart}ms aid=${this._aid ?? "-"}`);
30987
31761
  } catch (err) {
30988
31762
  this._clientLog.debug(`_connectOnce exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
@@ -31678,20 +32452,7 @@ var _AUNClient = class _AUNClient {
31678
32452
  return this._delivery.clampAckParams(method, params2);
31679
32453
  }
31680
32454
  _repairPushContiguousBound(ns, pushSeq, hasPayload, label) {
31681
- if (!ns || !Number.isFinite(pushSeq) || pushSeq <= 0) {
31682
- return ns ? this._seqTracker.getContiguousSeq(ns) : 0;
31683
- }
31684
- const contig = this._seqTracker.getContiguousSeq(ns);
31685
- const shouldRepair = contig > pushSeq;
31686
- if (!shouldRepair) return contig;
31687
- const repairedTo = Math.max(0, pushSeq - 1);
31688
- this._seqTracker.repairContiguousSeq(ns, repairedTo);
31689
- const repaired = this._seqTracker.getContiguousSeq(ns);
31690
- this._persistRepairedSeq(ns);
31691
- this._clientLog.warn(
31692
- `${label} push repaired contiguous_seq: ns=${ns} payload=${hasPayload} push_seq=${pushSeq} contiguous=${contig}->${repaired}`
31693
- );
31694
- return repaired;
32455
+ return ns ? this._seqTracker.getContiguousSeq(ns) : 0;
31695
32456
  }
31696
32457
  async _ensureV2SessionReady(method, errorMessage3) {
31697
32458
  if (!this._v2SessionMatchesIdentity()) {
@@ -31869,7 +32630,7 @@ var _AUNClient = class _AUNClient {
31869
32630
  * undecryptable 事件、成功消费后触发 SPK 轮换。inline Push 在游标提交和
31870
32631
  * Head/正文绑定校验前调用本入口时必须显式关闭这些副作用。
31871
32632
  */
31872
- async _decryptV2Message(msg, allowPending = true, emitUndecryptable = true, rotateKeys = true, observeAgentMd = true, deferStatus, deferKeyFetch = false) {
32633
+ async _decryptV2Message(msg, allowPending = true, emitUndecryptable = true, rotateKeys = true, observeAgentMd = true, deferStatus, deferKeyFetch = false, source = "pull") {
31873
32634
  const session = this._v2Session;
31874
32635
  if (!session) return null;
31875
32636
  const envJson = msg.envelope_json;
@@ -31957,7 +32718,8 @@ var _AUNClient = class _AUNClient {
31957
32718
  _decrypt_stage: "spk_lookup",
31958
32719
  _envelope_type: String(envelope.type ?? ""),
31959
32720
  _suite: String(envelope.suite ?? ""),
31960
- _spk_id: spkId
32721
+ _spk_id: spkId,
32722
+ source
31961
32723
  };
31962
32724
  attachV2EnvelopeMetadata2(event, e2eeMeta);
31963
32725
  this._dispatcher.enqueue(undecryptableEvent, event);
@@ -31998,7 +32760,8 @@ var _AUNClient = class _AUNClient {
31998
32760
  _decrypt_error: "sender_ik_not_found",
31999
32761
  _decrypt_stage: "sender_ik",
32000
32762
  _envelope_type: String(envelope.type ?? ""),
32001
- _suite: String(envelope.suite ?? "")
32763
+ _suite: String(envelope.suite ?? ""),
32764
+ source
32002
32765
  };
32003
32766
  attachV2EnvelopeMetadata2(event, e2eeMeta);
32004
32767
  this._dispatcher.enqueue(undecryptableEvent, event);
@@ -32034,7 +32797,8 @@ var _AUNClient = class _AUNClient {
32034
32797
  _decrypt_error: String(exc),
32035
32798
  _decrypt_stage: "decrypt",
32036
32799
  _envelope_type: String(envelope.type ?? ""),
32037
- _suite: String(envelope.suite ?? "")
32800
+ _suite: String(envelope.suite ?? ""),
32801
+ source
32038
32802
  };
32039
32803
  attachV2EnvelopeMetadata2(event, e2eeMeta);
32040
32804
  this._dispatcher.enqueue(undecryptableEvent, event);