@agentunion/fastaun-browser 0.5.10 → 0.5.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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.11";
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);
@@ -4877,6 +5010,26 @@ function nonNegativeSafeSequenceHint(value) {
4877
5010
  if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) return null;
4878
5011
  return value;
4879
5012
  }
5013
+ function canonicalDeliverySource(source) {
5014
+ switch (String(source ?? "").trim()) {
5015
+ case "group-push":
5016
+ case "ordered":
5017
+ case "legacy":
5018
+ return "push";
5019
+ case "tail":
5020
+ case "pull-drained":
5021
+ case "pull_drained":
5022
+ return "pull";
5023
+ case "inline-push":
5024
+ return "inline_push";
5025
+ case "pending-retry":
5026
+ return "pending_retry";
5027
+ case "":
5028
+ return "direct";
5029
+ default:
5030
+ return String(source).trim();
5031
+ }
5032
+ }
4880
5033
  function deduplicatePlainForwardPageMessages(value, afterSeq) {
4881
5034
  if (!Array.isArray(value)) return [];
4882
5035
  const bySeq = /* @__PURE__ */ new Map();
@@ -4905,6 +5058,9 @@ function p2pAppEventFromPlainPullMessage(message) {
4905
5058
  var MessageDeliveryEngine = class {
4906
5059
  constructor(runtime) {
4907
5060
  __publicField(this, "runtime");
5061
+ __publicField(this, "syncRun", 0);
5062
+ __publicField(this, "syncingNamespaces", /* @__PURE__ */ new Set());
5063
+ __publicField(this, "syncSessions", /* @__PURE__ */ new Map());
4908
5064
  __publicField(this, "pendingPullDeliveryChanges", null);
4909
5065
  __publicField(this, "deliveryGeneration", 0);
4910
5066
  __publicField(this, "realtimeTailResults", null);
@@ -4925,6 +5081,7 @@ var MessageDeliveryEngine = class {
4925
5081
  this.runtime = runtime;
4926
5082
  }
4927
5083
  resetInlineAckState() {
5084
+ this.syncStopped("aborted");
4928
5085
  this.inlineGeneration += 1;
4929
5086
  this.deliveryGeneration += 1;
4930
5087
  this.pendingPullDeliveryChanges = null;
@@ -4940,6 +5097,8 @@ var MessageDeliveryEngine = class {
4940
5097
  this.pendingP2PInlineAcks = null;
4941
5098
  this.pendingGroupInlineAcks = null;
4942
5099
  this.pendingGroupInlineAckNamespaces = null;
5100
+ this.syncingNamespaces.clear();
5101
+ this.syncSessions.clear();
4943
5102
  }
4944
5103
  isInlineGenerationCurrent(generation) {
4945
5104
  return generation === this.inlineGeneration;
@@ -4967,6 +5126,91 @@ var MessageDeliveryEngine = class {
4967
5126
  endRealtimeSync(ns) {
4968
5127
  this.realtimeSyncing?.delete(ns);
4969
5128
  }
5129
+ onPullStarted(ns) {
5130
+ if (!ns || this.syncingNamespaces.has(ns)) return;
5131
+ if (this.syncingNamespaces.size === 0) {
5132
+ this.syncRun += 1;
5133
+ this.syncSessions.clear();
5134
+ this.runtime.client._dispatcher?.enqueue?.("sync.started", {
5135
+ run_id: this.syncRun,
5136
+ source: "pull",
5137
+ syncing: true,
5138
+ namespaces_pending: 1,
5139
+ received_total: 0,
5140
+ namespace: ns,
5141
+ started_at: Date.now()
5142
+ });
5143
+ }
5144
+ this.syncingNamespaces.add(ns);
5145
+ this.syncSessions.set(ns, { pages: 0, raw: 0, pulled: 0 });
5146
+ }
5147
+ onPullPage(ns, pulledCount, hasMore, maxSeen, remaining, rawCount = pulledCount, currentSeq, targetSeq) {
5148
+ this.onPullStarted(ns);
5149
+ if (!ns) return;
5150
+ const session = this.syncSessions.get(ns) ?? { pages: 0, raw: 0, pulled: 0 };
5151
+ session.pages += 1;
5152
+ session.raw += Math.max(0, rawCount);
5153
+ session.pulled += Math.max(0, pulledCount);
5154
+ this.syncSessions.set(ns, session);
5155
+ const received = session.pulled;
5156
+ const progress = {
5157
+ run_id: this.syncRun,
5158
+ source: "pull",
5159
+ namespace: ns,
5160
+ page: session.pages,
5161
+ page_raw_count: Math.max(0, rawCount),
5162
+ page_pulled_count: Math.max(0, pulledCount),
5163
+ pulled_count: received,
5164
+ received_total: received,
5165
+ batch_size: Math.max(0, pulledCount),
5166
+ estimated_remaining: hasMore ? void 0 : 0,
5167
+ max_seen: maxSeen,
5168
+ has_more: hasMore
5169
+ };
5170
+ if (typeof currentSeq === "number" && Number.isSafeInteger(currentSeq) && currentSeq >= 0) {
5171
+ progress.current_seq = currentSeq;
5172
+ }
5173
+ if (typeof targetSeq === "number" && Number.isSafeInteger(targetSeq) && targetSeq >= 0) {
5174
+ progress.target_seq = targetSeq;
5175
+ }
5176
+ if (typeof remaining === "number" && Number.isSafeInteger(remaining) && remaining >= 0) {
5177
+ progress.remaining = remaining;
5178
+ progress.estimated_remaining = progress.remaining;
5179
+ }
5180
+ this.runtime.client._dispatcher?.enqueue?.("sync.progress", progress);
5181
+ }
5182
+ onPullAborted() {
5183
+ this.syncStopped("aborted");
5184
+ }
5185
+ syncStopped(reason = "pull_drained") {
5186
+ if (this.syncingNamespaces.size === 0) return;
5187
+ const sessions = [...this.syncSessions.entries()];
5188
+ const receivedTotal = sessions.reduce((total, [, session]) => total + session.pulled, 0);
5189
+ this.syncingNamespaces.clear();
5190
+ this.syncSessions.clear();
5191
+ const payload = {
5192
+ run_id: this.syncRun,
5193
+ source: "pull",
5194
+ syncing: false,
5195
+ namespaces_pending: 0,
5196
+ received_total: receivedTotal,
5197
+ reason,
5198
+ stopped_at: Date.now()
5199
+ };
5200
+ if (sessions.length === 1) {
5201
+ const [namespace, session] = sessions[0];
5202
+ payload.namespace = namespace;
5203
+ payload.pages = session.pages;
5204
+ payload.pulled_count = session.pulled;
5205
+ } else if (sessions.length > 1) {
5206
+ payload.namespaces = sessions.map(([namespace]) => namespace);
5207
+ payload.pages = sessions.reduce((total, [, session]) => total + session.pages, 0);
5208
+ payload.pulled_count = receivedTotal;
5209
+ }
5210
+ this.runtime.client._dispatcher?.enqueue?.("sync.stopped", {
5211
+ ...payload
5212
+ });
5213
+ }
4970
5214
  hasPendingPull(ns) {
4971
5215
  const pending = ns.startsWith("p2p:") ? this.pendingP2pPullUpper : this.pendingGroupPullUpper;
4972
5216
  return (pending?.get(ns) ?? 0) > 0;
@@ -4978,7 +5222,7 @@ var MessageDeliveryEngine = class {
4978
5222
  onPullWorkSettled() {
4979
5223
  const pipeline = this.runtime.client._rpcPipeline;
4980
5224
  if (pipeline?.hasAnyPullActivity?.() === true) return;
4981
- void this.flushPullDeliveryChanges();
5225
+ void this.flushPullDeliveryChanges().finally(() => this.syncStopped("pull_drained"));
4982
5226
  }
4983
5227
  deliveryChangeNamespace(event, payload, ns = "") {
4984
5228
  if (event === "message.received") return "p2p";
@@ -5024,7 +5268,11 @@ var MessageDeliveryEngine = class {
5024
5268
  if (batch.generation !== this.deliveryGeneration) return;
5025
5269
  const changes = this.deliveryChangesPayload(batch.changes);
5026
5270
  if (changes.length === 0) return;
5027
- this.runtime.client._dispatcher.enqueue("delivery.changed", { trigger: batch.trigger, changes });
5271
+ this.runtime.client._dispatcher.enqueue("delivery.changed", {
5272
+ trigger: batch.trigger,
5273
+ source: batch.trigger,
5274
+ changes
5275
+ });
5028
5276
  }
5029
5277
  async flushPullDeliveryChanges() {
5030
5278
  const generation = this.deliveryGeneration;
@@ -5034,7 +5282,11 @@ var MessageDeliveryEngine = class {
5034
5282
  if (generation !== this.deliveryGeneration) return;
5035
5283
  const changes = this.deliveryChangesPayload(pending);
5036
5284
  if (changes.length === 0) return;
5037
- this.runtime.client._dispatcher.enqueue("delivery.changed", { trigger: "pull_drained", changes });
5285
+ this.runtime.client._dispatcher.enqueue("delivery.changed", {
5286
+ trigger: "pull_drained",
5287
+ source: "pull",
5288
+ changes
5289
+ });
5038
5290
  }
5039
5291
  recordPendingPull(ns, seq2) {
5040
5292
  if (!ns || !Number.isSafeInteger(seq2) || seq2 <= 0) return;
@@ -5537,7 +5789,7 @@ var MessageDeliveryEngine = class {
5537
5789
  }
5538
5790
  const message = normalizeGroupMentionMode(rawMessage);
5539
5791
  if (this.recallEventFromGroupMessage(message)) {
5540
- if (await this.publishGroupRecallTombstone(groupId, seq2, message)) {
5792
+ if (await this.publishGroupRecallTombstone(groupId, seq2, message, "pull")) {
5541
5793
  this.ensurePullOperationCurrent();
5542
5794
  this.markPublishedSeq(ns, seq2);
5543
5795
  publishedCount += 1;
@@ -5608,6 +5860,17 @@ var MessageDeliveryEngine = class {
5608
5860
  this.ensurePullOperationCurrent();
5609
5861
  await this.confirmPlainForwardAck(ns, ackMethod, pendingAckSeq, groupId);
5610
5862
  }
5863
+ const remaining = typeof response.remaining === "number" && Number.isSafeInteger(response.remaining) && response.remaining >= 0 ? response.remaining : null;
5864
+ this.onPullPage(
5865
+ ns,
5866
+ publishedCount,
5867
+ response.has_more === true,
5868
+ messages.length > 0 ? Number(messages[messages.length - 1].seq ?? 0) : void 0,
5869
+ remaining,
5870
+ messages.length,
5871
+ client._seqTracker.getContiguousSeq(ns),
5872
+ messages.length > 0 ? Number(messages[messages.length - 1].seq ?? 0) : void 0
5873
+ );
5611
5874
  return { rawCount: messages.length, publishedCount };
5612
5875
  }
5613
5876
  enqueueOrderedMessage(ns, event, seq2, payload, source = "push") {
@@ -5629,11 +5892,11 @@ var MessageDeliveryEngine = class {
5629
5892
  async publishOrderedQueueItem(ns, event, seq2, payload, pullResponse = false, source = "push", batch) {
5630
5893
  const client = this.runtime.client;
5631
5894
  if (event === "group.changed" && this.isGroupEventNamespace(ns)) {
5632
- await this.publishOrderedGroupChanged(payload);
5895
+ await this.publishOrderedGroupChanged(payload, source);
5633
5896
  return;
5634
5897
  }
5635
5898
  if (event === "message.recalled") {
5636
- await this.publishMessageRecallTombstone(seq2, payload);
5899
+ await this.publishMessageRecallTombstone(seq2, payload, source);
5637
5900
  return;
5638
5901
  }
5639
5902
  if (pullResponse) {
@@ -5642,7 +5905,7 @@ var MessageDeliveryEngine = class {
5642
5905
  }
5643
5906
  await client._publishAppEvent(event, payload, source, ns, batch);
5644
5907
  }
5645
- async publishOrderedGroupChanged(payload) {
5908
+ async publishOrderedGroupChanged(payload, source = "ordered") {
5646
5909
  const client = this.runtime.client;
5647
5910
  if (isJsonObject(payload)) {
5648
5911
  const eventPayload = payload;
@@ -5653,7 +5916,7 @@ var MessageDeliveryEngine = class {
5653
5916
  if (groupId) client._cleanupDissolvedGroup?.(groupId);
5654
5917
  }
5655
5918
  }
5656
- await client._publishAppEvent("group.changed", payload);
5919
+ await client._publishAppEvent("group.changed", payload, source);
5657
5920
  }
5658
5921
  isInstanceScopedMessageEvent(event) {
5659
5922
  return event === "message.received" || event === "message.recalled" || event === "message.undecryptable" || event === "group.message_created" || event === "group.message_recalled" || event === "group.message_undecryptable";
@@ -5670,17 +5933,22 @@ var MessageDeliveryEngine = class {
5670
5933
  }
5671
5934
  return result;
5672
5935
  }
5673
- normalizePublishedMessagePayload(event, payload) {
5936
+ normalizePublishedMessagePayload(event, payload, source = "direct") {
5937
+ let normalized;
5674
5938
  if (this.isInstanceScopedMessageEvent(event)) {
5675
5939
  payload = this.stripInlineInternalFields(payload);
5676
5940
  if (event === "group.message_created") payload = normalizeGroupMentionMode(payload);
5677
- return this.attachAppMessageEnvelope(this.stripInternalSenderDeviceFields(this.attachCurrentInstanceContext(payload)));
5678
- }
5679
- if (this.isGroupScopedEvent(event)) {
5941
+ normalized = this.attachAppMessageEnvelope(this.stripInternalSenderDeviceFields(this.attachCurrentInstanceContext(payload)));
5942
+ } else if (this.isGroupScopedEvent(event)) {
5680
5943
  payload = this.stripInlineInternalFields(payload);
5681
- return this.attachAppGroupEventEnvelope(this.attachCurrentInstanceContext(payload));
5944
+ normalized = this.attachAppGroupEventEnvelope(this.attachCurrentInstanceContext(payload));
5945
+ } else {
5946
+ normalized = payload;
5682
5947
  }
5683
- return payload;
5948
+ const canonicalSource = canonicalDeliverySource(source);
5949
+ if (canonicalSource === "direct" || !isJsonObject(normalized)) return normalized;
5950
+ if (!this.isInstanceScopedMessageEvent(event) && !this.isGroupScopedEvent(event)) return normalized;
5951
+ return { ...normalized, source: canonicalSource };
5684
5952
  }
5685
5953
  stripInlineInternalFields(payload) {
5686
5954
  if (!isJsonObject(payload)) return payload;
@@ -5896,7 +6164,7 @@ var MessageDeliveryEngine = class {
5896
6164
  if (tombstoneId) return `p2p|tombstone:${tombstoneId}`;
5897
6165
  return `p2p|unknown:${Date.now()}:${Math.random()}`;
5898
6166
  }
5899
- async publishMessageRecallTombstone(seq2, message) {
6167
+ async publishMessageRecallTombstone(seq2, message, source = "push") {
5900
6168
  const client = this.runtime.client;
5901
6169
  const eventPayload = this.recallEventFromMessage(message);
5902
6170
  if (!eventPayload) return false;
@@ -5915,7 +6183,7 @@ var MessageDeliveryEngine = class {
5915
6183
  const drop = [...seen.entries()].sort((a, b) => a[1] - b[1]).slice(0, seen.size - MESSAGE_RECALL_SEEN_LIMIT);
5916
6184
  for (const [oldKey] of drop) seen.delete(oldKey);
5917
6185
  }
5918
- await client._publishAppEvent("message.recalled", eventPayload);
6186
+ await client._publishAppEvent("message.recalled", eventPayload, source);
5919
6187
  client._clientLog.debug(`message.recalled published: seq=${String(seq2)} ids=${JSON.stringify(eventPayload.message_ids)}`);
5920
6188
  return true;
5921
6189
  }
@@ -5977,7 +6245,7 @@ var MessageDeliveryEngine = class {
5977
6245
  if (tombstoneId) return `${normalizedGroupId}|tombstone:${tombstoneId}`;
5978
6246
  return `${normalizedGroupId}|unknown:${Date.now()}:${Math.random()}`;
5979
6247
  }
5980
- async publishGroupRecallTombstone(groupId, seq2, message) {
6248
+ async publishGroupRecallTombstone(groupId, seq2, message, source = "push") {
5981
6249
  const client = this.runtime.client;
5982
6250
  const eventPayload = this.recallEventFromGroupMessage(message);
5983
6251
  if (!eventPayload) return false;
@@ -5998,7 +6266,7 @@ var MessageDeliveryEngine = class {
5998
6266
  const drop = [...seen.entries()].sort((a, b) => a[1] - b[1]).slice(0, seen.size - GROUP_RECALL_SEEN_LIMIT);
5999
6267
  for (const [oldKey] of drop) seen.delete(oldKey);
6000
6268
  }
6001
- await client._publishAppEvent("group.message_recalled", eventPayload);
6269
+ await client._publishAppEvent("group.message_recalled", eventPayload, source);
6002
6270
  client._clientLog.debug(`group.message_recalled published: group=${groupId} seq=${String(seq2)} ids=${JSON.stringify(eventPayload.message_ids)}`);
6003
6271
  return true;
6004
6272
  }
@@ -6061,7 +6329,7 @@ var MessageDeliveryEngine = class {
6061
6329
  }
6062
6330
  if (contig !== contigBefore) this.persistSeq(ns);
6063
6331
  }
6064
- async publishAppEvent(event, payload, source = "", ns = "", batch) {
6332
+ async publishAppEvent(event, payload, source = "direct", ns = "", batch) {
6065
6333
  const client = this.runtime.client;
6066
6334
  const generation = this.deliveryGeneration;
6067
6335
  if ((event === "message.received" || event === "group.message_created") && isJsonObject(payload)) {
@@ -6082,11 +6350,16 @@ var MessageDeliveryEngine = class {
6082
6350
  client._clientLog.debug(`agent_md etag inject skipped: ${String(exc)}`);
6083
6351
  }
6084
6352
  }
6085
- client._dispatcher.enqueue(event, this.normalizePublishedMessagePayload(event, payload));
6353
+ const canonicalSource = canonicalDeliverySource(source);
6354
+ let normalized = this.normalizePublishedMessagePayload(event, payload, source);
6355
+ if (isJsonObject(normalized) && (this.isInstanceScopedMessageEvent(event) || this.isGroupScopedEvent(event)) && canonicalSource !== "direct") {
6356
+ normalized = { ...normalized, source: canonicalSource };
6357
+ }
6358
+ client._dispatcher.enqueue(event, normalized);
6086
6359
  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);
6360
+ if (canonicalSource !== "pull" && canonicalSource !== "pending_retry" && canonicalSource !== "push" && canonicalSource !== "inline_push") return;
6361
+ if (canonicalSource === "push" || canonicalSource === "inline_push") {
6362
+ const localBatch = batch ?? this.createDeliveryChangeBatch(canonicalSource);
6090
6363
  this.recordDeliveryChange(localBatch.changes, event, payload, ns, generation);
6091
6364
  if (!batch) await this.flushRealtimeDeliveryChanges(localBatch);
6092
6365
  } else {
@@ -6287,7 +6560,7 @@ var MessageDeliveryEngine = class {
6287
6560
  _decrypt_error: String(exc)
6288
6561
  };
6289
6562
  client._attachV2EnvelopeMetadataFromSource(safeEvent, data);
6290
- await client._publishAppEvent("message.undecryptable", safeEvent);
6563
+ await client._publishAppEvent("message.undecryptable", safeEvent, "push");
6291
6564
  }
6292
6565
  }
6293
6566
  }
@@ -6398,7 +6671,7 @@ var MessageDeliveryEngine = class {
6398
6671
  _decrypt_error: String(exc)
6399
6672
  };
6400
6673
  client._attachV2EnvelopeMetadataFromSource(safeEvent, data);
6401
- await client._publishAppEvent("group.message_undecryptable", safeEvent);
6674
+ await client._publishAppEvent("group.message_undecryptable", safeEvent, "push");
6402
6675
  }
6403
6676
  }
6404
6677
  }
@@ -6406,7 +6679,7 @@ var MessageDeliveryEngine = class {
6406
6679
  const client = this.runtime.client;
6407
6680
  const groupId = notification.group_id ?? "";
6408
6681
  if (!groupId) {
6409
- await client._publishAppEvent("group.message_created", notification);
6682
+ await client._publishAppEvent("group.message_created", notification, "push");
6410
6683
  return;
6411
6684
  }
6412
6685
  if (client._sessionOptions?.background_sync === false) {
@@ -6426,7 +6699,7 @@ var MessageDeliveryEngine = class {
6426
6699
  return;
6427
6700
  } catch (exc) {
6428
6701
  client._clientLog.warn(`auto pull group message failed:${String(exc)}`);
6429
- await client._publishAppEvent("group.message_created", notification);
6702
+ await client._publishAppEvent("group.message_created", notification, "push");
6430
6703
  return;
6431
6704
  }
6432
6705
  }
@@ -6453,7 +6726,7 @@ var MessageDeliveryEngine = class {
6453
6726
  } catch (exc) {
6454
6727
  client._clientLog.warn(`auto pull group message commit/ack failed:${String(exc)}`);
6455
6728
  if (!rawCompleted) {
6456
- await client._publishAppEvent("group.message_created", notification);
6729
+ await client._publishAppEvent("group.message_created", notification, "push");
6457
6730
  }
6458
6731
  }
6459
6732
  }
@@ -6581,6 +6854,7 @@ var MessageDeliveryEngine = class {
6581
6854
  client._gapFillDone.add(dedupKey);
6582
6855
  this.runtime.delivery.setGapFillActive(true);
6583
6856
  let continuationAfterSeq = 0;
6857
+ let failed = false;
6584
6858
  try {
6585
6859
  let nextAfterSeq = afterSeq;
6586
6860
  const maxPages = singlePage ? 1 : 100;
@@ -6628,6 +6902,7 @@ var MessageDeliveryEngine = class {
6628
6902
  }
6629
6903
  const eventSeqs = [];
6630
6904
  let hasDissolvedEvent = false;
6905
+ let publishedEventCount = 0;
6631
6906
  for (const evt of eventObjects) {
6632
6907
  const eventSeq = Number(evt.event_seq ?? 0);
6633
6908
  if (Number.isFinite(eventSeq) && eventSeq > 0) eventSeqs.push(eventSeq);
@@ -6645,7 +6920,8 @@ var MessageDeliveryEngine = class {
6645
6920
  }
6646
6921
  }
6647
6922
  if (Number.isFinite(eventSeq) && eventSeq > 0 && !client._pushedSeqs.get(ns)?.has(eventSeq)) {
6648
- this.enqueueOrderedMessage(ns, "group.changed", eventSeq, evt);
6923
+ this.enqueueOrderedMessage(ns, "group.changed", eventSeq, evt, "pull");
6924
+ publishedEventCount += 1;
6649
6925
  }
6650
6926
  }
6651
6927
  const ackContig = client._seqTracker.getContiguousSeq(ns);
@@ -6670,6 +6946,16 @@ var MessageDeliveryEngine = class {
6670
6946
  client._clientLog.warn("group event auto-ack failed: group=" + groupId, e);
6671
6947
  }
6672
6948
  }
6949
+ this.onPullPage(
6950
+ ns,
6951
+ publishedEventCount,
6952
+ result.has_more === true,
6953
+ eventObjects.length > 0 ? Number(eventObjects[eventObjects.length - 1].event_seq ?? 0) : void 0,
6954
+ typeof result.remaining === "number" && Number.isSafeInteger(result.remaining) && result.remaining >= 0 ? result.remaining : null,
6955
+ eventObjects.length,
6956
+ ackContig,
6957
+ eventObjects.length > 0 ? Number(eventObjects[eventObjects.length - 1].event_seq ?? 0) : void 0
6958
+ );
6673
6959
  const nextAfter = Math.max(eventSeqs.length > 0 ? Math.max(...eventSeqs) : nextAfterSeq, nextAfterSeq);
6674
6960
  if (singlePage && result.has_more === true && nextAfter > nextAfterSeq) {
6675
6961
  continuationAfterSeq = nextAfter;
@@ -6681,10 +6967,12 @@ var MessageDeliveryEngine = class {
6681
6967
  client._clientLog.warn(`group event gap fill reached max_pages=${maxPages} group=${groupId} after_seq=${nextAfterSeq}`);
6682
6968
  }
6683
6969
  } catch (exc) {
6970
+ failed = true;
6684
6971
  client._clientLog.warn(`group event gap-fill failed:${String(exc)}`);
6685
6972
  } finally {
6686
6973
  client._gapFillDone.delete(dedupKey);
6687
6974
  this.runtime.delivery.setGapFillActive(false);
6975
+ if (!client._rpcPipeline) this.syncStopped(failed ? "aborted" : "pull_drained");
6688
6976
  if (singlePage && continuationAfterSeq > afterSeq) {
6689
6977
  this.enqueueOnlineUnreadEventHint({
6690
6978
  group_id: groupId,
@@ -7919,7 +8207,7 @@ var MessageDeliveryEngine = class {
7919
8207
  if (event === "message.recalled") {
7920
8208
  const published = await client._withPullResponseProcessing(
7921
8209
  ns,
7922
- () => this.publishMessageRecallTombstone(seq2, payload)
8210
+ () => this.publishMessageRecallTombstone(seq2, payload, source)
7923
8211
  );
7924
8212
  this.ensurePullOperationCurrent();
7925
8213
  return published;
@@ -7941,7 +8229,7 @@ var MessageDeliveryEngine = class {
7941
8229
  if (event === "message.recalled") {
7942
8230
  const published = await client._withPullResponseProcessing(
7943
8231
  ns,
7944
- () => this.publishMessageRecallTombstone(seqNum, payload)
8232
+ () => this.publishMessageRecallTombstone(seqNum, payload, source)
7945
8233
  );
7946
8234
  this.ensurePullOperationCurrent();
7947
8235
  this.markPublishedSeq(ns, seqNum);
@@ -16301,9 +16589,13 @@ var RpcPipeline = class {
16301
16589
  throw new Error("pull invalidated");
16302
16590
  }
16303
16591
  if (!key) {
16592
+ this.runtime.client._delivery?.onPullStarted?.("");
16304
16593
  const releasePullWork = this.reservePullWork();
16305
16594
  try {
16306
16595
  return await this.executePullOperation(operation, background);
16596
+ } catch (error) {
16597
+ this.runtime.client._delivery?.onPullAborted?.();
16598
+ throw error;
16307
16599
  } finally {
16308
16600
  releasePullWork();
16309
16601
  }
@@ -16428,6 +16720,7 @@ var RpcPipeline = class {
16428
16720
  const job = takeRunnable(state.foreground) ?? takeRunnable(state.background);
16429
16721
  if (!job) return;
16430
16722
  state.active = job;
16723
+ this.runtime.client._delivery?.onPullStarted?.(job.namespace);
16431
16724
  if (job.timer) clearTimeout(job.timer);
16432
16725
  job.pullingStartedAt = Date.now();
16433
16726
  job.timedOut = false;
@@ -16441,11 +16734,13 @@ var RpcPipeline = class {
16441
16734
  return;
16442
16735
  }
16443
16736
  job.running = true;
16737
+ let failed = false;
16444
16738
  void this.executePullOperation(job.operation, job.background).then(
16445
16739
  (value) => {
16446
16740
  if (!job.timedOut && !job.invalidated) job.resolve(value);
16447
16741
  },
16448
16742
  (error) => {
16743
+ failed = true;
16449
16744
  if (!job.timedOut && !job.invalidated) job.reject(error);
16450
16745
  }
16451
16746
  ).finally(() => {
@@ -16457,6 +16752,7 @@ var RpcPipeline = class {
16457
16752
  job.settledResolve?.();
16458
16753
  job.settledResolve = null;
16459
16754
  if (!job.invalidated) this.runtime.client._delivery?.onPullGateIdle?.(job.namespace);
16755
+ if (failed || job.invalidated) this.runtime.client._delivery?.onPullAborted?.();
16460
16756
  this.runtime.client._delivery?.onPullWorkSettled?.();
16461
16757
  });
16462
16758
  }
@@ -24407,7 +24703,7 @@ var V2E2EECoordinator = class {
24407
24703
  let plaintext = null;
24408
24704
  const retryStatus = {};
24409
24705
  try {
24410
- plaintext = await client._decryptV2Message(entry.msg, false, true, true, true, retryStatus);
24706
+ plaintext = await client._decryptV2Message(entry.msg, false, true, true, true, retryStatus, false, "pending_retry");
24411
24707
  } catch (exc) {
24412
24708
  if (!generationCurrent()) return;
24413
24709
  client._clientLog.warn(`V2 sender IK pending retry raised: key=${key} err=${String(formatE2EEError(exc))}`);
@@ -24434,9 +24730,9 @@ var V2E2EECoordinator = class {
24434
24730
  if (entry.groupId) {
24435
24731
  plaintext = normalizeGroupMentionMode(plaintext, entry.msg, entry.msg.envelope_json);
24436
24732
  plaintext.group_id = entry.groupId;
24437
- await client._publishPulledMessage("group.message_created", `group:${entry.groupId}`, seq2, plaintext);
24733
+ await client._publishPulledMessage("group.message_created", `group:${entry.groupId}`, seq2, plaintext, false, "pending_retry");
24438
24734
  } else {
24439
- await client._publishPulledMessage("message.received", `p2p:${client._aid ?? ""}`, seq2, plaintext);
24735
+ await client._publishPulledMessage("message.received", `p2p:${client._aid ?? ""}`, seq2, plaintext, false, "pending_retry");
24440
24736
  }
24441
24737
  client._clientLog.debug(`V2 sender IK pending retry delivered: key=${key}`);
24442
24738
  }
@@ -24513,9 +24809,9 @@ var V2E2EECoordinator = class {
24513
24809
  let plaintext = null;
24514
24810
  if (String(msg.version ?? "") === "v1") {
24515
24811
  const legacy = isJsonObject(msg.legacy_v1) ? msg.legacy_v1 : {};
24516
- const payload = legacy.payload;
24812
+ const payload = legacy.payload !== void 0 ? legacy.payload : msg.payload;
24517
24813
  const payloadType = isJsonObject(payload) ? String(payload.type ?? "").trim() : "";
24518
- if (payload !== void 0 && payload !== null && !["e2ee.encrypted", "e2ee.group_encrypted"].includes(payloadType)) {
24814
+ if (payload !== void 0 && payload !== null && !["e2ee.encrypted", "e2ee.group_encrypted", "e2ee.p2p_encrypted"].includes(payloadType)) {
24519
24815
  plaintext = {
24520
24816
  message_id: String(msg.message_id ?? ""),
24521
24817
  from: String(msg.from_aid ?? ""),
@@ -24527,6 +24823,9 @@ var V2E2EECoordinator = class {
24527
24823
  encrypted: false
24528
24824
  };
24529
24825
  attachGatewayProximity(plaintext, msg);
24826
+ } else if (isJsonObject(payload) && ["e2ee.p2p_encrypted", "e2ee.encrypted"].includes(payloadType)) {
24827
+ msg = { ...msg, envelope_json: JSON.stringify(payload) };
24828
+ plaintext = await client._decryptV2Message(msg, false, !history, true, true, void 0, false, source);
24530
24829
  }
24531
24830
  } else {
24532
24831
  plaintext = source === "inline_push" ? await client._decryptV2Message(msg, false, false, false, false) : await client._decryptV2Message(msg, false);
@@ -24558,29 +24857,53 @@ var V2E2EECoordinator = class {
24558
24857
  let plaintext = null;
24559
24858
  if (String(msg.version ?? "") === "v1") {
24560
24859
  const payload = msg.payload;
24860
+ const recall = typeof client._delivery?.recallEventFromGroupMessage === "function" ? client._delivery.recallEventFromGroupMessage(msg) : null;
24861
+ if (recall) {
24862
+ const recallPayload = {
24863
+ ...recall,
24864
+ group_id: groupId,
24865
+ group_aid: groupAid,
24866
+ seq: seq2
24867
+ };
24868
+ if (publish) {
24869
+ if (orderedPublish) {
24870
+ await client._delivery.publishOrderedMessage(
24871
+ "group.message_recalled",
24872
+ `group:${groupId}`,
24873
+ seq2,
24874
+ recallPayload,
24875
+ source
24876
+ );
24877
+ } else {
24878
+ await client._publishAppEvent("group.message_recalled", recallPayload, source);
24879
+ }
24880
+ }
24881
+ return recallPayload;
24882
+ }
24561
24883
  if (payload === void 0 || payload === null) {
24562
24884
  if (history) return this.historyDecryptFailure(msg, "legacy payload is missing");
24563
24885
  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
24886
  return null;
24565
24887
  }
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;
24888
+ const payloadType = isJsonObject(payload) ? String(payload.type ?? "").trim() : "";
24889
+ if (isJsonObject(payload) && ["e2ee.group_encrypted", "e2ee.p2p_encrypted", "e2ee.encrypted"].includes(payloadType)) {
24890
+ msg = { ...msg, envelope_json: JSON.stringify(payload) };
24891
+ plaintext = await client._decryptV2Message(msg, false, !history, true, true, void 0, false, source);
24892
+ } else {
24893
+ plaintext = {
24894
+ message_id: String(msg.message_id ?? ""),
24895
+ from: String(msg.from_aid ?? ""),
24896
+ group_id: groupId,
24897
+ group_aid: groupAid,
24898
+ seq: seq2,
24899
+ type: String(msg.type ?? ""),
24900
+ timestamp: msg.t_server,
24901
+ payload,
24902
+ encrypted: false
24903
+ };
24904
+ plaintext = normalizeGroupMentionMode(plaintext, msg, msg.envelope_json);
24905
+ attachGatewayProximity(plaintext, msg);
24570
24906
  }
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
24907
  } else {
24585
24908
  plaintext = source === "inline_push" ? await client._decryptV2Message(msg, false, false, false, false) : await client._decryptV2Message(msg, false);
24586
24909
  if (plaintext) {
@@ -24632,6 +24955,7 @@ var V2E2EECoordinator = class {
24632
24955
  const afterSeq = strictWindowSeq(params2.after_seq ?? 0, "after_seq");
24633
24956
  const limit = strictWindowLimit(params2.limit ?? V2_PULL_DEFAULT_PAGE_LIMIT);
24634
24957
  const ns = client._aid ? `p2p:${client._aid}` : "";
24958
+ if (ns) client._delivery.onPullStarted?.(ns);
24635
24959
  const result = await client._callRawV2Rpc("message.v2.pull", { window_mode: "tail", after_seq: afterSeq, limit, _rpc_foreground: true });
24636
24960
  const floor = Number(result.retention_floor_seq ?? 0);
24637
24961
  const previous = ns ? client._seqTracker.getSyncState(ns) : null;
@@ -24654,6 +24978,17 @@ var V2E2EECoordinator = class {
24654
24978
  }
24655
24979
  const ack = ns ? client._seqTracker.getContiguousSeq(ns) : 0;
24656
24980
  if (previous && ack > previous.ack) await this.ackV2(ack);
24981
+ if (ns) client._delivery.onPullPage?.(
24982
+ ns,
24983
+ decoded.length,
24984
+ result.has_more === true,
24985
+ page.head,
24986
+ typeof result.remaining === "number" && Number.isSafeInteger(result.remaining) && result.remaining >= 0 ? result.remaining : null,
24987
+ page.messages.length,
24988
+ ack,
24989
+ page.head
24990
+ );
24991
+ client._delivery.onPullWorkSettled?.();
24657
24992
  return { ...result, messages: decoded, raw_count: page.messages.length };
24658
24993
  }
24659
24994
  async historyV2Internal(params2) {
@@ -24668,10 +25003,24 @@ var V2E2EECoordinator = class {
24668
25003
  }
24669
25004
  return { ...result, messages: decoded };
24670
25005
  }
25006
+ async runPullWithLifecycle(ns, operation) {
25007
+ if (ns) this.client._delivery.onPullStarted?.(ns);
25008
+ try {
25009
+ return await operation();
25010
+ } catch (error) {
25011
+ this.client._delivery.onPullAborted?.();
25012
+ throw error;
25013
+ }
25014
+ }
24671
25015
  async pullV2(afterSeq = 0, limit = 50, opts) {
25016
+ const ns = this.client._aid ? `p2p:${this.client._aid}` : "";
25017
+ return await this.runPullWithLifecycle(ns, () => this.pullV2Impl(afterSeq, limit, opts));
25018
+ }
25019
+ async pullV2Impl(afterSeq = 0, limit = 50, opts) {
24672
25020
  const client = this.client;
24673
25021
  await client._ensureV2SessionReady("message.pull");
24674
25022
  const ns = client._aid ? `p2p:${client._aid}` : "";
25023
+ if (ns) client._delivery.onPullStarted?.(ns);
24675
25024
  if (opts?.windowMode === "tail") {
24676
25025
  if (!opts.gateLocked) {
24677
25026
  const key = pullGateKeyForClient(client, "message.v2.pull", {
@@ -24679,7 +25028,7 @@ var V2E2EECoordinator = class {
24679
25028
  after_seq: afterSeq,
24680
25029
  limit
24681
25030
  }, `${ns}|tail|after=${afterSeq}|limit=${limit}`);
24682
- return await client._runPullSerialized(key, () => this.pullV2(afterSeq, limit, {
25031
+ return await client._runPullSerialized(key, () => this.pullV2Impl(afterSeq, limit, {
24683
25032
  ...opts ?? {},
24684
25033
  gateLocked: true
24685
25034
  }), false);
@@ -24693,7 +25042,7 @@ var V2E2EECoordinator = class {
24693
25042
  force: opts?.force === true,
24694
25043
  limit
24695
25044
  }, `${ns}|forward|after=${afterSeq}|force=${String(Boolean(opts?.force))}|limit=${limit}`);
24696
- return await client._runPullSerialized(key, () => this.pullV2(afterSeq, limit, {
25045
+ return await client._runPullSerialized(key, () => this.pullV2Impl(afterSeq, limit, {
24697
25046
  ...opts ?? {},
24698
25047
  gateLocked: true
24699
25048
  }), true);
@@ -24750,15 +25099,16 @@ var V2E2EECoordinator = class {
24750
25099
  const pageMaxSeq = seqs.length > 0 ? Math.max(...seqs) : nextAfterSeq;
24751
25100
  const deferredKeyFetches = /* @__PURE__ */ new Map();
24752
25101
  let blockedSeq = 0;
25102
+ const pageDecryptedBefore = decrypted.length;
24753
25103
  for (const msg of messages) {
24754
25104
  const seq2 = Number(msg.seq ?? 0);
24755
25105
  if (!Number.isFinite(seq2) || seq2 <= 0) continue;
24756
25106
  const version = String(msg.version ?? "v2");
24757
25107
  if (version === "v1") {
24758
25108
  const legacy = isJsonObject(msg.legacy_v1) ? msg.legacy_v1 : {};
24759
- const legacyPayload = legacy.payload;
25109
+ const legacyPayload = legacy.payload !== void 0 ? legacy.payload : msg.payload;
24760
25110
  const payloadType = isJsonObject(legacyPayload) ? String(legacyPayload.type ?? "").trim() : "";
24761
- if (legacyPayload !== void 0 && legacyPayload !== null && payloadType !== "e2ee.encrypted" && payloadType !== "e2ee.group_encrypted") {
25111
+ if (legacyPayload !== void 0 && legacyPayload !== null && !["e2ee.encrypted", "e2ee.group_encrypted", "e2ee.p2p_encrypted"].includes(payloadType)) {
24762
25112
  const v1Msg = {
24763
25113
  message_id: String(msg.message_id ?? ""),
24764
25114
  from: String(msg.from_aid ?? ""),
@@ -24774,6 +25124,30 @@ var V2E2EECoordinator = class {
24774
25124
  if (ns) await client._publishPulledMessage(appEvent.event, ns, seq2, appEvent.payload, false);
24775
25125
  else await client._publishAppEvent(appEvent.event, appEvent.payload, "pull");
24776
25126
  decrypted.push(v1Msg);
25127
+ } else if (isJsonObject(legacyPayload) && ["e2ee.p2p_encrypted", "e2ee.encrypted"].includes(payloadType)) {
25128
+ const deferStatus2 = {};
25129
+ const plaintext2 = await client._decryptV2Message(
25130
+ { ...msg, envelope_json: JSON.stringify(legacyPayload) },
25131
+ true,
25132
+ true,
25133
+ true,
25134
+ true,
25135
+ deferStatus2,
25136
+ true,
25137
+ "pull"
25138
+ );
25139
+ if (deferStatus2.deferred) {
25140
+ blockedSeq = blockedSeq > 0 ? Math.min(blockedSeq, seq2) : seq2;
25141
+ if (deferStatus2.fromAid) {
25142
+ const key = `${deferStatus2.fromAid}\0${deferStatus2.senderDeviceId ?? ""}\0${deferStatus2.groupId ?? ""}`;
25143
+ deferredKeyFetches.set(key, { fromAid: deferStatus2.fromAid, senderDeviceId: deferStatus2.senderDeviceId ?? "", groupId: deferStatus2.groupId ?? "" });
25144
+ }
25145
+ }
25146
+ if (plaintext2) {
25147
+ if (ns) await client._publishPulledMessage("message.received", ns, seq2, plaintext2, false);
25148
+ else await client._publishAppEvent("message.received", plaintext2, "pull");
25149
+ decrypted.push(plaintext2);
25150
+ }
24777
25151
  } else {
24778
25152
  client._clientLog.debug(`message.v2.pull skipping V1 envelope seq=${seq2} payload_type=${payloadType || "<none>"} (V1 E2EE removed)`);
24779
25153
  }
@@ -24788,7 +25162,7 @@ var V2E2EECoordinator = class {
24788
25162
  client._v2Session.trackOldSPKMaxSeq(msgSpkId, seq2);
24789
25163
  }
24790
25164
  const deferStatus = {};
24791
- const plaintext = await client._decryptV2Message(msg, true, true, true, true, deferStatus, true);
25165
+ const plaintext = await client._decryptV2Message(msg, true, true, true, true, deferStatus, true, "pull");
24792
25166
  if (deferStatus.deferred) {
24793
25167
  blockedSeq = blockedSeq > 0 ? Math.min(blockedSeq, seq2) : seq2;
24794
25168
  }
@@ -24860,7 +25234,7 @@ var V2E2EECoordinator = class {
24860
25234
  if (ackNeeded) {
24861
25235
  this.recordForwardAck(ns, ackSeq);
24862
25236
  const nextAfter2 = Math.max(pageMaxSeq, nextAfterSeq);
24863
- const canContinuePage = messages.length > 0 && nextAfter2 > nextAfterSeq;
25237
+ const canContinuePage = blockedSeq <= 0 && messages.length > 0 && nextAfter2 > nextAfterSeq;
24864
25238
  if (canContinuePage) {
24865
25239
  pendingAckSeq = Math.max(pendingAckSeq, ackSeq);
24866
25240
  lastAutoAckSeq = Math.max(lastAutoAckSeq, ackSeq);
@@ -24870,6 +25244,16 @@ var V2E2EECoordinator = class {
24870
25244
  }
24871
25245
  }
24872
25246
  }
25247
+ if (ns) client._delivery.onPullPage?.(
25248
+ ns,
25249
+ decrypted.length - pageDecryptedBefore,
25250
+ hasMore,
25251
+ pageMaxSeq,
25252
+ typeof result.remaining === "number" && Number.isSafeInteger(result.remaining) && result.remaining >= 0 ? result.remaining : null,
25253
+ messages.length,
25254
+ client._seqTracker.getContiguousSeq(ns),
25255
+ pageMaxSeq
25256
+ );
24873
25257
  const nextAfter = Math.max(pageMaxSeq, nextAfterSeq);
24874
25258
  const rawCount = messages.length;
24875
25259
  if (blockedSeq > 0) break;
@@ -24904,6 +25288,7 @@ var V2E2EECoordinator = class {
24904
25288
  pendingAckSeq = 0;
24905
25289
  }
24906
25290
  }
25291
+ client._delivery.onPullWorkSettled?.();
24907
25292
  return decrypted;
24908
25293
  }
24909
25294
  async confirmForwardP2PAck(upToSeq, inlineGeneration) {
@@ -25041,6 +25426,11 @@ var V2E2EECoordinator = class {
25041
25426
  const afterSeq = strictWindowSeq(params2.after_seq ?? 0, "after_seq");
25042
25427
  const limit = strictWindowLimit(params2.limit ?? V2_PULL_DEFAULT_PAGE_LIMIT);
25043
25428
  const ns = `group:${groupId}`;
25429
+ const cursorParams = isJsonObject(params2._group_cursor_params) ? params2._group_cursor_params : params2;
25430
+ const requestDeviceId = String(cursorParams.device_id ?? "").trim();
25431
+ const requestSlotId = String(cursorParams.slot_id ?? "").trim();
25432
+ const ownsCursor = (!requestDeviceId || requestDeviceId === String(client._deviceId ?? "")) && (!requestSlotId || requestSlotId === String(client._slotId ?? ""));
25433
+ if (ownsCursor) client._delivery.onPullStarted?.(ns);
25044
25434
  const result = await client._callRawV2Rpc("group.v2.pull", withExplicitGroupAid({
25045
25435
  group_id: groupId,
25046
25436
  window_mode: "tail",
@@ -25070,6 +25460,17 @@ var V2E2EECoordinator = class {
25070
25460
  }
25071
25461
  const ack = client._seqTracker.getContiguousSeq(ns);
25072
25462
  if (ack > previous.ack) await this.ackGroupV2(groupId, ack, groupAid);
25463
+ if (ownsCursor) client._delivery.onPullPage?.(
25464
+ ns,
25465
+ decoded.length,
25466
+ result.has_more === true,
25467
+ page.head,
25468
+ typeof result.remaining === "number" && Number.isSafeInteger(result.remaining) && result.remaining >= 0 ? result.remaining : null,
25469
+ page.messages.length,
25470
+ ack,
25471
+ page.head
25472
+ );
25473
+ client._delivery.onPullWorkSettled?.();
25073
25474
  return { ...result, messages: decoded, raw_count: page.messages.length };
25074
25475
  }
25075
25476
  async historyGroupV2Internal(params2) {
@@ -25102,6 +25503,13 @@ var V2E2EECoordinator = class {
25102
25503
  });
25103
25504
  }
25104
25505
  async pullGroupV2(groupId, afterSeq = 0, limit = 50, opts) {
25506
+ const lifecycleNs = `group:${String(groupId ?? "").trim()}`;
25507
+ if (opts?.ownsCursor === false) {
25508
+ return await this.pullGroupV2Impl(groupId, afterSeq, limit, opts);
25509
+ }
25510
+ return await this.runPullWithLifecycle(lifecycleNs, () => this.pullGroupV2Impl(groupId, afterSeq, limit, opts));
25511
+ }
25512
+ async pullGroupV2Impl(groupId, afterSeq = 0, limit = 50, opts) {
25105
25513
  const client = this.client;
25106
25514
  await client._ensureV2SessionReady("group.pull");
25107
25515
  const gid = String(groupId ?? "").trim();
@@ -25117,12 +25525,13 @@ var V2E2EECoordinator = class {
25117
25525
  limit,
25118
25526
  _group_cursor_params: opts?.cursorParams
25119
25527
  }, `${ns}|tail|after=${afterSeq}|limit=${limit}`);
25120
- return await client._runPullSerialized(key, () => this.pullGroupV2(gid, afterSeq, limit, {
25528
+ return await client._runPullSerialized(key, () => this.pullGroupV2Impl(gid, afterSeq, limit, {
25121
25529
  ...opts ?? {},
25122
25530
  gateLocked: true
25123
25531
  }), false);
25124
25532
  }
25125
25533
  const result = await this.pullGroupV2TailInternal({
25534
+ ...opts?.cursorParams ?? {},
25126
25535
  group_id: String(opts.wireGroupId ?? gid),
25127
25536
  group_aid: groupAid || void 0,
25128
25537
  window_mode: "tail",
@@ -25139,7 +25548,7 @@ var V2E2EECoordinator = class {
25139
25548
  limit,
25140
25549
  _group_cursor_params: opts?.cursorParams
25141
25550
  }, `${ns}|forward|after=${afterSeq}|force=${String(Boolean(opts?.force))}|limit=${limit}`);
25142
- return await client._runPullSerialized(key, () => this.pullGroupV2(gid, afterSeq, limit, {
25551
+ return await client._runPullSerialized(key, () => this.pullGroupV2Impl(gid, afterSeq, limit, {
25143
25552
  ...opts ?? {},
25144
25553
  gateLocked: true
25145
25554
  }), true);
@@ -25148,6 +25557,7 @@ var V2E2EECoordinator = class {
25148
25557
  const wireGroupId = String(opts?.wireGroupId ?? groupId ?? "").trim() || gid;
25149
25558
  const cursorParams = opts?.cursorParams ?? {};
25150
25559
  const ownsCursor = opts?.ownsCursor !== false;
25560
+ if (ownsCursor) client._delivery.onPullStarted?.(ns);
25151
25561
  let pullGateKey = ownsCursor ? pullGateKeyForClient(client, "group.v2.pull", {
25152
25562
  group_id: gid,
25153
25563
  after_seq: afterSeq,
@@ -25208,26 +25618,28 @@ var V2E2EECoordinator = class {
25208
25618
  const pageMaxSeq = seqs.length > 0 ? Math.max(...seqs) : nextAfterSeq;
25209
25619
  const deferredKeyFetches = /* @__PURE__ */ new Map();
25210
25620
  let blockedSeq = 0;
25621
+ const pageDecryptedBefore = decrypted.length;
25211
25622
  for (const msg of messages) {
25212
25623
  const seq2 = Number(msg.seq ?? 0);
25213
25624
  if (!Number.isFinite(seq2) || seq2 <= 0) continue;
25214
25625
  const version = String(msg.version ?? "v2");
25215
25626
  if (version === "v1") {
25216
- const payload = msg.payload;
25627
+ const legacy = isJsonObject(msg.legacy_v1) ? msg.legacy_v1 : {};
25628
+ const payload = msg.payload !== void 0 ? msg.payload : legacy.payload;
25217
25629
  const payloadObj = isJsonObject(payload) ? payload : null;
25218
- if (client._delivery.recallEventFromGroupMessage(msg)) {
25630
+ if (typeof client._delivery?.recallEventFromGroupMessage === "function" && client._delivery.recallEventFromGroupMessage(msg)) {
25219
25631
  await client._delivery.publishGroupRecallTombstone(gid, seq2, {
25220
25632
  ...msg,
25221
25633
  group_id: gid,
25222
25634
  group_aid: eventGroupAid
25223
- });
25635
+ }, "pull");
25224
25636
  client._markPublishedSeq(ns, seq2);
25225
25637
  client._clientLog.debug(`group.v2.pull recall tombstone delivered: group=${gid}, seq=${seq2}`);
25226
25638
  continue;
25227
25639
  }
25228
25640
  if (payloadObj) {
25229
25641
  const payloadType = String(payloadObj.type ?? "").trim();
25230
- if (payloadType !== "e2ee.encrypted" && payloadType !== "e2ee.group_encrypted") {
25642
+ if (payloadType !== "e2ee.encrypted" && payloadType !== "e2ee.group_encrypted" && payloadType !== "e2ee.p2p_encrypted") {
25231
25643
  let v1Msg = {
25232
25644
  message_id: String(msg.message_id ?? ""),
25233
25645
  from: String(msg.from_aid ?? ""),
@@ -25263,6 +25675,32 @@ var V2E2EECoordinator = class {
25263
25675
  decrypted.push(v1Msg);
25264
25676
  continue;
25265
25677
  }
25678
+ if (payloadObj && ["e2ee.group_encrypted", "e2ee.p2p_encrypted", "e2ee.encrypted"].includes(String(payloadObj.type ?? "").trim())) {
25679
+ const deferStatus2 = {};
25680
+ const plaintext2 = await client._decryptV2Message(
25681
+ { ...msg, envelope_json: JSON.stringify(payloadObj) },
25682
+ true,
25683
+ true,
25684
+ true,
25685
+ true,
25686
+ deferStatus2,
25687
+ true,
25688
+ "pull"
25689
+ );
25690
+ if (deferStatus2.deferred) {
25691
+ blockedSeq = blockedSeq > 0 ? Math.min(blockedSeq, seq2) : seq2;
25692
+ if (deferStatus2.fromAid) {
25693
+ const key = `${deferStatus2.fromAid}\0${deferStatus2.senderDeviceId ?? ""}\0${deferStatus2.groupId ?? ""}`;
25694
+ deferredKeyFetches.set(key, { fromAid: deferStatus2.fromAid, senderDeviceId: deferStatus2.senderDeviceId ?? "", groupId: deferStatus2.groupId ?? "" });
25695
+ }
25696
+ }
25697
+ if (plaintext2) {
25698
+ plaintext2.group_id = gid;
25699
+ plaintext2.group_aid = eventGroupAid;
25700
+ await client._publishPulledMessage("group.message_created", ns, seq2, plaintext2, false);
25701
+ decrypted.push(plaintext2);
25702
+ }
25703
+ }
25266
25704
  client._clientLog.debug(`group.v2.pull skipping V1 envelope group=${gid} seq=${seq2} payload_type=${payloadObj ? String(payloadObj.type ?? "") : "<none>"} (V1 E2EE removed)`);
25267
25705
  continue;
25268
25706
  }
@@ -25271,7 +25709,7 @@ var V2E2EECoordinator = class {
25271
25709
  continue;
25272
25710
  }
25273
25711
  const deferStatus = {};
25274
- let plaintext = await client._decryptV2Message(msg, true, true, true, true, deferStatus, true);
25712
+ let plaintext = await client._decryptV2Message(msg, true, true, true, true, deferStatus, true, "pull");
25275
25713
  if (deferStatus.deferred) {
25276
25714
  blockedSeq = blockedSeq > 0 ? Math.min(blockedSeq, seq2) : seq2;
25277
25715
  }
@@ -25343,6 +25781,16 @@ var V2E2EECoordinator = class {
25343
25781
  for (const fetch2 of deferredKeyFetches.values()) {
25344
25782
  this.scheduleSenderIKFetch(fetch2.fromAid, fetch2.senderDeviceId, fetch2.groupId);
25345
25783
  }
25784
+ client._delivery.onPullPage?.(
25785
+ ns,
25786
+ decrypted.length - pageDecryptedBefore,
25787
+ hasMore,
25788
+ pageMaxSeq,
25789
+ typeof result.remaining === "number" && Number.isSafeInteger(result.remaining) && result.remaining >= 0 ? result.remaining : null,
25790
+ messages.length,
25791
+ client._seqTracker.getContiguousSeq(ns),
25792
+ pageMaxSeq
25793
+ );
25346
25794
  if (ownsCursor && parsedCursorCurrentSeq !== null && cursorCurrentSeq > ackSeq) {
25347
25795
  this.recordForwardCursor(ns, cursorCurrentSeq, ackSeq);
25348
25796
  }
@@ -25351,7 +25799,7 @@ var V2E2EECoordinator = class {
25351
25799
  if (ackNeeded) {
25352
25800
  this.recordForwardAck(ns, ackSeq);
25353
25801
  const nextAfter2 = Math.max(pageMaxSeq, nextAfterSeq);
25354
- const canContinuePage = messages.length > 0 && nextAfter2 > nextAfterSeq && ownsCursor;
25802
+ const canContinuePage = blockedSeq <= 0 && messages.length > 0 && nextAfter2 > nextAfterSeq && ownsCursor;
25355
25803
  if (canContinuePage) {
25356
25804
  pendingAckSeq = Math.max(pendingAckSeq, ackSeq);
25357
25805
  lastAutoAckSeq = Math.max(lastAutoAckSeq, ackSeq);
@@ -25397,6 +25845,7 @@ var V2E2EECoordinator = class {
25397
25845
  pendingAckSeq = 0;
25398
25846
  }
25399
25847
  }
25848
+ client._delivery.onPullWorkSettled?.();
25400
25849
  return decrypted;
25401
25850
  }
25402
25851
  async confirmForwardGroupAck(groupId, upToSeq, groupAid) {
@@ -25906,7 +26355,7 @@ var V2E2EECoordinator = class {
25906
26355
  }
25907
26356
  async decryptV2PushMessage(data) {
25908
26357
  if (!isJsonObject(data)) return null;
25909
- return await this.client._decryptV2Message(data);
26358
+ return await this.client._decryptV2Message(data, false, false, false, false, void 0, false, "inline_push");
25910
26359
  }
25911
26360
  async buildV2P2PEnvelope(opts) {
25912
26361
  const client = this.client;
@@ -29952,6 +30401,53 @@ var _AUNClient = class _AUNClient {
29952
30401
  await this._agentMdManager.upload(await this._agentMdManager.readContent(target) ?? buildDefaultAgentMd(target));
29953
30402
  return true;
29954
30403
  }
30404
+ async _checkIdentityAdmissionAfterConnect() {
30405
+ const aid = String(this._aid ?? this._currentAid?.aid ?? "").trim();
30406
+ if (!aid) return;
30407
+ try {
30408
+ const checked = await this._agentMdManager.check(aid);
30409
+ if (!checked.remote_found) {
30410
+ const content = await this._agentMdManager.readContent(aid) ?? buildDefaultAgentMd(aid);
30411
+ await this._agentMdManager.upload(content);
30412
+ }
30413
+ } catch (exc) {
30414
+ this._clientLog.warn(`post-connect agent.md check failed: ${String(exc)}`);
30415
+ }
30416
+ const store = this._aidStore;
30417
+ if (!store) return;
30418
+ let listed;
30419
+ try {
30420
+ listed = await this.call("group.list_my", {});
30421
+ } catch (exc) {
30422
+ this._clientLog.warn(`post-connect group identity check failed: ${String(exc)}`);
30423
+ return;
30424
+ }
30425
+ const result = isJsonObject(listed) ? listed : {};
30426
+ const groups = Array.isArray(result.groups) ? result.groups : Array.isArray(result.items) ? result.items : [];
30427
+ for (const raw of groups) {
30428
+ if (!isJsonObject(raw)) continue;
30429
+ const role = String(raw.role ?? raw.my_role ?? "").trim().toLowerCase();
30430
+ if (role !== "owner") continue;
30431
+ const groupId = String(raw.group_id ?? raw.groupId ?? raw.group_aid ?? raw.groupAid ?? "").trim();
30432
+ const groupAid = String(raw.group_aid ?? raw.groupAid ?? groupId).trim();
30433
+ if (!groupId || !groupAid) continue;
30434
+ try {
30435
+ await this._runGroupIdentityOperation(groupId, async () => {
30436
+ const loaded = await store.load(groupAid);
30437
+ if (!loaded.ok || !loaded.data?.aid?.isPrivateKeyValid()) {
30438
+ await this.bindGroupAid({ group_id: groupId, group_aid: groupAid }, { aidStore: store });
30439
+ return;
30440
+ }
30441
+ const checked = await this._agentMdManager.check(groupAid);
30442
+ if (!checked.remote_found) {
30443
+ await this._uploadGroupAgentMd(store, groupAid, {}, {}, aid);
30444
+ }
30445
+ });
30446
+ } catch (exc) {
30447
+ this._clientLog.warn(`post-connect group identity check failed: group=${groupId} err=${String(exc)}`);
30448
+ }
30449
+ }
30450
+ }
29955
30451
  async _runGroupIdentityOperation(groupId, operation) {
29956
30452
  const aid = String(this._aid ?? this._currentAid?.aid ?? "").trim();
29957
30453
  const dot = aid.indexOf(".");
@@ -30585,7 +31081,7 @@ var _AUNClient = class _AUNClient {
30585
31081
  _markPublishedSeq(ns, seq2) {
30586
31082
  this._delivery.markPublishedSeq(ns, seq2);
30587
31083
  }
30588
- async _publishAppEvent(event, payload, source = "", ns = "", batch) {
31084
+ async _publishAppEvent(event, payload, source = "direct", ns = "", batch) {
30589
31085
  await this._delivery.publishAppEvent(event, payload, source, ns, batch);
30590
31086
  }
30591
31087
  _echoTimestamp() {
@@ -30983,6 +31479,7 @@ var _AUNClient = class _AUNClient {
30983
31479
  if (backgroundSyncEnabled) {
30984
31480
  this._safeAsync(this._fillP2pGap());
30985
31481
  }
31482
+ this._safeAsync(this._checkIdentityAdmissionAfterConnect());
30986
31483
  this._clientLog.debug(`_connectOnce exit: elapsed=${Date.now() - tStart}ms aid=${this._aid ?? "-"}`);
30987
31484
  } catch (err) {
30988
31485
  this._clientLog.debug(`_connectOnce exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
@@ -31869,7 +32366,7 @@ var _AUNClient = class _AUNClient {
31869
32366
  * undecryptable 事件、成功消费后触发 SPK 轮换。inline Push 在游标提交和
31870
32367
  * Head/正文绑定校验前调用本入口时必须显式关闭这些副作用。
31871
32368
  */
31872
- async _decryptV2Message(msg, allowPending = true, emitUndecryptable = true, rotateKeys = true, observeAgentMd = true, deferStatus, deferKeyFetch = false) {
32369
+ async _decryptV2Message(msg, allowPending = true, emitUndecryptable = true, rotateKeys = true, observeAgentMd = true, deferStatus, deferKeyFetch = false, source = "pull") {
31873
32370
  const session = this._v2Session;
31874
32371
  if (!session) return null;
31875
32372
  const envJson = msg.envelope_json;
@@ -31957,7 +32454,8 @@ var _AUNClient = class _AUNClient {
31957
32454
  _decrypt_stage: "spk_lookup",
31958
32455
  _envelope_type: String(envelope.type ?? ""),
31959
32456
  _suite: String(envelope.suite ?? ""),
31960
- _spk_id: spkId
32457
+ _spk_id: spkId,
32458
+ source
31961
32459
  };
31962
32460
  attachV2EnvelopeMetadata2(event, e2eeMeta);
31963
32461
  this._dispatcher.enqueue(undecryptableEvent, event);
@@ -31998,7 +32496,8 @@ var _AUNClient = class _AUNClient {
31998
32496
  _decrypt_error: "sender_ik_not_found",
31999
32497
  _decrypt_stage: "sender_ik",
32000
32498
  _envelope_type: String(envelope.type ?? ""),
32001
- _suite: String(envelope.suite ?? "")
32499
+ _suite: String(envelope.suite ?? ""),
32500
+ source
32002
32501
  };
32003
32502
  attachV2EnvelopeMetadata2(event, e2eeMeta);
32004
32503
  this._dispatcher.enqueue(undecryptableEvent, event);
@@ -32034,7 +32533,8 @@ var _AUNClient = class _AUNClient {
32034
32533
  _decrypt_error: String(exc),
32035
32534
  _decrypt_stage: "decrypt",
32036
32535
  _envelope_type: String(envelope.type ?? ""),
32037
- _suite: String(envelope.suite ?? "")
32536
+ _suite: String(envelope.suite ?? ""),
32537
+ source
32038
32538
  };
32039
32539
  attachV2EnvelopeMetadata2(event, e2eeMeta);
32040
32540
  this._dispatcher.enqueue(undecryptableEvent, event);