@agentunion/fastaun-browser 0.5.9 → 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.
Files changed (54) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/_packed_docs/CHANGELOG.md +64 -0
  3. package/_packed_docs/INDEX.md +3 -3
  4. package/_packed_docs/KITE_DOCS_GUIDE.md +1 -1
  5. package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +6 -5
  6. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +102 -8
  7. package/_packed_docs/sdk/09-group-rpc-manual.md +18 -3
  8. package/_packed_docs/sdk/09-message-rpc-manual.md +34 -10
  9. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +3 -2
  10. package/_packed_docs/sdk/INDEX.md +2 -1
  11. package/dist/agent-md.d.ts.map +1 -1
  12. package/dist/agent-md.js +23 -1
  13. package/dist/agent-md.js.map +1 -1
  14. package/dist/auth.d.ts.map +1 -1
  15. package/dist/auth.js +16 -2
  16. package/dist/auth.js.map +1 -1
  17. package/dist/bundle.js +1374 -267
  18. package/dist/client/delivery.d.ts +34 -11
  19. package/dist/client/delivery.d.ts.map +1 -1
  20. package/dist/client/delivery.js +390 -100
  21. package/dist/client/delivery.js.map +1 -1
  22. package/dist/client/group-state.js +6 -6
  23. package/dist/client/group-state.js.map +1 -1
  24. package/dist/client/lifecycle.d.ts.map +1 -1
  25. package/dist/client/lifecycle.js +34 -38
  26. package/dist/client/lifecycle.js.map +1 -1
  27. package/dist/client/rpc-pipeline.d.ts +4 -0
  28. package/dist/client/rpc-pipeline.d.ts.map +1 -1
  29. package/dist/client/rpc-pipeline.js +70 -4
  30. package/dist/client/rpc-pipeline.js.map +1 -1
  31. package/dist/client/v2-e2ee.d.ts +4 -1
  32. package/dist/client/v2-e2ee.d.ts.map +1 -1
  33. package/dist/client/v2-e2ee.js +169 -38
  34. package/dist/client/v2-e2ee.js.map +1 -1
  35. package/dist/client.d.ts +1 -1
  36. package/dist/client.d.ts.map +1 -1
  37. package/dist/client.js +83 -33
  38. package/dist/client.js.map +1 -1
  39. package/dist/events.d.ts +31 -2
  40. package/dist/events.d.ts.map +1 -1
  41. package/dist/events.js +167 -7
  42. package/dist/events.js.map +1 -1
  43. package/dist/register-flow.d.ts.map +1 -1
  44. package/dist/register-flow.js +16 -2
  45. package/dist/register-flow.js.map +1 -1
  46. package/dist/transport.d.ts +10 -1
  47. package/dist/transport.d.ts.map +1 -1
  48. package/dist/transport.js +427 -29
  49. package/dist/transport.js.map +1 -1
  50. package/dist/version.d.ts +1 -1
  51. package/dist/version.d.ts.map +1 -1
  52. package/dist/version.js +1 -1
  53. package/dist/version.js.map +1 -1
  54. 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.9";
463
+ var VERSION = "0.5.11";
464
464
 
465
465
  // src/types.ts
466
466
  var ConnectionState = /* @__PURE__ */ ((ConnectionState2) => {
@@ -799,6 +799,15 @@ var EventDispatcher = class {
799
799
  constructor() {
800
800
  __publicField(this, "_log", _noopLog);
801
801
  __publicField(this, "_handlers", /* @__PURE__ */ new Map());
802
+ __publicField(this, "_queue", []);
803
+ __publicField(this, "_draining", false);
804
+ __publicField(this, "_drainScheduled", false);
805
+ __publicField(this, "_handlerDepth", 0);
806
+ __publicField(this, "_synchronousHandlerDepth", 0);
807
+ __publicField(this, "_drainPromise", null);
808
+ __publicField(this, "_drainResolve", null);
809
+ __publicField(this, "_closing", false);
810
+ __publicField(this, "_closed", false);
802
811
  }
803
812
  setLogger(log) {
804
813
  this._log = log;
@@ -827,17 +836,154 @@ var EventDispatcher = class {
827
836
  this._handlers.delete(event);
828
837
  }
829
838
  }
830
- /** 发布事件(依次调用所有处理函数,支持异步) */
839
+ /**
840
+ * 发布事件。事件总是异步进入 FIFO 队列;调用方 await 时等待该事件处理完成。
841
+ * drain 中派生事件只入队,不等待自身,避免事件处理器互相等待形成死锁。
842
+ */
831
843
  async publish(event, payload) {
844
+ if (this._closed || this._closing) return;
845
+ let resolveItem;
846
+ const itemDone = new Promise((resolve) => {
847
+ resolveItem = resolve;
848
+ });
849
+ this._queue.push({
850
+ run: () => this.dispatchNow(event, payload),
851
+ resolve: resolveItem
852
+ });
853
+ if (this._handlerDepth > 0) return;
854
+ if (this._drainPromise === null) {
855
+ this._drainPromise = new Promise((resolve) => {
856
+ this._drainResolve = resolve;
857
+ });
858
+ }
859
+ this.scheduleDrain();
860
+ await itemDone;
861
+ }
862
+ /** 将事件放入异步 FIFO 队列,不等待处理器执行完成。 */
863
+ enqueue(event, payload) {
864
+ void this.publish(event, payload).catch((exc) => {
865
+ this._log.warn(`event ${event} enqueue failed:`, exc);
866
+ });
867
+ }
868
+ /** 将非事件 observer 放入同一 FIFO 队列。 */
869
+ enqueueTask(task) {
870
+ if (this._closed || this._closing) return;
871
+ this._queue.push({
872
+ run: () => this.dispatchTask(task),
873
+ resolve: () => {
874
+ }
875
+ });
876
+ if (this._drainPromise === null) {
877
+ this._drainPromise = new Promise((resolve) => {
878
+ this._drainResolve = resolve;
879
+ });
880
+ }
881
+ this.scheduleDrain();
882
+ }
883
+ /** 关闭调度器,排空已经入队的应用事件后拒绝后续事件。 */
884
+ async close() {
885
+ if (this._closed) return;
886
+ this._closing = true;
887
+ if (this._synchronousHandlerDepth > 0) return;
888
+ if (this._drainPromise !== null) await this._drainPromise;
889
+ this.finishClose();
890
+ }
891
+ /** 等待当前队列排空;事件 handler 重入时直接返回,避免自等待。 */
892
+ async flush() {
893
+ if (this._synchronousHandlerDepth > 0) return;
894
+ if (this._drainPromise !== null) await this._drainPromise;
895
+ await Promise.resolve();
896
+ }
897
+ finishClose() {
898
+ if (!this._closing || this._closed || this._draining || this._drainScheduled || this._queue.length > 0) return;
899
+ this._closed = true;
900
+ this._handlers.clear();
901
+ }
902
+ scheduleDrain() {
903
+ if (this._draining || this._drainScheduled || this._queue.length === 0) return;
904
+ this._drainScheduled = true;
905
+ queueMicrotask(() => {
906
+ this._drainScheduled = false;
907
+ void this._drain();
908
+ });
909
+ }
910
+ async _drain() {
911
+ if (this._draining) return;
912
+ this._draining = true;
913
+ try {
914
+ while (this._queue.length > 0) {
915
+ const item = this._queue.shift();
916
+ try {
917
+ await item.run();
918
+ } catch (exc) {
919
+ this._log.warn("event dispatch failed:", exc);
920
+ } finally {
921
+ item.resolve();
922
+ }
923
+ }
924
+ } finally {
925
+ this._draining = false;
926
+ const resolve = this._drainResolve;
927
+ this._drainResolve = null;
928
+ this._drainPromise = null;
929
+ resolve?.();
930
+ this.finishClose();
931
+ if (this._queue.length > 0 && !this._closed) {
932
+ if (this._drainPromise === null) {
933
+ this._drainPromise = new Promise((nextResolve) => {
934
+ this._drainResolve = nextResolve;
935
+ });
936
+ }
937
+ this.scheduleDrain();
938
+ }
939
+ }
940
+ }
941
+ /** 当前是否正在调用应用 handler 或 observer。供生命周期入口识别重入。 */
942
+ isDispatchingHandler() {
943
+ return this._handlerDepth > 0;
944
+ }
945
+ async dispatchTask(task) {
946
+ let result;
947
+ this._handlerDepth += 1;
948
+ try {
949
+ this._synchronousHandlerDepth += 1;
950
+ result = task();
951
+ } catch (exc) {
952
+ this._log.warn("event task execution exception:", exc);
953
+ this._handlerDepth -= 1;
954
+ return;
955
+ } finally {
956
+ this._synchronousHandlerDepth -= 1;
957
+ }
958
+ try {
959
+ await result;
960
+ } catch (exc) {
961
+ this._log.warn("event task execution exception:", exc);
962
+ } finally {
963
+ this._handlerDepth -= 1;
964
+ }
965
+ }
966
+ async dispatchNow(event, payload) {
832
967
  const handlers = [...this._handlers.get(event) ?? []];
833
968
  for (const handler of handlers) {
969
+ let result;
970
+ this._handlerDepth += 1;
834
971
  try {
835
- const result = handler(payload);
836
- if (result instanceof Promise) {
837
- await result;
838
- }
972
+ this._synchronousHandlerDepth += 1;
973
+ result = handler(payload);
839
974
  } catch (exc) {
840
975
  this._log.warn(`event ${event} handler execution exception:`, exc);
976
+ this._handlerDepth -= 1;
977
+ continue;
978
+ } finally {
979
+ this._synchronousHandlerDepth -= 1;
980
+ }
981
+ try {
982
+ await result;
983
+ } catch (exc) {
984
+ this._log.warn(`event ${event} handler execution exception:`, exc);
985
+ } finally {
986
+ this._handlerDepth -= 1;
841
987
  }
842
988
  }
843
989
  }
@@ -1150,12 +1296,293 @@ var GatewayDiscovery = class {
1150
1296
  var MAX_WS_PAYLOAD_SIZE = 1e6;
1151
1297
  var MAX_RPC_INFLIGHT = 16;
1152
1298
  var MAX_BACKGROUND_RPC_INFLIGHT = 8;
1299
+ var WORKER_START_TIMEOUT_MS = 2e3;
1300
+ var WORKER_OPEN_TIMEOUT_MS = 2e3;
1153
1301
  var _noopLog3 = { error: () => {
1154
1302
  }, warn: () => {
1155
1303
  }, info: () => {
1156
1304
  }, debug: () => {
1157
1305
  } };
1158
1306
  var _rpcIdCounter = 0;
1307
+ var WORKER_WEBSOCKET_SOURCE = `
1308
+ let socket = null;
1309
+ self.postMessage({ type: 'ready' });
1310
+ self.onmessage = (event) => {
1311
+ const command = event.data || {};
1312
+ if (command.type === 'connect') {
1313
+ try {
1314
+ socket = new WebSocket(command.url);
1315
+ socket.onopen = () => self.postMessage({ type: 'open' });
1316
+ socket.onmessage = (message) => self.postMessage({ type: 'message', data: message.data });
1317
+ socket.onerror = () => self.postMessage({ type: 'error', message: 'websocket error' });
1318
+ socket.onclose = (close) => self.postMessage({
1319
+ type: 'close', code: close.code, reason: close.reason || '', wasClean: close.wasClean === true,
1320
+ });
1321
+ } catch (error) {
1322
+ self.postMessage({ type: 'error', message: error instanceof Error ? error.message : String(error) });
1323
+ }
1324
+ return;
1325
+ }
1326
+ if (command.type === 'send') {
1327
+ try {
1328
+ if (!socket || socket.readyState !== WebSocket.OPEN) throw new Error('websocket is not open');
1329
+ socket.send(command.data);
1330
+ self.postMessage({ type: 'send_result', id: command.id, ok: true });
1331
+ } catch (error) {
1332
+ self.postMessage({
1333
+ type: 'send_result', id: command.id, ok: false,
1334
+ error: error instanceof Error ? error.message : String(error),
1335
+ });
1336
+ }
1337
+ return;
1338
+ }
1339
+ if (command.type === 'close' && socket) socket.close(command.code, command.reason);
1340
+ };
1341
+ `;
1342
+ var _WorkerWebSocketProxy = class _WorkerWebSocketProxy {
1343
+ constructor(url) {
1344
+ __publicField(this, "readyState", _WorkerWebSocketProxy.CONNECTING);
1345
+ __publicField(this, "onopen", null);
1346
+ __publicField(this, "onmessage", null);
1347
+ __publicField(this, "onerror", null);
1348
+ __publicField(this, "onclose", null);
1349
+ __publicField(this, "_url");
1350
+ __publicField(this, "_worker");
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);
1358
+ __publicField(this, "_listeners", /* @__PURE__ */ new Map());
1359
+ __publicField(this, "_pendingSends", /* @__PURE__ */ new Map());
1360
+ __publicField(this, "_sendSeq", 0);
1361
+ this._url = url;
1362
+ this._workerUrl = URL.createObjectURL(new Blob([WORKER_WEBSOCKET_SOURCE], { type: "text/javascript" }));
1363
+ try {
1364
+ this._worker = new Worker(this._workerUrl);
1365
+ } catch (error) {
1366
+ URL.revokeObjectURL(this._workerUrl);
1367
+ throw error;
1368
+ }
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);
1380
+ }
1381
+ send(data) {
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"));
1393
+ const id = ++this._sendSeq;
1394
+ return new Promise((resolve, reject) => {
1395
+ this._pendingSends.set(id, { resolve, reject });
1396
+ try {
1397
+ worker.postMessage({ type: "send", id, data });
1398
+ } catch (error) {
1399
+ this._pendingSends.delete(id);
1400
+ reject(error instanceof Error ? error : new Error(String(error)));
1401
+ }
1402
+ });
1403
+ }
1404
+ close(code, reason) {
1405
+ if (this.readyState === _WorkerWebSocketProxy.CLOSED) return;
1406
+ this.readyState = _WorkerWebSocketProxy.CLOSING;
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
+ }
1425
+ }
1426
+ addEventListener(type, listener) {
1427
+ const callback = typeof listener === "function" ? listener : (event) => listener.handleEvent(event);
1428
+ const listeners = this._listeners.get(type) ?? /* @__PURE__ */ new Set();
1429
+ listeners.add(callback);
1430
+ this._listeners.set(type, listeners);
1431
+ }
1432
+ removeEventListener(type, listener) {
1433
+ if (typeof listener === "function") this._listeners.get(type)?.delete(listener);
1434
+ }
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
+ }
1451
+ if (reply.type === "send_result" && reply.id !== void 0) {
1452
+ const pending = this._pendingSends.get(reply.id);
1453
+ if (!pending) return;
1454
+ this._pendingSends.delete(reply.id);
1455
+ if (reply.ok) pending.resolve();
1456
+ else pending.reject(new Error(reply.error || "websocket send failed"));
1457
+ return;
1458
+ }
1459
+ if (reply.type === "open") {
1460
+ this._clearWorkerOpenTimer();
1461
+ this._opened = true;
1462
+ this.readyState = _WorkerWebSocketProxy.OPEN;
1463
+ const event = new Event("open");
1464
+ this.onopen?.(event);
1465
+ this._emit("open", event);
1466
+ return;
1467
+ }
1468
+ if (reply.type === "message") {
1469
+ const event = new MessageEvent("message", { data: reply.data });
1470
+ this.onmessage?.(event);
1471
+ this._emit("message", event);
1472
+ return;
1473
+ }
1474
+ if (reply.type === "error") {
1475
+ if (!this._opened && this.readyState === _WorkerWebSocketProxy.CONNECTING) {
1476
+ this._fallbackToNative();
1477
+ return;
1478
+ }
1479
+ this._emitError(reply.message || "websocket worker error");
1480
+ return;
1481
+ }
1482
+ if (reply.type === "close") {
1483
+ if (!this._opened && this.readyState === _WorkerWebSocketProxy.CONNECTING) {
1484
+ this._fallbackToNative();
1485
+ return;
1486
+ }
1487
+ this._emitClose(new CloseEvent("close", {
1488
+ code: reply.code ?? 1006,
1489
+ reason: reply.reason ?? "",
1490
+ wasClean: reply.wasClean === true
1491
+ }));
1492
+ }
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
+ }
1563
+ _emitError(message) {
1564
+ const event = new ErrorEvent("error", { message });
1565
+ this.onerror?.(event);
1566
+ this._emit("error", event);
1567
+ }
1568
+ _emit(type, event) {
1569
+ for (const listener of [...this._listeners.get(type) ?? []]) listener(event);
1570
+ }
1571
+ };
1572
+ __publicField(_WorkerWebSocketProxy, "CONNECTING", 0);
1573
+ __publicField(_WorkerWebSocketProxy, "OPEN", 1);
1574
+ __publicField(_WorkerWebSocketProxy, "CLOSING", 2);
1575
+ __publicField(_WorkerWebSocketProxy, "CLOSED", 3);
1576
+ var WorkerWebSocketProxy = _WorkerWebSocketProxy;
1577
+ function createTransportWebSocket(url) {
1578
+ if (typeof Worker === "function" && typeof Blob === "function" && typeof URL !== "undefined" && typeof URL.createObjectURL === "function" && typeof URL.revokeObjectURL === "function") {
1579
+ try {
1580
+ return new WorkerWebSocketProxy(url);
1581
+ } catch {
1582
+ }
1583
+ }
1584
+ return new WebSocket(url);
1585
+ }
1159
1586
  var TRACE_SPAN_DETAIL_FIELDS = [
1160
1587
  "method",
1161
1588
  "route",
@@ -1426,6 +1853,10 @@ var RPCTransport = class {
1426
1853
  __publicField(this, "_traceMode", "off");
1427
1854
  // Trace observer:observer(traceInfo) 在每次 RPC/事件携带 _trace 时调用
1428
1855
  __publicField(this, "_traceObserver", null);
1856
+ // 每个 transport 实例一个轻量级网络 actor。命令只在此处启动网络操作,
1857
+ // 不等待 RPC 响应,避免应用回调中的 send 反向占住收包路径。
1858
+ __publicField(this, "_actorTail", Promise.resolve());
1859
+ __publicField(this, "_actorBusy", false);
1429
1860
  this._dispatcher = opts.eventDispatcher;
1430
1861
  this._timeout = opts.timeout ?? 10;
1431
1862
  this._connectTimeout = opts.timeout ?? 10;
@@ -1455,15 +1886,18 @@ var RPCTransport = class {
1455
1886
  setMetaObserver(observer) {
1456
1887
  this._metaObserver = observer;
1457
1888
  }
1458
- async _notifyMetaObserver(message) {
1459
- if (this._metaObserver === null) return;
1889
+ _notifyMetaObserver(message) {
1890
+ const observer = this._metaObserver;
1891
+ if (observer === null) return;
1460
1892
  const meta = message._meta;
1461
1893
  if (!isJsonObject(meta)) return;
1462
- try {
1463
- await this._metaObserver(meta);
1464
- } catch (exc) {
1465
- this._log.debug(`meta_observer raised: ${String(exc)}`);
1466
- }
1894
+ this._dispatcher.enqueueTask(async () => {
1895
+ try {
1896
+ await observer(meta);
1897
+ } catch (exc) {
1898
+ this._log.debug(`meta_observer raised: ${String(exc)}`);
1899
+ }
1900
+ });
1467
1901
  }
1468
1902
  /** 设置 trace 模式:off / log / diag */
1469
1903
  setTraceMode(mode) {
@@ -1499,7 +1933,10 @@ var RPCTransport = class {
1499
1933
  * 连接到 WebSocket URL。
1500
1934
  * 等待首条消息,若为 challenge 则返回,否则进入消息路由。
1501
1935
  */
1502
- async connect(url) {
1936
+ connect(url) {
1937
+ return this._enqueueActorStart(() => this._connectImpl(url));
1938
+ }
1939
+ async _connectImpl(url) {
1503
1940
  const tStart = Date.now();
1504
1941
  this._log.debug(`connect enter: url=${url}`);
1505
1942
  const setup = await this._withConnectionSetup(async () => {
@@ -1508,7 +1945,7 @@ var RPCTransport = class {
1508
1945
  this._lastCloseCode = null;
1509
1946
  this._lastCloseReason = "";
1510
1947
  const handshake = new Promise((resolve, reject) => {
1511
- const ws = new WebSocket(url);
1948
+ const ws = createTransportWebSocket(url);
1512
1949
  this._ws = ws;
1513
1950
  this._closed = false;
1514
1951
  let initialResolved = false;
@@ -1637,7 +2074,10 @@ var RPCTransport = class {
1637
2074
  return setup.handshake;
1638
2075
  }
1639
2076
  /** 关闭连接 */
1640
- async close() {
2077
+ close() {
2078
+ return this._enqueueActor(() => this._closeImpl());
2079
+ }
2080
+ async _closeImpl() {
1641
2081
  await this._withConnectionSetup(() => this._closeUnlocked());
1642
2082
  }
1643
2083
  /** 已持有连接建立串行权时关闭当前 WebSocket。 */
@@ -1708,7 +2148,13 @@ var RPCTransport = class {
1708
2148
  * 发起 JSON-RPC 2.0 调用。
1709
2149
  * 返回 result 字段的值;若有 error 字段则抛出映射后的错误。
1710
2150
  */
1711
- async call(method, params2, timeout, trace, background = false) {
2151
+ call(method, params2, timeout, trace, background = false) {
2152
+ return this._enqueueActorStart(
2153
+ () => this._callImpl(method, params2, timeout, trace, background),
2154
+ () => new TimeoutError(`rpc cancelled: ${method}`, { retryable: true })
2155
+ );
2156
+ }
2157
+ _callImpl(method, params2, timeout, trace, background = false) {
1712
2158
  if (this._closed || !this._ws) {
1713
2159
  throw this._notConnectedError();
1714
2160
  }
@@ -1749,7 +2195,7 @@ var RPCTransport = class {
1749
2195
  this._drainRpcQueue();
1750
2196
  }, effectiveTimeout);
1751
2197
  const pending = {
1752
- resolve: async (response) => {
2198
+ resolve: (response) => {
1753
2199
  clearTimeout(timer);
1754
2200
  const elapsed = Date.now() - tStart;
1755
2201
  if (response.error !== void 0) {
@@ -1767,7 +2213,7 @@ var RPCTransport = class {
1767
2213
  if (traceId) {
1768
2214
  this._log.info(`[trace=${traceId}] rpc_recv method=${method} rpc_id=${rpcId} duration_ms=${elapsed} status=ok`);
1769
2215
  }
1770
- await this._notifyMetaObserver(response);
2216
+ this._notifyMetaObserver(response);
1771
2217
  const respTrace = response._trace;
1772
2218
  if (respTrace && typeof respTrace === "object" && !Array.isArray(respTrace)) {
1773
2219
  this._handleResponseTrace(method, "ok", elapsed, respTrace);
@@ -1815,7 +2261,10 @@ var RPCTransport = class {
1815
2261
  return Object.assign(promise, { cancel: () => cancelRpc?.() });
1816
2262
  }
1817
2263
  /** 发送 JSON-RPC 2.0 Notification,不分配 id,也不等待响应。 */
1818
- async notify(method, params2) {
2264
+ notify(method, params2) {
2265
+ return this._enqueueActorStart(() => this._notifyImpl(method, params2));
2266
+ }
2267
+ async _notifyImpl(method, params2) {
1819
2268
  if (this._closed || !this._ws) {
1820
2269
  throw this._notConnectedError();
1821
2270
  }
@@ -1844,6 +2293,58 @@ var RPCTransport = class {
1844
2293
  });
1845
2294
  return run;
1846
2295
  }
2296
+ _enqueueActor(operation) {
2297
+ this._actorBusy = true;
2298
+ const run = this._actorTail.then(async () => {
2299
+ return await operation();
2300
+ }, async () => {
2301
+ return await operation();
2302
+ });
2303
+ const tail = run.then(() => void 0, () => void 0);
2304
+ this._actorTail = tail;
2305
+ void tail.then(() => {
2306
+ if (this._actorTail === tail) this._actorBusy = false;
2307
+ });
2308
+ return run;
2309
+ }
2310
+ _enqueueActorStart(operation, cancellationError) {
2311
+ let resolveResult;
2312
+ let rejectResult;
2313
+ const result = new Promise((resolve, reject) => {
2314
+ resolveResult = resolve;
2315
+ rejectResult = reject;
2316
+ });
2317
+ let cancel;
2318
+ let cancelRequested = false;
2319
+ const launch = () => {
2320
+ if (cancelRequested) return;
2321
+ try {
2322
+ const inner = operation();
2323
+ cancel = inner.cancel;
2324
+ inner.then(resolveResult, rejectResult);
2325
+ } catch (err) {
2326
+ rejectResult(err);
2327
+ }
2328
+ };
2329
+ if (this._actorBusy) {
2330
+ const gate = this._actorTail.then(launch, launch);
2331
+ const tail = gate.then(() => void 0, () => void 0);
2332
+ this._actorTail = tail;
2333
+ void tail.then(() => {
2334
+ if (this._actorTail === tail) this._actorBusy = false;
2335
+ });
2336
+ } else {
2337
+ launch();
2338
+ }
2339
+ return Object.assign(result, {
2340
+ cancel: () => {
2341
+ if (cancelRequested) return;
2342
+ cancelRequested = true;
2343
+ if (cancel) cancel();
2344
+ else rejectResult(cancellationError?.() ?? new TimeoutError("rpc cancelled", { retryable: true }));
2345
+ }
2346
+ });
2347
+ }
1847
2348
  _sendText(payload, context, beforeSend) {
1848
2349
  return this._enqueueSend(async () => {
1849
2350
  if (beforeSend && !beforeSend()) {
@@ -1854,7 +2355,7 @@ var RPCTransport = class {
1854
2355
  }
1855
2356
  const ws = this._ws;
1856
2357
  try {
1857
- ws.send(payload);
2358
+ await Promise.resolve(ws.send(payload));
1858
2359
  } catch (err) {
1859
2360
  throw await this._sendFailureError(context, err, ws);
1860
2361
  }
@@ -1978,7 +2479,14 @@ var RPCTransport = class {
1978
2479
  const enriched = { ...respTrace, spans };
1979
2480
  this._log.info(traceDisplay(method, status, elapsedMs, respTrace, spans));
1980
2481
  if (this._traceObserver !== null) {
1981
- this._traceObserver({ type: "rpc", method, trace: enriched, status, duration_ms: elapsedMs });
2482
+ const observer = this._traceObserver;
2483
+ this._dispatcher.enqueueTask(async () => {
2484
+ try {
2485
+ await observer({ type: "rpc", method, trace: enriched, status, duration_ms: elapsedMs });
2486
+ } catch (err) {
2487
+ this._log.debug(`trace observer raised: ${err instanceof Error ? err.message : String(err)}`);
2488
+ }
2489
+ });
1982
2490
  }
1983
2491
  } catch (err) {
1984
2492
  this._log.debug(`trace handling raised: ${err instanceof Error ? err.message : String(err)}`);
@@ -2019,10 +2527,17 @@ var RPCTransport = class {
2019
2527
  this._backgroundRpcQueue = [];
2020
2528
  if (!wasClosed) {
2021
2529
  const error = new ConnectionError(`websocket closed: code=${event.code} reason=${event.reason}`);
2022
- this._dispatcher.publish("connection.error", { error });
2023
2530
  if (this._onDisconnect) {
2024
- this._onDisconnect(error, event.code).catch((exc) => this._log.warn("[aun_core.transport] disconnect callback exception:", exc));
2531
+ const onDisconnect = this._onDisconnect;
2532
+ this._dispatcher.enqueueTask(async () => {
2533
+ try {
2534
+ await onDisconnect(error, event.code);
2535
+ } catch (exc) {
2536
+ this._log.warn("[aun_core.transport] disconnect callback exception:", exc);
2537
+ }
2538
+ });
2025
2539
  }
2540
+ this._dispatcher.enqueue("connection.error", { error });
2026
2541
  }
2027
2542
  }
2028
2543
  _notConnectedError() {
@@ -2050,39 +2565,43 @@ var RPCTransport = class {
2050
2565
  if (method === "challenge") {
2051
2566
  this._challenge = message;
2052
2567
  this._log.debug("challenge received");
2053
- this._dispatcher.publish("connection.challenge", message.params ?? {});
2568
+ this._dispatcher.enqueue("connection.challenge", message.params ?? {});
2054
2569
  return;
2055
2570
  }
2056
2571
  if (method.startsWith("event/")) {
2057
2572
  const protocolEvent = method.slice(6);
2058
2573
  const sdkEvent = EVENT_NAME_MAP[protocolEvent] ?? protocolEvent;
2059
2574
  this._log.debug(`event recv: event=${sdkEvent} ${summarizeDict(message.params, DIAG_RESULT_FIELDS)}`);
2060
- void this._notifyMetaObserver(message);
2575
+ this._notifyMetaObserver(message);
2061
2576
  const params2 = message.params ?? {};
2062
2577
  if ("_trace" in params2) {
2063
2578
  const eventTrace = params2._trace;
2064
2579
  delete params2._trace;
2065
2580
  if (eventTrace && typeof eventTrace === "object" && !Array.isArray(eventTrace)) {
2066
2581
  if (this._traceObserver !== null) {
2067
- try {
2068
- this._traceObserver({ type: "event", event: sdkEvent, trace: eventTrace });
2069
- } catch {
2070
- }
2582
+ const observer = this._traceObserver;
2583
+ const tracePayload = eventTrace;
2584
+ this._dispatcher.enqueueTask(async () => {
2585
+ try {
2586
+ await observer({ type: "event", event: sdkEvent, trace: tracePayload });
2587
+ } catch {
2588
+ }
2589
+ });
2071
2590
  }
2072
2591
  const traceObj = eventTrace;
2073
2592
  this._log.info(`[trace=${String(traceObj.trace_id ?? "")}] event_recv event=${sdkEvent}`);
2074
2593
  }
2075
2594
  }
2076
2595
  if (sdkEvent.startsWith("app.")) {
2077
- this._dispatcher.publish(sdkEvent, params2);
2596
+ this._dispatcher.enqueue(sdkEvent, params2);
2078
2597
  return;
2079
2598
  }
2080
- this._dispatcher.publish(`_raw.${sdkEvent}`, params2);
2599
+ this._dispatcher.enqueue(`_raw.${sdkEvent}`, params2);
2081
2600
  return;
2082
2601
  }
2083
- void this._notifyMetaObserver(message);
2602
+ this._notifyMetaObserver(message);
2084
2603
  this._log.debug(`notification recv: method=${method || "<no-method>"}`);
2085
- this._dispatcher.publish("notification", message);
2604
+ this._dispatcher.enqueue("notification", message);
2086
2605
  }
2087
2606
  _decodeMessage(raw) {
2088
2607
  if (isJsonObject(raw)) {
@@ -2806,7 +3325,7 @@ var _AuthFlow = class _AuthFlow {
2806
3325
  return new Promise((resolve, reject) => {
2807
3326
  let ws;
2808
3327
  try {
2809
- ws = new WebSocket(gatewayUrl);
3328
+ ws = createTransportWebSocket(gatewayUrl);
2810
3329
  } catch (e) {
2811
3330
  reject(new AuthError(`WebSocket \u8FDE\u63A5\u5931\u8D25: ${gatewayUrl}`));
2812
3331
  return;
@@ -2849,7 +3368,19 @@ var _AuthFlow = class _AuthFlow {
2849
3368
  params: params2
2850
3369
  });
2851
3370
  this._log.debug(`short RPC request full: ${JSON.stringify(redactRpcLogPayload(JSON.parse(requestPayload)))}`);
2852
- ws.send(requestPayload);
3371
+ const sendResult = ws.send(requestPayload);
3372
+ if (sendResult && typeof sendResult.then === "function") {
3373
+ void Promise.resolve(sendResult).catch((error) => {
3374
+ if (settled) return;
3375
+ settled = true;
3376
+ globalThis.clearTimeout(timeout);
3377
+ try {
3378
+ ws.close();
3379
+ } catch {
3380
+ }
3381
+ reject(error instanceof Error ? error : new AuthError(String(error)));
3382
+ });
3383
+ }
2853
3384
  return;
2854
3385
  }
2855
3386
  if (!isJsonObject(msg) || msg.id !== requestId) return;
@@ -4479,6 +5010,26 @@ function nonNegativeSafeSequenceHint(value) {
4479
5010
  if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) return null;
4480
5011
  return value;
4481
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
+ }
4482
5033
  function deduplicatePlainForwardPageMessages(value, afterSeq) {
4483
5034
  if (!Array.isArray(value)) return [];
4484
5035
  const bySeq = /* @__PURE__ */ new Map();
@@ -4507,6 +5058,11 @@ function p2pAppEventFromPlainPullMessage(message) {
4507
5058
  var MessageDeliveryEngine = class {
4508
5059
  constructor(runtime) {
4509
5060
  __publicField(this, "runtime");
5061
+ __publicField(this, "syncRun", 0);
5062
+ __publicField(this, "syncingNamespaces", /* @__PURE__ */ new Set());
5063
+ __publicField(this, "syncSessions", /* @__PURE__ */ new Map());
5064
+ __publicField(this, "pendingPullDeliveryChanges", null);
5065
+ __publicField(this, "deliveryGeneration", 0);
4510
5066
  __publicField(this, "realtimeTailResults", null);
4511
5067
  __publicField(this, "realtimeSyncing", null);
4512
5068
  __publicField(this, "pendingP2pPullUpper", null);
@@ -4525,7 +5081,10 @@ var MessageDeliveryEngine = class {
4525
5081
  this.runtime = runtime;
4526
5082
  }
4527
5083
  resetInlineAckState() {
5084
+ this.syncStopped("aborted");
4528
5085
  this.inlineGeneration += 1;
5086
+ this.deliveryGeneration += 1;
5087
+ this.pendingPullDeliveryChanges = null;
4529
5088
  void this.runtime.client._rpcPipeline?.invalidatePulls?.();
4530
5089
  this.realtimeSyncing = null;
4531
5090
  this.pendingP2pPullUpper = null;
@@ -4538,6 +5097,8 @@ var MessageDeliveryEngine = class {
4538
5097
  this.pendingP2PInlineAcks = null;
4539
5098
  this.pendingGroupInlineAcks = null;
4540
5099
  this.pendingGroupInlineAckNamespaces = null;
5100
+ this.syncingNamespaces.clear();
5101
+ this.syncSessions.clear();
4541
5102
  }
4542
5103
  isInlineGenerationCurrent(generation) {
4543
5104
  return generation === this.inlineGeneration;
@@ -4565,6 +5126,91 @@ var MessageDeliveryEngine = class {
4565
5126
  endRealtimeSync(ns) {
4566
5127
  this.realtimeSyncing?.delete(ns);
4567
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
+ }
4568
5214
  hasPendingPull(ns) {
4569
5215
  const pending = ns.startsWith("p2p:") ? this.pendingP2pPullUpper : this.pendingGroupPullUpper;
4570
5216
  return (pending?.get(ns) ?? 0) > 0;
@@ -4573,6 +5219,75 @@ var MessageDeliveryEngine = class {
4573
5219
  if (!ns) return;
4574
5220
  this.schedulePendingPullIfNeeded(ns, "pull-gate-idle");
4575
5221
  }
5222
+ onPullWorkSettled() {
5223
+ const pipeline = this.runtime.client._rpcPipeline;
5224
+ if (pipeline?.hasAnyPullActivity?.() === true) return;
5225
+ void this.flushPullDeliveryChanges().finally(() => this.syncStopped("pull_drained"));
5226
+ }
5227
+ deliveryChangeNamespace(event, payload, ns = "") {
5228
+ if (event === "message.received") return "p2p";
5229
+ if (event !== "group.message_created") return "";
5230
+ if (!isJsonObject(payload)) return "";
5231
+ const aid = String(this.runtime.client._aid ?? "").trim().toLowerCase();
5232
+ const dot = aid.indexOf(".");
5233
+ const localIssuer = dot > 0 ? aid.slice(dot + 1) : "";
5234
+ const fallback = ns.startsWith("group:") ? ns.slice("group:".length) : "";
5235
+ const groupAid = normalizeGroupAid(payload.group_aid ?? payload.group_id ?? fallback, { localIssuer });
5236
+ return groupAid.includes(".") ? `group:${groupAid}` : "";
5237
+ }
5238
+ isDeliverableMessageBody(event, payload) {
5239
+ if (event !== "message.received" && event !== "group.message_created" || !isJsonObject(payload)) return false;
5240
+ const messageId = typeof payload.message_id === "string" && payload.message_id.trim().length > 0;
5241
+ const seq2 = typeof payload.seq === "number" && Number.isSafeInteger(payload.seq) && payload.seq > 0;
5242
+ return messageId || seq2;
5243
+ }
5244
+ recordDeliveryChange(changes, event, payload, ns, generation) {
5245
+ if (generation !== this.deliveryGeneration || !this.isDeliverableMessageBody(event, payload)) return;
5246
+ const namespace = this.deliveryChangeNamespace(event, payload, ns);
5247
+ if (!namespace) return;
5248
+ changes.set(namespace, (changes.get(namespace) ?? 0) + 1);
5249
+ }
5250
+ deliveryChangesPayload(changes) {
5251
+ return [...changes.entries()].filter(([, deliveredCount]) => deliveredCount > 0).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([namespace, deliveredCount]) => ({ namespace, delivered_count: deliveredCount }));
5252
+ }
5253
+ createDeliveryChangeBatch(trigger) {
5254
+ return { generation: this.deliveryGeneration, trigger, changes: /* @__PURE__ */ new Map(), closed: false };
5255
+ }
5256
+ async flushRealtimeDeliveryChanges(batch) {
5257
+ if (batch.closed) return;
5258
+ batch.closed = true;
5259
+ if (batch.generation !== this.deliveryGeneration) return;
5260
+ if (this.deliveryChangesPayload(batch.changes).length === 0) return;
5261
+ const pendingPull = this.pendingPullDeliveryChanges;
5262
+ this.pendingPullDeliveryChanges = null;
5263
+ if (pendingPull) {
5264
+ for (const [namespace, count] of pendingPull) {
5265
+ batch.changes.set(namespace, (batch.changes.get(namespace) ?? 0) + count);
5266
+ }
5267
+ }
5268
+ if (batch.generation !== this.deliveryGeneration) return;
5269
+ const changes = this.deliveryChangesPayload(batch.changes);
5270
+ if (changes.length === 0) return;
5271
+ this.runtime.client._dispatcher.enqueue("delivery.changed", {
5272
+ trigger: batch.trigger,
5273
+ source: batch.trigger,
5274
+ changes
5275
+ });
5276
+ }
5277
+ async flushPullDeliveryChanges() {
5278
+ const generation = this.deliveryGeneration;
5279
+ const pending = this.pendingPullDeliveryChanges;
5280
+ if (!pending || pending.size === 0 || generation !== this.deliveryGeneration) return;
5281
+ this.pendingPullDeliveryChanges = null;
5282
+ if (generation !== this.deliveryGeneration) return;
5283
+ const changes = this.deliveryChangesPayload(pending);
5284
+ if (changes.length === 0) return;
5285
+ this.runtime.client._dispatcher.enqueue("delivery.changed", {
5286
+ trigger: "pull_drained",
5287
+ source: "pull",
5288
+ changes
5289
+ });
5290
+ }
4576
5291
  recordPendingPull(ns, seq2) {
4577
5292
  if (!ns || !Number.isSafeInteger(seq2) || seq2 <= 0) return;
4578
5293
  const pending = ns.startsWith("p2p:") ? this.pendingP2pPullUpper ?? /* @__PURE__ */ new Map() : this.pendingGroupPullUpper ?? /* @__PURE__ */ new Map();
@@ -5074,7 +5789,7 @@ var MessageDeliveryEngine = class {
5074
5789
  }
5075
5790
  const message = normalizeGroupMentionMode(rawMessage);
5076
5791
  if (this.recallEventFromGroupMessage(message)) {
5077
- if (await this.publishGroupRecallTombstone(groupId, seq2, message)) {
5792
+ if (await this.publishGroupRecallTombstone(groupId, seq2, message, "pull")) {
5078
5793
  this.ensurePullOperationCurrent();
5079
5794
  this.markPublishedSeq(ns, seq2);
5080
5795
  publishedCount += 1;
@@ -5145,16 +5860,27 @@ var MessageDeliveryEngine = class {
5145
5860
  this.ensurePullOperationCurrent();
5146
5861
  await this.confirmPlainForwardAck(ns, ackMethod, pendingAckSeq, groupId);
5147
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
+ );
5148
5874
  return { rawCount: messages.length, publishedCount };
5149
5875
  }
5150
- enqueueOrderedMessage(ns, event, seq2, payload) {
5876
+ enqueueOrderedMessage(ns, event, seq2, payload, source = "push") {
5151
5877
  const client = this.runtime.client;
5152
5878
  let queue = client._pendingOrderedMsgs.get(ns);
5153
5879
  if (!queue) {
5154
5880
  queue = /* @__PURE__ */ new Map();
5155
5881
  client._pendingOrderedMsgs.set(ns, queue);
5156
5882
  }
5157
- queue.set(seq2, { event, payload });
5883
+ queue.set(seq2, { event, payload, source });
5158
5884
  if (queue.size > PENDING_ORDERED_LIMIT) {
5159
5885
  const drop = [...queue.keys()].sort((a, b) => a - b).slice(0, queue.size - PENDING_ORDERED_LIMIT);
5160
5886
  for (const oldSeq of drop) queue.delete(oldSeq);
@@ -5163,23 +5889,23 @@ var MessageDeliveryEngine = class {
5163
5889
  isGroupEventNamespace(ns) {
5164
5890
  return ns.startsWith("group_event:");
5165
5891
  }
5166
- async publishOrderedQueueItem(ns, event, seq2, payload, pullResponse = false) {
5892
+ async publishOrderedQueueItem(ns, event, seq2, payload, pullResponse = false, source = "push", batch) {
5167
5893
  const client = this.runtime.client;
5168
5894
  if (event === "group.changed" && this.isGroupEventNamespace(ns)) {
5169
- await this.publishOrderedGroupChanged(payload);
5895
+ await this.publishOrderedGroupChanged(payload, source);
5170
5896
  return;
5171
5897
  }
5172
5898
  if (event === "message.recalled") {
5173
- await this.publishMessageRecallTombstone(seq2, payload);
5899
+ await this.publishMessageRecallTombstone(seq2, payload, source);
5174
5900
  return;
5175
5901
  }
5176
5902
  if (pullResponse) {
5177
- await client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload));
5903
+ await client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload, source, ns, batch));
5178
5904
  return;
5179
5905
  }
5180
- await client._publishAppEvent(event, payload);
5906
+ await client._publishAppEvent(event, payload, source, ns, batch);
5181
5907
  }
5182
- async publishOrderedGroupChanged(payload) {
5908
+ async publishOrderedGroupChanged(payload, source = "ordered") {
5183
5909
  const client = this.runtime.client;
5184
5910
  if (isJsonObject(payload)) {
5185
5911
  const eventPayload = payload;
@@ -5190,7 +5916,7 @@ var MessageDeliveryEngine = class {
5190
5916
  if (groupId) client._cleanupDissolvedGroup?.(groupId);
5191
5917
  }
5192
5918
  }
5193
- await client._publishAppEvent("group.changed", payload);
5919
+ await client._publishAppEvent("group.changed", payload, source);
5194
5920
  }
5195
5921
  isInstanceScopedMessageEvent(event) {
5196
5922
  return event === "message.received" || event === "message.recalled" || event === "message.undecryptable" || event === "group.message_created" || event === "group.message_recalled" || event === "group.message_undecryptable";
@@ -5207,17 +5933,22 @@ var MessageDeliveryEngine = class {
5207
5933
  }
5208
5934
  return result;
5209
5935
  }
5210
- normalizePublishedMessagePayload(event, payload) {
5936
+ normalizePublishedMessagePayload(event, payload, source = "direct") {
5937
+ let normalized;
5211
5938
  if (this.isInstanceScopedMessageEvent(event)) {
5212
5939
  payload = this.stripInlineInternalFields(payload);
5213
5940
  if (event === "group.message_created") payload = normalizeGroupMentionMode(payload);
5214
- return this.attachAppMessageEnvelope(this.stripInternalSenderDeviceFields(this.attachCurrentInstanceContext(payload)));
5215
- }
5216
- if (this.isGroupScopedEvent(event)) {
5941
+ normalized = this.attachAppMessageEnvelope(this.stripInternalSenderDeviceFields(this.attachCurrentInstanceContext(payload)));
5942
+ } else if (this.isGroupScopedEvent(event)) {
5217
5943
  payload = this.stripInlineInternalFields(payload);
5218
- return this.attachAppGroupEventEnvelope(this.attachCurrentInstanceContext(payload));
5944
+ normalized = this.attachAppGroupEventEnvelope(this.attachCurrentInstanceContext(payload));
5945
+ } else {
5946
+ normalized = payload;
5219
5947
  }
5220
- 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 };
5221
5952
  }
5222
5953
  stripInlineInternalFields(payload) {
5223
5954
  if (!isJsonObject(payload)) return payload;
@@ -5320,6 +6051,7 @@ var MessageDeliveryEngine = class {
5320
6051
  setIfPresent("type", firstValue(body.type, params2.type, params2.message_type, params2.payload_type));
5321
6052
  setIfPresent("kind", firstValue(body.kind, params2.kind));
5322
6053
  setIfPresent("version", firstValue(body.version, params2.version));
6054
+ setIfPresent("message_id", firstValue(params2.message_id, body.message_id, resultObj.message_id));
5323
6055
  setIfPresent("timestamp", firstValue(params2.timestamp, resultObj.timestamp, resultObj.created_at, resultObj.t_server, Date.now()));
5324
6056
  envelope.encrypted = Boolean(encrypted);
5325
6057
  const context = this.envelopeMetadata(params2.context);
@@ -5432,7 +6164,7 @@ var MessageDeliveryEngine = class {
5432
6164
  if (tombstoneId) return `p2p|tombstone:${tombstoneId}`;
5433
6165
  return `p2p|unknown:${Date.now()}:${Math.random()}`;
5434
6166
  }
5435
- async publishMessageRecallTombstone(seq2, message) {
6167
+ async publishMessageRecallTombstone(seq2, message, source = "push") {
5436
6168
  const client = this.runtime.client;
5437
6169
  const eventPayload = this.recallEventFromMessage(message);
5438
6170
  if (!eventPayload) return false;
@@ -5451,7 +6183,7 @@ var MessageDeliveryEngine = class {
5451
6183
  const drop = [...seen.entries()].sort((a, b) => a[1] - b[1]).slice(0, seen.size - MESSAGE_RECALL_SEEN_LIMIT);
5452
6184
  for (const [oldKey] of drop) seen.delete(oldKey);
5453
6185
  }
5454
- await client._publishAppEvent("message.recalled", eventPayload);
6186
+ await client._publishAppEvent("message.recalled", eventPayload, source);
5455
6187
  client._clientLog.debug(`message.recalled published: seq=${String(seq2)} ids=${JSON.stringify(eventPayload.message_ids)}`);
5456
6188
  return true;
5457
6189
  }
@@ -5513,7 +6245,7 @@ var MessageDeliveryEngine = class {
5513
6245
  if (tombstoneId) return `${normalizedGroupId}|tombstone:${tombstoneId}`;
5514
6246
  return `${normalizedGroupId}|unknown:${Date.now()}:${Math.random()}`;
5515
6247
  }
5516
- async publishGroupRecallTombstone(groupId, seq2, message) {
6248
+ async publishGroupRecallTombstone(groupId, seq2, message, source = "push") {
5517
6249
  const client = this.runtime.client;
5518
6250
  const eventPayload = this.recallEventFromGroupMessage(message);
5519
6251
  if (!eventPayload) return false;
@@ -5534,7 +6266,7 @@ var MessageDeliveryEngine = class {
5534
6266
  const drop = [...seen.entries()].sort((a, b) => a[1] - b[1]).slice(0, seen.size - GROUP_RECALL_SEEN_LIMIT);
5535
6267
  for (const [oldKey] of drop) seen.delete(oldKey);
5536
6268
  }
5537
- await client._publishAppEvent("group.message_recalled", eventPayload);
6269
+ await client._publishAppEvent("group.message_recalled", eventPayload, source);
5538
6270
  client._clientLog.debug(`group.message_recalled published: group=${groupId} seq=${String(seq2)} ids=${JSON.stringify(eventPayload.message_ids)}`);
5539
6271
  return true;
5540
6272
  }
@@ -5597,8 +6329,9 @@ var MessageDeliveryEngine = class {
5597
6329
  }
5598
6330
  if (contig !== contigBefore) this.persistSeq(ns);
5599
6331
  }
5600
- async publishAppEvent(event, payload) {
6332
+ async publishAppEvent(event, payload, source = "direct", ns = "", batch) {
5601
6333
  const client = this.runtime.client;
6334
+ const generation = this.deliveryGeneration;
5602
6335
  if ((event === "message.received" || event === "group.message_created") && isJsonObject(payload)) {
5603
6336
  client._maybeAppendEchoTraceReceive(payload);
5604
6337
  }
@@ -5617,7 +6350,24 @@ var MessageDeliveryEngine = class {
5617
6350
  client._clientLog.debug(`agent_md etag inject skipped: ${String(exc)}`);
5618
6351
  }
5619
6352
  }
5620
- await client._dispatcher.publish(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);
6359
+ if (generation !== this.deliveryGeneration) return;
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);
6363
+ this.recordDeliveryChange(localBatch.changes, event, payload, ns, generation);
6364
+ if (!batch) await this.flushRealtimeDeliveryChanges(localBatch);
6365
+ } else {
6366
+ const changes = this.pendingPullDeliveryChanges ?? /* @__PURE__ */ new Map();
6367
+ this.pendingPullDeliveryChanges = changes;
6368
+ this.recordDeliveryChange(changes, event, payload, ns, generation);
6369
+ if (client._rpcPipeline?.hasAnyPullActivity?.() !== true) await this.flushPullDeliveryChanges();
6370
+ }
5621
6371
  }
5622
6372
  messageTargetsCurrentInstance(message) {
5623
6373
  if (!isJsonObject(message)) return true;
@@ -5750,7 +6500,7 @@ var MessageDeliveryEngine = class {
5750
6500
  const client = this.runtime.client;
5751
6501
  try {
5752
6502
  if (!isJsonObject(data)) {
5753
- await client._publishAppEvent("message.received", data);
6503
+ await client._publishAppEvent("message.received", data, "push");
5754
6504
  return;
5755
6505
  }
5756
6506
  const msg = { ...data };
@@ -5795,7 +6545,7 @@ var MessageDeliveryEngine = class {
5795
6545
  await client._publishEncryptedPushMessage("message.received", "message.undecryptable", "", seq2 ?? 0, msg, false);
5796
6546
  return;
5797
6547
  }
5798
- await client._publishAppEvent("message.received", msg);
6548
+ await client._publishAppEvent("message.received", msg, "push");
5799
6549
  }
5800
6550
  } catch (exc) {
5801
6551
  client._clientLog.warn(`P2P push processing failed:${String(exc)}`);
@@ -5810,7 +6560,7 @@ var MessageDeliveryEngine = class {
5810
6560
  _decrypt_error: String(exc)
5811
6561
  };
5812
6562
  client._attachV2EnvelopeMetadataFromSource(safeEvent, data);
5813
- await client._publishAppEvent("message.undecryptable", safeEvent);
6563
+ await client._publishAppEvent("message.undecryptable", safeEvent, "push");
5814
6564
  }
5815
6565
  }
5816
6566
  }
@@ -5834,7 +6584,7 @@ var MessageDeliveryEngine = class {
5834
6584
  const client = this.runtime.client;
5835
6585
  try {
5836
6586
  if (!isJsonObject(data)) {
5837
- await client._publishAppEvent("group.message_created", data);
6587
+ await client._publishAppEvent("group.message_created", data, "push");
5838
6588
  return;
5839
6589
  }
5840
6590
  const msg = { ...data };
@@ -5906,7 +6656,7 @@ var MessageDeliveryEngine = class {
5906
6656
  await client._publishEncryptedPushMessage("group.message_created", "group.message_undecryptable", "", seq2 ?? 0, msg, true);
5907
6657
  return;
5908
6658
  }
5909
- await client._publishAppEvent("group.message_created", msg);
6659
+ await client._publishAppEvent("group.message_created", msg, "push");
5910
6660
  }
5911
6661
  } catch (exc) {
5912
6662
  client._clientLog.warn(`group push processing failed:${String(exc)}`);
@@ -5921,7 +6671,7 @@ var MessageDeliveryEngine = class {
5921
6671
  _decrypt_error: String(exc)
5922
6672
  };
5923
6673
  client._attachV2EnvelopeMetadataFromSource(safeEvent, data);
5924
- await client._publishAppEvent("group.message_undecryptable", safeEvent);
6674
+ await client._publishAppEvent("group.message_undecryptable", safeEvent, "push");
5925
6675
  }
5926
6676
  }
5927
6677
  }
@@ -5929,7 +6679,7 @@ var MessageDeliveryEngine = class {
5929
6679
  const client = this.runtime.client;
5930
6680
  const groupId = notification.group_id ?? "";
5931
6681
  if (!groupId) {
5932
- await client._publishAppEvent("group.message_created", notification);
6682
+ await client._publishAppEvent("group.message_created", notification, "push");
5933
6683
  return;
5934
6684
  }
5935
6685
  if (client._sessionOptions?.background_sync === false) {
@@ -5949,7 +6699,7 @@ var MessageDeliveryEngine = class {
5949
6699
  return;
5950
6700
  } catch (exc) {
5951
6701
  client._clientLog.warn(`auto pull group message failed:${String(exc)}`);
5952
- await client._publishAppEvent("group.message_created", notification);
6702
+ await client._publishAppEvent("group.message_created", notification, "push");
5953
6703
  return;
5954
6704
  }
5955
6705
  }
@@ -5976,7 +6726,7 @@ var MessageDeliveryEngine = class {
5976
6726
  } catch (exc) {
5977
6727
  client._clientLog.warn(`auto pull group message commit/ack failed:${String(exc)}`);
5978
6728
  if (!rawCompleted) {
5979
- await client._publishAppEvent("group.message_created", notification);
6729
+ await client._publishAppEvent("group.message_created", notification, "push");
5980
6730
  }
5981
6731
  }
5982
6732
  }
@@ -5990,11 +6740,16 @@ var MessageDeliveryEngine = class {
5990
6740
  P2P_GAP_FILL_RETRY_MAX_MS
5991
6741
  );
5992
6742
  client._gapFillDone.add(retryKey);
6743
+ const releasePullWork = client._rpcPipeline?.reservePullWork?.() ?? (() => {
6744
+ });
5993
6745
  client._clientLog.debug(`P2P message gap-fill retry scheduled: ns=${ns} attempt=${retryAttempt} delay_ms=${delayMs}`);
5994
6746
  globalThis.setTimeout(() => {
5995
6747
  client._gapFillDone.delete(retryKey);
5996
- if (client.state !== "ready" /* READY */ || client._closing) return;
5997
- client._safeAsync(this.fillP2pGap(retryAttempt));
6748
+ if (client.state !== "ready" /* READY */ || client._closing) {
6749
+ releasePullWork();
6750
+ return;
6751
+ }
6752
+ client._safeAsync(this.fillP2pGap(retryAttempt).finally(releasePullWork));
5998
6753
  }, delayMs);
5999
6754
  }
6000
6755
  async fillP2pGap(retryAttempt = 0) {
@@ -6099,6 +6854,7 @@ var MessageDeliveryEngine = class {
6099
6854
  client._gapFillDone.add(dedupKey);
6100
6855
  this.runtime.delivery.setGapFillActive(true);
6101
6856
  let continuationAfterSeq = 0;
6857
+ let failed = false;
6102
6858
  try {
6103
6859
  let nextAfterSeq = afterSeq;
6104
6860
  const maxPages = singlePage ? 1 : 100;
@@ -6146,6 +6902,7 @@ var MessageDeliveryEngine = class {
6146
6902
  }
6147
6903
  const eventSeqs = [];
6148
6904
  let hasDissolvedEvent = false;
6905
+ let publishedEventCount = 0;
6149
6906
  for (const evt of eventObjects) {
6150
6907
  const eventSeq = Number(evt.event_seq ?? 0);
6151
6908
  if (Number.isFinite(eventSeq) && eventSeq > 0) eventSeqs.push(eventSeq);
@@ -6163,7 +6920,8 @@ var MessageDeliveryEngine = class {
6163
6920
  }
6164
6921
  }
6165
6922
  if (Number.isFinite(eventSeq) && eventSeq > 0 && !client._pushedSeqs.get(ns)?.has(eventSeq)) {
6166
- this.enqueueOrderedMessage(ns, "group.changed", eventSeq, evt);
6923
+ this.enqueueOrderedMessage(ns, "group.changed", eventSeq, evt, "pull");
6924
+ publishedEventCount += 1;
6167
6925
  }
6168
6926
  }
6169
6927
  const ackContig = client._seqTracker.getContiguousSeq(ns);
@@ -6188,6 +6946,16 @@ var MessageDeliveryEngine = class {
6188
6946
  client._clientLog.warn("group event auto-ack failed: group=" + groupId, e);
6189
6947
  }
6190
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
+ );
6191
6959
  const nextAfter = Math.max(eventSeqs.length > 0 ? Math.max(...eventSeqs) : nextAfterSeq, nextAfterSeq);
6192
6960
  if (singlePage && result.has_more === true && nextAfter > nextAfterSeq) {
6193
6961
  continuationAfterSeq = nextAfter;
@@ -6199,10 +6967,12 @@ var MessageDeliveryEngine = class {
6199
6967
  client._clientLog.warn(`group event gap fill reached max_pages=${maxPages} group=${groupId} after_seq=${nextAfterSeq}`);
6200
6968
  }
6201
6969
  } catch (exc) {
6970
+ failed = true;
6202
6971
  client._clientLog.warn(`group event gap-fill failed:${String(exc)}`);
6203
6972
  } finally {
6204
6973
  client._gapFillDone.delete(dedupKey);
6205
6974
  this.runtime.delivery.setGapFillActive(false);
6975
+ if (!client._rpcPipeline) this.syncStopped(failed ? "aborted" : "pull_drained");
6206
6976
  if (singlePage && continuationAfterSeq > afterSeq) {
6207
6977
  this.enqueueOnlineUnreadEventHint({
6208
6978
  group_id: groupId,
@@ -6547,7 +7317,7 @@ var MessageDeliveryEngine = class {
6547
7317
  const tracker = client._seqTracker;
6548
7318
  const snapshot = typeof tracker.snapshotNamespace === "function" ? tracker.snapshotNamespace(ns) : null;
6549
7319
  try {
6550
- const published = await this.publishPulledMessage(event, ns, seq2, payload);
7320
+ const published = await this.publishPulledMessage(event, ns, seq2, payload, true, "inline_push");
6551
7321
  if (!this.isInlineGenerationCurrent(generation)) return false;
6552
7322
  if (!published) return false;
6553
7323
  const needsPull = Boolean(tracker.onMessageSeq(ns, seq2));
@@ -7035,13 +7805,12 @@ var MessageDeliveryEngine = class {
7035
7805
  }
7036
7806
  }
7037
7807
  } catch (exc) {
7038
- client._dispatcher.publish("seq_tracker.persist_error", {
7808
+ client._dispatcher.enqueue("seq_tracker.persist_error", {
7039
7809
  phase: "restore",
7040
7810
  aid,
7041
7811
  device_id: deviceId,
7042
7812
  slot_id: slotId,
7043
7813
  error: String(exc)
7044
- }).catch(() => {
7045
7814
  });
7046
7815
  }
7047
7816
  }
@@ -7232,13 +8001,12 @@ var MessageDeliveryEngine = class {
7232
8001
  } catch (exc) {
7233
8002
  const error = formatDeliveryError(exc);
7234
8003
  client._clientLog.warn(`save SeqTracker state failed: ${error}`);
7235
- client._dispatcher.publish("seq_tracker.persist_error", {
8004
+ client._dispatcher.enqueue("seq_tracker.persist_error", {
7236
8005
  phase: "save",
7237
8006
  aid,
7238
8007
  device_id: deviceId,
7239
8008
  slot_id: slotId,
7240
8009
  error: String(error)
7241
- }).catch(() => {
7242
8010
  });
7243
8011
  if (throwOnError) throw exc;
7244
8012
  }
@@ -7343,7 +8111,7 @@ var MessageDeliveryEngine = class {
7343
8111
  }
7344
8112
  return params2;
7345
8113
  }
7346
- async drainOrderedMessages(ns, beforeSeq, pullResponse = false, persist = true) {
8114
+ async drainOrderedMessages(ns, beforeSeq, pullResponse = false, persist = true, batch, source = "pull") {
7347
8115
  const client = this.runtime.client;
7348
8116
  this.ensurePullOperationCurrent();
7349
8117
  const queue = client._pendingOrderedMsgs.get(ns);
@@ -7351,15 +8119,39 @@ var MessageDeliveryEngine = class {
7351
8119
  const contig = client._seqTracker.getContiguousSeq(ns);
7352
8120
  const ready = [...queue.keys()].filter((seq2) => seq2 <= contig && (beforeSeq === void 0 || seq2 < beforeSeq)).sort((a, b) => a - b);
7353
8121
  let delivered = false;
7354
- for (const seq2 of ready) {
7355
- this.ensurePullOperationCurrent();
7356
- const item = queue.get(seq2);
7357
- queue.delete(seq2);
7358
- if (!item || client._pushedSeqs.get(ns)?.has(seq2)) continue;
7359
- await this.publishOrderedQueueItem(ns, item.event, seq2, item.payload, pullResponse);
7360
- this.ensurePullOperationCurrent();
7361
- this.markPublishedSeq(ns, seq2);
7362
- delivered = true;
8122
+ const drainBatches = /* @__PURE__ */ new Map();
8123
+ try {
8124
+ for (const seq2 of ready) {
8125
+ this.ensurePullOperationCurrent();
8126
+ const item = queue.get(seq2);
8127
+ queue.delete(seq2);
8128
+ if (!item || client._pushedSeqs.get(ns)?.has(seq2)) continue;
8129
+ const itemSource = item.source ?? source;
8130
+ let itemBatch = batch;
8131
+ if ((itemSource === "push" || itemSource === "inline_push") && (!batch || batch.trigger !== itemSource)) {
8132
+ itemBatch = drainBatches.get(itemSource);
8133
+ if (!itemBatch) {
8134
+ itemBatch = this.createDeliveryChangeBatch(itemSource);
8135
+ drainBatches.set(itemSource, itemBatch);
8136
+ }
8137
+ }
8138
+ await this.publishOrderedQueueItem(
8139
+ ns,
8140
+ item.event,
8141
+ seq2,
8142
+ item.payload,
8143
+ pullResponse,
8144
+ itemSource,
8145
+ itemBatch
8146
+ );
8147
+ this.ensurePullOperationCurrent();
8148
+ this.markPublishedSeq(ns, seq2);
8149
+ delivered = true;
8150
+ }
8151
+ } finally {
8152
+ for (const drainBatch of drainBatches.values()) {
8153
+ await this.flushRealtimeDeliveryChanges(drainBatch);
8154
+ }
7363
8155
  }
7364
8156
  if (queue.size === 0) {
7365
8157
  client._pendingOrderedMsgs.delete(ns);
@@ -7369,75 +8161,87 @@ var MessageDeliveryEngine = class {
7369
8161
  }
7370
8162
  }
7371
8163
  }
7372
- async publishOrderedMessage(event, ns, seq2, payload) {
8164
+ async publishOrderedMessage(event, ns, seq2, payload, source = "push", operationBatch) {
7373
8165
  const client = this.runtime.client;
7374
- const seqNum = Number(seq2);
7375
- if (!Number.isFinite(seqNum) || !Number.isInteger(seqNum) || seqNum <= 0) {
7376
- await this.publishOrderedQueueItem(ns, event, seqNum, payload);
8166
+ const ownsBatch = !operationBatch && (source === "push" || source === "inline_push");
8167
+ const batch = operationBatch ?? (ownsBatch ? this.createDeliveryChangeBatch(source) : void 0);
8168
+ try {
8169
+ const seqNum = Number(seq2);
8170
+ if (!Number.isFinite(seqNum) || !Number.isInteger(seqNum) || seqNum <= 0) {
8171
+ await this.publishOrderedQueueItem(ns, event, seqNum, payload, false, source, batch);
8172
+ return true;
8173
+ }
8174
+ if (client._pushedSeqs.get(ns)?.has(seqNum)) {
8175
+ const queue2 = client._pendingOrderedMsgs.get(ns);
8176
+ queue2?.delete(seqNum);
8177
+ if (queue2 && queue2.size === 0) client._pendingOrderedMsgs.delete(ns);
8178
+ return false;
8179
+ }
8180
+ const contig = client._seqTracker.getContiguousSeq(ns);
8181
+ if (seqNum > contig) {
8182
+ this.enqueueOrderedMessage(ns, event, seqNum, payload, source);
8183
+ return false;
8184
+ }
8185
+ await this.drainOrderedMessages(ns, seqNum, true, true, batch, source);
8186
+ if (client._pushedSeqs.get(ns)?.has(seqNum)) return false;
8187
+ const queue = client._pendingOrderedMsgs.get(ns);
8188
+ queue?.delete(seqNum);
8189
+ if (queue && queue.size === 0) client._pendingOrderedMsgs.delete(ns);
8190
+ await this.publishOrderedQueueItem(ns, event, seqNum, payload, false, source, batch);
8191
+ this.markPublishedSeq(ns, seqNum);
8192
+ await this.drainOrderedMessages(ns, void 0, false, true, batch, source);
8193
+ if (!client._pendingOrderedMsgs.get(ns)) await this.saveSeqTrackerState();
7377
8194
  return true;
8195
+ } finally {
8196
+ if (ownsBatch && batch) await this.flushRealtimeDeliveryChanges(batch);
7378
8197
  }
7379
- if (client._pushedSeqs.get(ns)?.has(seqNum)) {
7380
- const queue2 = client._pendingOrderedMsgs.get(ns);
7381
- queue2?.delete(seqNum);
7382
- if (queue2 && queue2.size === 0) client._pendingOrderedMsgs.delete(ns);
7383
- return false;
7384
- }
7385
- const contig = client._seqTracker.getContiguousSeq(ns);
7386
- if (seqNum > contig) {
7387
- this.enqueueOrderedMessage(ns, event, seqNum, payload);
7388
- return false;
7389
- }
7390
- await this.drainOrderedMessages(ns, seqNum, true);
7391
- if (client._pushedSeqs.get(ns)?.has(seqNum)) return false;
7392
- const queue = client._pendingOrderedMsgs.get(ns);
7393
- queue?.delete(seqNum);
7394
- if (queue && queue.size === 0) client._pendingOrderedMsgs.delete(ns);
7395
- await this.publishOrderedQueueItem(ns, event, seqNum, payload);
7396
- this.markPublishedSeq(ns, seqNum);
7397
- await this.drainOrderedMessages(ns);
7398
- if (!client._pendingOrderedMsgs.get(ns)) await this.saveSeqTrackerState();
7399
- return true;
7400
8198
  }
7401
- async publishPulledMessage(event, ns, seq2, payload, persist = true) {
8199
+ async publishPulledMessage(event, ns, seq2, payload, persist = true, source = "pull", operationBatch) {
7402
8200
  const client = this.runtime.client;
7403
- this.ensurePullOperationCurrent();
7404
- const seqNum = Number(seq2);
7405
- if (!Number.isFinite(seqNum) || !Number.isInteger(seqNum) || seqNum <= 0 || !ns) {
8201
+ const ownsBatch = !operationBatch && (source === "push" || source === "inline_push");
8202
+ const batch = operationBatch ?? (ownsBatch ? this.createDeliveryChangeBatch(source) : void 0);
8203
+ try {
8204
+ this.ensurePullOperationCurrent();
8205
+ const seqNum = Number(seq2);
8206
+ if (!Number.isFinite(seqNum) || !Number.isInteger(seqNum) || seqNum <= 0 || !ns) {
8207
+ if (event === "message.recalled") {
8208
+ const published = await client._withPullResponseProcessing(
8209
+ ns,
8210
+ () => this.publishMessageRecallTombstone(seq2, payload, source)
8211
+ );
8212
+ this.ensurePullOperationCurrent();
8213
+ return published;
8214
+ }
8215
+ await client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload, source, ns, batch));
8216
+ this.ensurePullOperationCurrent();
8217
+ return true;
8218
+ }
8219
+ const queue = client._pendingOrderedMsgs.get(ns);
8220
+ if (client._pushedSeqs.get(ns)?.has(seqNum)) {
8221
+ queue?.delete(seqNum);
8222
+ if (queue && queue.size === 0) client._pendingOrderedMsgs.delete(ns);
8223
+ return false;
8224
+ }
8225
+ await this.drainOrderedMessages(ns, seqNum, false, persist, batch, source);
8226
+ this.ensurePullOperationCurrent();
8227
+ queue?.delete(seqNum);
8228
+ if (queue && queue.size === 0) client._pendingOrderedMsgs.delete(ns);
7406
8229
  if (event === "message.recalled") {
7407
8230
  const published = await client._withPullResponseProcessing(
7408
8231
  ns,
7409
- () => this.publishMessageRecallTombstone(seq2, payload)
8232
+ () => this.publishMessageRecallTombstone(seqNum, payload, source)
7410
8233
  );
7411
8234
  this.ensurePullOperationCurrent();
8235
+ this.markPublishedSeq(ns, seqNum);
7412
8236
  return published;
7413
8237
  }
7414
- await client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload));
7415
- this.ensurePullOperationCurrent();
7416
- return true;
7417
- }
7418
- const queue = client._pendingOrderedMsgs.get(ns);
7419
- if (client._pushedSeqs.get(ns)?.has(seqNum)) {
7420
- queue?.delete(seqNum);
7421
- if (queue && queue.size === 0) client._pendingOrderedMsgs.delete(ns);
7422
- return false;
7423
- }
7424
- await this.drainOrderedMessages(ns, seqNum, false, persist);
7425
- this.ensurePullOperationCurrent();
7426
- queue?.delete(seqNum);
7427
- if (queue && queue.size === 0) client._pendingOrderedMsgs.delete(ns);
7428
- if (event === "message.recalled") {
7429
- const published = await client._withPullResponseProcessing(
7430
- ns,
7431
- () => this.publishMessageRecallTombstone(seqNum, payload)
7432
- );
8238
+ await client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload, source, ns, batch));
7433
8239
  this.ensurePullOperationCurrent();
7434
8240
  this.markPublishedSeq(ns, seqNum);
7435
- return published;
8241
+ return true;
8242
+ } finally {
8243
+ if (ownsBatch && batch) await this.flushRealtimeDeliveryChanges(batch);
7436
8244
  }
7437
- await client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload));
7438
- this.ensurePullOperationCurrent();
7439
- this.markPublishedSeq(ns, seqNum);
7440
- return true;
7441
8245
  }
7442
8246
  };
7443
8247
 
@@ -7626,12 +8430,7 @@ var LifecycleController = class {
7626
8430
  async publishConnectionEvent(owner, event, payload) {
7627
8431
  const client = this.runtime.client;
7628
8432
  this.assertConnectionAttemptOwner(owner);
7629
- client._connectEventDispatchDepth = Number(client._connectEventDispatchDepth ?? 0) + 1;
7630
- try {
7631
- await client._dispatcher.publish(event, payload);
7632
- } finally {
7633
- client._connectEventDispatchDepth -= 1;
7634
- }
8433
+ client._dispatcher.enqueue(event, payload);
7635
8434
  return this.ownsConnectionAttempt(owner);
7636
8435
  }
7637
8436
  async cancelConnectionAttemptAndWait() {
@@ -7939,14 +8738,9 @@ var LifecycleController = class {
7939
8738
  client._clientLog.warn(`reentrant close failed: ${err instanceof Error ? err.message : String(err)}`);
7940
8739
  });
7941
8740
  }
7942
- async publishLifecycleStopEvent(payload) {
8741
+ publishLifecycleStopEvent(payload) {
7943
8742
  const client = this.runtime.client;
7944
- client._lifecycleStopEventDispatchDepth = Number(client._lifecycleStopEventDispatchDepth ?? 0) + 1;
7945
- try {
7946
- await client._dispatcher.publish("state_change", payload);
7947
- } finally {
7948
- client._lifecycleStopEventDispatchDepth -= 1;
7949
- }
8743
+ client._dispatcher.enqueue("state_change", payload);
7950
8744
  }
7951
8745
  async withLifecycleStop(operation, kind) {
7952
8746
  const client = this.runtime.client;
@@ -8007,7 +8801,7 @@ var LifecycleController = class {
8007
8801
  client._stopBackgroundTasks();
8008
8802
  if (client._closing) return;
8009
8803
  this.runtime.lifecycle.resetForDisconnect("standby");
8010
- await this.publishLifecycleStopEvent({ state: client._publicState(client._state) });
8804
+ this.publishLifecycleStopEvent({ state: client._publicState(client._state) });
8011
8805
  client._clientLog.debug(`disconnect exit: elapsed=${Date.now() - tStart}ms`);
8012
8806
  }, "disconnect");
8013
8807
  } finally {
@@ -8017,6 +8811,7 @@ var LifecycleController = class {
8017
8811
  async close() {
8018
8812
  const client = this.runtime.client;
8019
8813
  const tStart = Date.now();
8814
+ const calledFromHandler = client._dispatcher.isDispatchingHandler();
8020
8815
  client._clientLog.debug(`close enter: state=${client._state}`);
8021
8816
  this.runtime.lifecycle.setClosing(true);
8022
8817
  client._delivery.resetInlineAckState();
@@ -8025,34 +8820,39 @@ var LifecycleController = class {
8025
8820
  return;
8026
8821
  }
8027
8822
  return this.withLifecycleStop(async () => {
8028
- const pullInvalidation = client._rpcPipeline?.invalidatePulls?.();
8029
- client._rpcPipeline?.stopPullGateWatchdogs?.();
8030
- client._stopBackgroundTasks();
8031
- if (client._state === "idle" || client._state === "closed") {
8032
- const reconnectCancellation2 = client._cancelReconnectAndWait();
8033
- const connectionCancellation2 = this.cancelConnectionAttemptAndWait();
8034
- await Promise.all([reconnectCancellation2, connectionCancellation2]);
8035
- await pullInvalidation;
8823
+ try {
8824
+ const pullInvalidation = client._rpcPipeline?.invalidatePulls?.();
8825
+ client._rpcPipeline?.stopPullGateWatchdogs?.();
8826
+ client._stopBackgroundTasks();
8827
+ if (client._state === "idle" || client._state === "closed") {
8828
+ const reconnectCancellation2 = client._cancelReconnectAndWait();
8829
+ const connectionCancellation2 = this.cancelConnectionAttemptAndWait();
8830
+ await Promise.all([reconnectCancellation2, connectionCancellation2]);
8831
+ await pullInvalidation;
8832
+ await client._saveSeqTrackerState();
8833
+ this.runtime.lifecycle.setState("closed");
8834
+ client._resetSeqTrackingState();
8835
+ client._clientLog.debug(`close exit: elapsed=${Date.now() - tStart}ms reason=already_idle`);
8836
+ return;
8837
+ }
8838
+ const reconnectCancellation = client._cancelReconnectAndWait();
8839
+ const connectionCancellation = this.cancelConnectionAttemptAndWait();
8840
+ try {
8841
+ await client._transport.call("auth.logout", {});
8842
+ } catch (err) {
8843
+ client._clientLog.warn(`auth.logout during close failed: ${err instanceof Error ? err.message : String(err)}`);
8844
+ }
8845
+ await client._transport.close();
8846
+ await Promise.all([pullInvalidation, reconnectCancellation, connectionCancellation]);
8036
8847
  await client._saveSeqTrackerState();
8037
8848
  this.runtime.lifecycle.setState("closed");
8849
+ this.publishLifecycleStopEvent({ state: client._publicState(client._state) });
8038
8850
  client._resetSeqTrackingState();
8039
- client._clientLog.debug(`close exit: elapsed=${Date.now() - tStart}ms reason=already_idle`);
8040
- return;
8851
+ client._clientLog.debug(`close exit: elapsed=${Date.now() - tStart}ms`);
8852
+ } finally {
8853
+ const closing = client._dispatcher.close();
8854
+ if (!calledFromHandler) await closing;
8041
8855
  }
8042
- const reconnectCancellation = client._cancelReconnectAndWait();
8043
- const connectionCancellation = this.cancelConnectionAttemptAndWait();
8044
- try {
8045
- await client._transport.call("auth.logout", {});
8046
- } catch (err) {
8047
- client._clientLog.warn(`auth.logout during close failed: ${err instanceof Error ? err.message : String(err)}`);
8048
- }
8049
- await client._transport.close();
8050
- await Promise.all([pullInvalidation, reconnectCancellation, connectionCancellation]);
8051
- await client._saveSeqTrackerState();
8052
- this.runtime.lifecycle.setState("closed");
8053
- await this.publishLifecycleStopEvent({ state: client._publicState(client._state) });
8054
- client._resetSeqTrackingState();
8055
- client._clientLog.debug(`close exit: elapsed=${Date.now() - tStart}ms`);
8056
8856
  }, "close");
8057
8857
  }
8058
8858
  };
@@ -14849,6 +15649,14 @@ var PROTECTED_HEADERS_METHODS = /* @__PURE__ */ new Set([
14849
15649
  "message.thought.put",
14850
15650
  "group.thought.put"
14851
15651
  ]);
15652
+ function generateMessageId() {
15653
+ if (typeof crypto.randomUUID === "function") return `m-${crypto.randomUUID().replace(/-/g, "")}`;
15654
+ const bytes = new Uint8Array(16);
15655
+ crypto.getRandomValues(bytes);
15656
+ bytes[6] = bytes[6] & 15 | 64;
15657
+ bytes[8] = bytes[8] & 63 | 128;
15658
+ return `m-${Array.from(bytes, (value) => value.toString(16).padStart(2, "0")).join("")}`;
15659
+ }
14852
15660
  var SIGNED_METHODS = /* @__PURE__ */ new Set([
14853
15661
  "message.send",
14854
15662
  "message.v2.put_peer_pk",
@@ -15111,6 +15919,7 @@ var RpcPipeline = class {
15111
15919
  __publicField(this, "runtime");
15112
15920
  __publicField(this, "pullGateStates", /* @__PURE__ */ new Map());
15113
15921
  __publicField(this, "inlineRealtimeScopes", /* @__PURE__ */ new Set());
15922
+ __publicField(this, "pullWorkTokens", /* @__PURE__ */ new Set());
15114
15923
  __publicField(this, "pullGeneration", 0);
15115
15924
  __publicField(this, "pullInvalidationWait", null);
15116
15925
  this.runtime = runtime;
@@ -15318,6 +16127,9 @@ var RpcPipeline = class {
15318
16127
  delete p._rpc_background;
15319
16128
  if (method === "message.send" || method === "group.send") {
15320
16129
  this.normalizeOutboundMessagePayload(p, method);
16130
+ if (!Object.prototype.hasOwnProperty.call(p, "message_id") || p.message_id == null) {
16131
+ p.message_id = generateMessageId();
16132
+ }
15321
16133
  }
15322
16134
  this.normalizeGroupCallIdentifier(method, p);
15323
16135
  this.validateOutboundCall(method, p);
@@ -15622,6 +16434,28 @@ var RpcPipeline = class {
15622
16434
  const matches = (job) => job !== null && job.namespace === ns;
15623
16435
  return matches(state.active) || state.foreground.some(matches) || state.background.some(matches);
15624
16436
  }
16437
+ hasAnyPullActivity() {
16438
+ if (this.pullWorkTokens.size > 0) return true;
16439
+ for (const state of this.pullGateStates.values()) {
16440
+ if (state.active || state.foreground.length > 0 || state.background.length > 0) return true;
16441
+ }
16442
+ return false;
16443
+ }
16444
+ reservePullWork() {
16445
+ const token = Symbol("pull-work");
16446
+ this.pullWorkTokens.add(token);
16447
+ let released = false;
16448
+ return () => {
16449
+ if (released) return;
16450
+ released = true;
16451
+ this.pullWorkTokens.delete(token);
16452
+ this.runtime.client._delivery?.onPullWorkSettled?.();
16453
+ };
16454
+ }
16455
+ releasePullWorkToken(token) {
16456
+ if (!this.pullWorkTokens.delete(token)) return;
16457
+ this.runtime.client._delivery?.onPullWorkSettled?.();
16458
+ }
15625
16459
  async tryRunInlineRealtime(ns, seq2, operation) {
15626
16460
  const normalized = String(ns ?? "").trim();
15627
16461
  if (!normalized.startsWith("p2p:") && !normalized.startsWith("group:") || !Number.isSafeInteger(seq2) || seq2 <= 0 || typeof operation !== "function") {
@@ -15663,7 +16497,8 @@ var RpcPipeline = class {
15663
16497
  invalidated: false,
15664
16498
  cancel: null,
15665
16499
  settled: Promise.resolve(),
15666
- settledResolve: null
16500
+ settledResolve: null,
16501
+ workToken: Symbol("inline-pull-work")
15667
16502
  };
15668
16503
  this.inlineRealtimeScopes.add(normalized);
15669
16504
  try {
@@ -15725,6 +16560,7 @@ var RpcPipeline = class {
15725
16560
  if (job.running) {
15726
16561
  waits.push(job.settled);
15727
16562
  } else {
16563
+ this.releasePullWorkToken(job.workToken);
15728
16564
  job.settledResolve?.();
15729
16565
  job.settledResolve = null;
15730
16566
  }
@@ -15737,6 +16573,7 @@ var RpcPipeline = class {
15737
16573
  this.drainPullGate(state);
15738
16574
  }
15739
16575
  const wait = Promise.all(waits).then(() => void 0);
16576
+ this.runtime.client._delivery?.onPullWorkSettled?.();
15740
16577
  let tracked;
15741
16578
  tracked = wait.finally(() => {
15742
16579
  if (this.pullInvalidationWait === tracked) this.pullInvalidationWait = null;
@@ -15751,7 +16588,18 @@ var RpcPipeline = class {
15751
16588
  if (this.pullInvalidationInProgress()) {
15752
16589
  throw new Error("pull invalidated");
15753
16590
  }
15754
- if (!key) return await this.executePullOperation(operation, background);
16591
+ if (!key) {
16592
+ this.runtime.client._delivery?.onPullStarted?.("");
16593
+ const releasePullWork = this.reservePullWork();
16594
+ try {
16595
+ return await this.executePullOperation(operation, background);
16596
+ } catch (error) {
16597
+ this.runtime.client._delivery?.onPullAborted?.();
16598
+ throw error;
16599
+ } finally {
16600
+ releasePullWork();
16601
+ }
16602
+ }
15755
16603
  const state = this.pullGateState(key);
15756
16604
  const namespace = this.pullScopeKey(key);
15757
16605
  const candidate = state.byKey.get(key);
@@ -15765,6 +16613,8 @@ var RpcPipeline = class {
15765
16613
  }
15766
16614
  return await existing.promise;
15767
16615
  }
16616
+ const workToken = Symbol("pull-work");
16617
+ this.pullWorkTokens.add(workToken);
15768
16618
  let resolve;
15769
16619
  let reject;
15770
16620
  let settledResolve;
@@ -15795,7 +16645,8 @@ var RpcPipeline = class {
15795
16645
  invalidated: false,
15796
16646
  cancel: null,
15797
16647
  settled,
15798
- settledResolve
16648
+ settledResolve,
16649
+ workToken
15799
16650
  };
15800
16651
  (background ? state.background : state.foreground).push(job);
15801
16652
  state.byKey.set(key, job);
@@ -15869,6 +16720,7 @@ var RpcPipeline = class {
15869
16720
  const job = takeRunnable(state.foreground) ?? takeRunnable(state.background);
15870
16721
  if (!job) return;
15871
16722
  state.active = job;
16723
+ this.runtime.client._delivery?.onPullStarted?.(job.namespace);
15872
16724
  if (job.timer) clearTimeout(job.timer);
15873
16725
  job.pullingStartedAt = Date.now();
15874
16726
  job.timedOut = false;
@@ -15882,14 +16734,17 @@ var RpcPipeline = class {
15882
16734
  return;
15883
16735
  }
15884
16736
  job.running = true;
16737
+ let failed = false;
15885
16738
  void this.executePullOperation(job.operation, job.background).then(
15886
16739
  (value) => {
15887
16740
  if (!job.timedOut && !job.invalidated) job.resolve(value);
15888
16741
  },
15889
16742
  (error) => {
16743
+ failed = true;
15890
16744
  if (!job.timedOut && !job.invalidated) job.reject(error);
15891
16745
  }
15892
16746
  ).finally(() => {
16747
+ this.pullWorkTokens.delete(job.workToken);
15893
16748
  state.lifecycle.set(job.namespace, "idle");
15894
16749
  if (state.active === job) state.active = null;
15895
16750
  if (state.byKey.get(job.key) === job) state.byKey.delete(job.key);
@@ -15897,6 +16752,8 @@ var RpcPipeline = class {
15897
16752
  job.settledResolve?.();
15898
16753
  job.settledResolve = null;
15899
16754
  if (!job.invalidated) this.runtime.client._delivery?.onPullGateIdle?.(job.namespace);
16755
+ if (failed || job.invalidated) this.runtime.client._delivery?.onPullAborted?.();
16756
+ this.runtime.client._delivery?.onPullWorkSettled?.();
15900
16757
  });
15901
16758
  }
15902
16759
  armQueuedPullTimers(_state) {
@@ -23793,11 +24650,22 @@ var V2E2EECoordinator = class {
23793
24650
  const fetchKey = this.pendingSenderIKFetchKey(fromAid, senderDeviceId, groupId);
23794
24651
  if (!fromAid || client._v2SenderIKFetching.has(fetchKey)) return;
23795
24652
  client._v2SenderIKFetching.add(fetchKey);
23796
- client._safeAsync(this.resolveSenderIKPending(fromAid, senderDeviceId, groupId, fetchKey));
24653
+ const releasePullWork = client._rpcPipeline?.reservePullWork?.() ?? (() => {
24654
+ });
24655
+ const generation = client._delivery.captureInlineGeneration?.();
24656
+ client._safeAsync(this.resolveSenderIKPending(
24657
+ fromAid,
24658
+ senderDeviceId,
24659
+ groupId,
24660
+ fetchKey,
24661
+ generation
24662
+ ).finally(releasePullWork));
23797
24663
  }
23798
- async resolveSenderIKPending(fromAid, senderDeviceId, groupId, fetchKey) {
24664
+ async resolveSenderIKPending(fromAid, senderDeviceId, groupId, fetchKey, generation) {
23799
24665
  const client = this.client;
24666
+ const generationCurrent = () => generation === void 0 || client._delivery.isInlineGenerationCurrent?.(generation) !== false;
23800
24667
  try {
24668
+ if (!generationCurrent()) return;
23801
24669
  const session = client._v2Session;
23802
24670
  if (session && fromAid) {
23803
24671
  try {
@@ -23828,17 +24696,21 @@ var V2E2EECoordinator = class {
23828
24696
  await this.getV2SenderPubDer(fromAid, senderDeviceId);
23829
24697
  }
23830
24698
  }
24699
+ if (!generationCurrent()) return;
23831
24700
  const pendingItems = [...client._v2SenderIKPending.entries()].filter(([, entry]) => entry.fromAid === fromAid && entry.senderDeviceId === senderDeviceId && entry.groupId === groupId);
23832
24701
  for (const [key, entry] of pendingItems) {
24702
+ if (!generationCurrent()) return;
23833
24703
  let plaintext = null;
23834
24704
  const retryStatus = {};
23835
24705
  try {
23836
- 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");
23837
24707
  } catch (exc) {
24708
+ if (!generationCurrent()) return;
23838
24709
  client._clientLog.warn(`V2 sender IK pending retry raised: key=${key} err=${String(formatE2EEError(exc))}`);
23839
24710
  client._v2SenderIKPending.delete(key);
23840
24711
  continue;
23841
24712
  }
24713
+ if (!generationCurrent()) return;
23842
24714
  if (plaintext === null) {
23843
24715
  if (retryStatus.deferred) {
23844
24716
  client._clientLog.warn(`V2 pending retry still missing key material: key=${key}`);
@@ -23858,9 +24730,9 @@ var V2E2EECoordinator = class {
23858
24730
  if (entry.groupId) {
23859
24731
  plaintext = normalizeGroupMentionMode(plaintext, entry.msg, entry.msg.envelope_json);
23860
24732
  plaintext.group_id = entry.groupId;
23861
- 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");
23862
24734
  } else {
23863
- await client._publishPulledMessage("message.received", `p2p:${client._aid ?? ""}`, seq2, plaintext);
24735
+ await client._publishPulledMessage("message.received", `p2p:${client._aid ?? ""}`, seq2, plaintext, false, "pending_retry");
23864
24736
  }
23865
24737
  client._clientLog.debug(`V2 sender IK pending retry delivered: key=${key}`);
23866
24738
  }
@@ -23890,6 +24762,7 @@ var V2E2EECoordinator = class {
23890
24762
  return client.call("message.send", {
23891
24763
  to: toAid,
23892
24764
  payload: envelope,
24765
+ message_id: opts?.messageId,
23893
24766
  encrypt: false,
23894
24767
  _skip_send_result_envelope: true
23895
24768
  });
@@ -23936,9 +24809,9 @@ var V2E2EECoordinator = class {
23936
24809
  let plaintext = null;
23937
24810
  if (String(msg.version ?? "") === "v1") {
23938
24811
  const legacy = isJsonObject(msg.legacy_v1) ? msg.legacy_v1 : {};
23939
- const payload = legacy.payload;
24812
+ const payload = legacy.payload !== void 0 ? legacy.payload : msg.payload;
23940
24813
  const payloadType = isJsonObject(payload) ? String(payload.type ?? "").trim() : "";
23941
- 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)) {
23942
24815
  plaintext = {
23943
24816
  message_id: String(msg.message_id ?? ""),
23944
24817
  from: String(msg.from_aid ?? ""),
@@ -23950,6 +24823,9 @@ var V2E2EECoordinator = class {
23950
24823
  encrypted: false
23951
24824
  };
23952
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);
23953
24829
  }
23954
24830
  } else {
23955
24831
  plaintext = source === "inline_push" ? await client._decryptV2Message(msg, false, false, false, false) : await client._decryptV2Message(msg, false);
@@ -23966,7 +24842,8 @@ var V2E2EECoordinator = class {
23966
24842
  event.event,
23967
24843
  client._aid ? `p2p:${client._aid}` : "",
23968
24844
  seq2,
23969
- event.payload
24845
+ event.payload,
24846
+ source
23970
24847
  );
23971
24848
  } else {
23972
24849
  await client._publishAppEvent(event.event, event.payload, source);
@@ -23980,29 +24857,53 @@ var V2E2EECoordinator = class {
23980
24857
  let plaintext = null;
23981
24858
  if (String(msg.version ?? "") === "v1") {
23982
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
+ }
23983
24883
  if (payload === void 0 || payload === null) {
23984
24884
  if (history) return this.historyDecryptFailure(msg, "legacy payload is missing");
23985
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}`);
23986
24886
  return null;
23987
24887
  }
23988
- if (isJsonObject(payload) && ["e2ee.encrypted", "e2ee.group_encrypted"].includes(String(payload.type ?? ""))) {
23989
- if (history) return this.historyDecryptFailure(msg, "unsupported legacy encrypted envelope");
23990
- 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}`);
23991
- 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);
23992
24906
  }
23993
- plaintext = {
23994
- message_id: String(msg.message_id ?? ""),
23995
- from: String(msg.from_aid ?? ""),
23996
- group_id: groupId,
23997
- group_aid: groupAid,
23998
- seq: seq2,
23999
- type: String(msg.type ?? ""),
24000
- timestamp: msg.t_server,
24001
- payload,
24002
- encrypted: false
24003
- };
24004
- plaintext = normalizeGroupMentionMode(plaintext, msg, msg.envelope_json);
24005
- attachGatewayProximity(plaintext, msg);
24006
24907
  } else {
24007
24908
  plaintext = source === "inline_push" ? await client._decryptV2Message(msg, false, false, false, false) : await client._decryptV2Message(msg, false);
24008
24909
  if (plaintext) {
@@ -24025,7 +24926,8 @@ var V2E2EECoordinator = class {
24025
24926
  "group.message_created",
24026
24927
  `group:${groupId}`,
24027
24928
  seq2,
24028
- plaintext
24929
+ plaintext,
24930
+ source
24029
24931
  );
24030
24932
  } else {
24031
24933
  await client._publishAppEvent("group.message_created", plaintext, source);
@@ -24053,6 +24955,7 @@ var V2E2EECoordinator = class {
24053
24955
  const afterSeq = strictWindowSeq(params2.after_seq ?? 0, "after_seq");
24054
24956
  const limit = strictWindowLimit(params2.limit ?? V2_PULL_DEFAULT_PAGE_LIMIT);
24055
24957
  const ns = client._aid ? `p2p:${client._aid}` : "";
24958
+ if (ns) client._delivery.onPullStarted?.(ns);
24056
24959
  const result = await client._callRawV2Rpc("message.v2.pull", { window_mode: "tail", after_seq: afterSeq, limit, _rpc_foreground: true });
24057
24960
  const floor = Number(result.retention_floor_seq ?? 0);
24058
24961
  const previous = ns ? client._seqTracker.getSyncState(ns) : null;
@@ -24075,6 +24978,17 @@ var V2E2EECoordinator = class {
24075
24978
  }
24076
24979
  const ack = ns ? client._seqTracker.getContiguousSeq(ns) : 0;
24077
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?.();
24078
24992
  return { ...result, messages: decoded, raw_count: page.messages.length };
24079
24993
  }
24080
24994
  async historyV2Internal(params2) {
@@ -24089,10 +25003,24 @@ var V2E2EECoordinator = class {
24089
25003
  }
24090
25004
  return { ...result, messages: decoded };
24091
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
+ }
24092
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) {
24093
25020
  const client = this.client;
24094
25021
  await client._ensureV2SessionReady("message.pull");
24095
25022
  const ns = client._aid ? `p2p:${client._aid}` : "";
25023
+ if (ns) client._delivery.onPullStarted?.(ns);
24096
25024
  if (opts?.windowMode === "tail") {
24097
25025
  if (!opts.gateLocked) {
24098
25026
  const key = pullGateKeyForClient(client, "message.v2.pull", {
@@ -24100,7 +25028,7 @@ var V2E2EECoordinator = class {
24100
25028
  after_seq: afterSeq,
24101
25029
  limit
24102
25030
  }, `${ns}|tail|after=${afterSeq}|limit=${limit}`);
24103
- return await client._runPullSerialized(key, () => this.pullV2(afterSeq, limit, {
25031
+ return await client._runPullSerialized(key, () => this.pullV2Impl(afterSeq, limit, {
24104
25032
  ...opts ?? {},
24105
25033
  gateLocked: true
24106
25034
  }), false);
@@ -24114,7 +25042,7 @@ var V2E2EECoordinator = class {
24114
25042
  force: opts?.force === true,
24115
25043
  limit
24116
25044
  }, `${ns}|forward|after=${afterSeq}|force=${String(Boolean(opts?.force))}|limit=${limit}`);
24117
- return await client._runPullSerialized(key, () => this.pullV2(afterSeq, limit, {
25045
+ return await client._runPullSerialized(key, () => this.pullV2Impl(afterSeq, limit, {
24118
25046
  ...opts ?? {},
24119
25047
  gateLocked: true
24120
25048
  }), true);
@@ -24171,15 +25099,16 @@ var V2E2EECoordinator = class {
24171
25099
  const pageMaxSeq = seqs.length > 0 ? Math.max(...seqs) : nextAfterSeq;
24172
25100
  const deferredKeyFetches = /* @__PURE__ */ new Map();
24173
25101
  let blockedSeq = 0;
25102
+ const pageDecryptedBefore = decrypted.length;
24174
25103
  for (const msg of messages) {
24175
25104
  const seq2 = Number(msg.seq ?? 0);
24176
25105
  if (!Number.isFinite(seq2) || seq2 <= 0) continue;
24177
25106
  const version = String(msg.version ?? "v2");
24178
25107
  if (version === "v1") {
24179
25108
  const legacy = isJsonObject(msg.legacy_v1) ? msg.legacy_v1 : {};
24180
- const legacyPayload = legacy.payload;
25109
+ const legacyPayload = legacy.payload !== void 0 ? legacy.payload : msg.payload;
24181
25110
  const payloadType = isJsonObject(legacyPayload) ? String(legacyPayload.type ?? "").trim() : "";
24182
- 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)) {
24183
25112
  const v1Msg = {
24184
25113
  message_id: String(msg.message_id ?? ""),
24185
25114
  from: String(msg.from_aid ?? ""),
@@ -24193,8 +25122,32 @@ var V2E2EECoordinator = class {
24193
25122
  attachGatewayProximity(v1Msg, msg);
24194
25123
  const appEvent = client._delivery.p2pAppEventForMessage(v1Msg);
24195
25124
  if (ns) await client._publishPulledMessage(appEvent.event, ns, seq2, appEvent.payload, false);
24196
- else await client._publishAppEvent(appEvent.event, appEvent.payload);
25125
+ else await client._publishAppEvent(appEvent.event, appEvent.payload, "pull");
24197
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
+ }
24198
25151
  } else {
24199
25152
  client._clientLog.debug(`message.v2.pull skipping V1 envelope seq=${seq2} payload_type=${payloadType || "<none>"} (V1 E2EE removed)`);
24200
25153
  }
@@ -24209,7 +25162,7 @@ var V2E2EECoordinator = class {
24209
25162
  client._v2Session.trackOldSPKMaxSeq(msgSpkId, seq2);
24210
25163
  }
24211
25164
  const deferStatus = {};
24212
- 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");
24213
25166
  if (deferStatus.deferred) {
24214
25167
  blockedSeq = blockedSeq > 0 ? Math.min(blockedSeq, seq2) : seq2;
24215
25168
  }
@@ -24226,7 +25179,7 @@ var V2E2EECoordinator = class {
24226
25179
  await client._publishPulledMessage("message.received", ns, seq2, plaintext, false);
24227
25180
  decrypted.push(plaintext);
24228
25181
  } else {
24229
- await client._publishAppEvent("message.received", plaintext);
25182
+ await client._publishAppEvent("message.received", plaintext, "pull");
24230
25183
  decrypted.push(plaintext);
24231
25184
  }
24232
25185
  }
@@ -24281,7 +25234,7 @@ var V2E2EECoordinator = class {
24281
25234
  if (ackNeeded) {
24282
25235
  this.recordForwardAck(ns, ackSeq);
24283
25236
  const nextAfter2 = Math.max(pageMaxSeq, nextAfterSeq);
24284
- const canContinuePage = messages.length > 0 && nextAfter2 > nextAfterSeq;
25237
+ const canContinuePage = blockedSeq <= 0 && messages.length > 0 && nextAfter2 > nextAfterSeq;
24285
25238
  if (canContinuePage) {
24286
25239
  pendingAckSeq = Math.max(pendingAckSeq, ackSeq);
24287
25240
  lastAutoAckSeq = Math.max(lastAutoAckSeq, ackSeq);
@@ -24291,6 +25244,16 @@ var V2E2EECoordinator = class {
24291
25244
  }
24292
25245
  }
24293
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
+ );
24294
25257
  const nextAfter = Math.max(pageMaxSeq, nextAfterSeq);
24295
25258
  const rawCount = messages.length;
24296
25259
  if (blockedSeq > 0) break;
@@ -24325,6 +25288,7 @@ var V2E2EECoordinator = class {
24325
25288
  pendingAckSeq = 0;
24326
25289
  }
24327
25290
  }
25291
+ client._delivery.onPullWorkSettled?.();
24328
25292
  return decrypted;
24329
25293
  }
24330
25294
  async confirmForwardP2PAck(upToSeq, inlineGeneration) {
@@ -24402,7 +25366,8 @@ var V2E2EECoordinator = class {
24402
25366
  });
24403
25367
  return client.call("group.v2.send", withExplicitGroupAid({
24404
25368
  group_id: gid,
24405
- envelope
25369
+ envelope,
25370
+ message_id: opts?.messageId
24406
25371
  }, groupAid));
24407
25372
  };
24408
25373
  try {
@@ -24461,6 +25426,11 @@ var V2E2EECoordinator = class {
24461
25426
  const afterSeq = strictWindowSeq(params2.after_seq ?? 0, "after_seq");
24462
25427
  const limit = strictWindowLimit(params2.limit ?? V2_PULL_DEFAULT_PAGE_LIMIT);
24463
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);
24464
25434
  const result = await client._callRawV2Rpc("group.v2.pull", withExplicitGroupAid({
24465
25435
  group_id: groupId,
24466
25436
  window_mode: "tail",
@@ -24490,6 +25460,17 @@ var V2E2EECoordinator = class {
24490
25460
  }
24491
25461
  const ack = client._seqTracker.getContiguousSeq(ns);
24492
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?.();
24493
25474
  return { ...result, messages: decoded, raw_count: page.messages.length };
24494
25475
  }
24495
25476
  async historyGroupV2Internal(params2) {
@@ -24522,6 +25503,13 @@ var V2E2EECoordinator = class {
24522
25503
  });
24523
25504
  }
24524
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) {
24525
25513
  const client = this.client;
24526
25514
  await client._ensureV2SessionReady("group.pull");
24527
25515
  const gid = String(groupId ?? "").trim();
@@ -24537,12 +25525,13 @@ var V2E2EECoordinator = class {
24537
25525
  limit,
24538
25526
  _group_cursor_params: opts?.cursorParams
24539
25527
  }, `${ns}|tail|after=${afterSeq}|limit=${limit}`);
24540
- return await client._runPullSerialized(key, () => this.pullGroupV2(gid, afterSeq, limit, {
25528
+ return await client._runPullSerialized(key, () => this.pullGroupV2Impl(gid, afterSeq, limit, {
24541
25529
  ...opts ?? {},
24542
25530
  gateLocked: true
24543
25531
  }), false);
24544
25532
  }
24545
25533
  const result = await this.pullGroupV2TailInternal({
25534
+ ...opts?.cursorParams ?? {},
24546
25535
  group_id: String(opts.wireGroupId ?? gid),
24547
25536
  group_aid: groupAid || void 0,
24548
25537
  window_mode: "tail",
@@ -24559,7 +25548,7 @@ var V2E2EECoordinator = class {
24559
25548
  limit,
24560
25549
  _group_cursor_params: opts?.cursorParams
24561
25550
  }, `${ns}|forward|after=${afterSeq}|force=${String(Boolean(opts?.force))}|limit=${limit}`);
24562
- return await client._runPullSerialized(key, () => this.pullGroupV2(gid, afterSeq, limit, {
25551
+ return await client._runPullSerialized(key, () => this.pullGroupV2Impl(gid, afterSeq, limit, {
24563
25552
  ...opts ?? {},
24564
25553
  gateLocked: true
24565
25554
  }), true);
@@ -24568,6 +25557,7 @@ var V2E2EECoordinator = class {
24568
25557
  const wireGroupId = String(opts?.wireGroupId ?? groupId ?? "").trim() || gid;
24569
25558
  const cursorParams = opts?.cursorParams ?? {};
24570
25559
  const ownsCursor = opts?.ownsCursor !== false;
25560
+ if (ownsCursor) client._delivery.onPullStarted?.(ns);
24571
25561
  let pullGateKey = ownsCursor ? pullGateKeyForClient(client, "group.v2.pull", {
24572
25562
  group_id: gid,
24573
25563
  after_seq: afterSeq,
@@ -24628,26 +25618,28 @@ var V2E2EECoordinator = class {
24628
25618
  const pageMaxSeq = seqs.length > 0 ? Math.max(...seqs) : nextAfterSeq;
24629
25619
  const deferredKeyFetches = /* @__PURE__ */ new Map();
24630
25620
  let blockedSeq = 0;
25621
+ const pageDecryptedBefore = decrypted.length;
24631
25622
  for (const msg of messages) {
24632
25623
  const seq2 = Number(msg.seq ?? 0);
24633
25624
  if (!Number.isFinite(seq2) || seq2 <= 0) continue;
24634
25625
  const version = String(msg.version ?? "v2");
24635
25626
  if (version === "v1") {
24636
- 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;
24637
25629
  const payloadObj = isJsonObject(payload) ? payload : null;
24638
- if (client._delivery.recallEventFromGroupMessage(msg)) {
25630
+ if (typeof client._delivery?.recallEventFromGroupMessage === "function" && client._delivery.recallEventFromGroupMessage(msg)) {
24639
25631
  await client._delivery.publishGroupRecallTombstone(gid, seq2, {
24640
25632
  ...msg,
24641
25633
  group_id: gid,
24642
25634
  group_aid: eventGroupAid
24643
- });
25635
+ }, "pull");
24644
25636
  client._markPublishedSeq(ns, seq2);
24645
25637
  client._clientLog.debug(`group.v2.pull recall tombstone delivered: group=${gid}, seq=${seq2}`);
24646
25638
  continue;
24647
25639
  }
24648
25640
  if (payloadObj) {
24649
25641
  const payloadType = String(payloadObj.type ?? "").trim();
24650
- if (payloadType !== "e2ee.encrypted" && payloadType !== "e2ee.group_encrypted") {
25642
+ if (payloadType !== "e2ee.encrypted" && payloadType !== "e2ee.group_encrypted" && payloadType !== "e2ee.p2p_encrypted") {
24651
25643
  let v1Msg = {
24652
25644
  message_id: String(msg.message_id ?? ""),
24653
25645
  from: String(msg.from_aid ?? ""),
@@ -24683,6 +25675,32 @@ var V2E2EECoordinator = class {
24683
25675
  decrypted.push(v1Msg);
24684
25676
  continue;
24685
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
+ }
24686
25704
  client._clientLog.debug(`group.v2.pull skipping V1 envelope group=${gid} seq=${seq2} payload_type=${payloadObj ? String(payloadObj.type ?? "") : "<none>"} (V1 E2EE removed)`);
24687
25705
  continue;
24688
25706
  }
@@ -24691,7 +25709,7 @@ var V2E2EECoordinator = class {
24691
25709
  continue;
24692
25710
  }
24693
25711
  const deferStatus = {};
24694
- 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");
24695
25713
  if (deferStatus.deferred) {
24696
25714
  blockedSeq = blockedSeq > 0 ? Math.min(blockedSeq, seq2) : seq2;
24697
25715
  }
@@ -24763,6 +25781,16 @@ var V2E2EECoordinator = class {
24763
25781
  for (const fetch2 of deferredKeyFetches.values()) {
24764
25782
  this.scheduleSenderIKFetch(fetch2.fromAid, fetch2.senderDeviceId, fetch2.groupId);
24765
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
+ );
24766
25794
  if (ownsCursor && parsedCursorCurrentSeq !== null && cursorCurrentSeq > ackSeq) {
24767
25795
  this.recordForwardCursor(ns, cursorCurrentSeq, ackSeq);
24768
25796
  }
@@ -24771,7 +25799,7 @@ var V2E2EECoordinator = class {
24771
25799
  if (ackNeeded) {
24772
25800
  this.recordForwardAck(ns, ackSeq);
24773
25801
  const nextAfter2 = Math.max(pageMaxSeq, nextAfterSeq);
24774
- const canContinuePage = messages.length > 0 && nextAfter2 > nextAfterSeq && ownsCursor;
25802
+ const canContinuePage = blockedSeq <= 0 && messages.length > 0 && nextAfter2 > nextAfterSeq && ownsCursor;
24775
25803
  if (canContinuePage) {
24776
25804
  pendingAckSeq = Math.max(pendingAckSeq, ackSeq);
24777
25805
  lastAutoAckSeq = Math.max(lastAutoAckSeq, ackSeq);
@@ -24817,6 +25845,7 @@ var V2E2EECoordinator = class {
24817
25845
  pendingAckSeq = 0;
24818
25846
  }
24819
25847
  }
25848
+ client._delivery.onPullWorkSettled?.();
24820
25849
  return decrypted;
24821
25850
  }
24822
25851
  async confirmForwardGroupAck(groupId, upToSeq, groupAid) {
@@ -25326,7 +26355,7 @@ var V2E2EECoordinator = class {
25326
26355
  }
25327
26356
  async decryptV2PushMessage(data) {
25328
26357
  if (!isJsonObject(data)) return null;
25329
- return await this.client._decryptV2Message(data);
26358
+ return await this.client._decryptV2Message(data, false, false, false, false, void 0, false, "inline_push");
25330
26359
  }
25331
26360
  async buildV2P2PEnvelope(opts) {
25332
26361
  const client = this.client;
@@ -25838,7 +26867,7 @@ var GroupStateCoordinator = class {
25838
26867
  if (!isJsonObject(data) || !client._v2Session) return;
25839
26868
  const groupId = groupIdFromRecord(data);
25840
26869
  if (!groupId) return;
25841
- await client._dispatcher.publish("group.v2.state_proposed", data);
26870
+ client._dispatcher.enqueue("group.v2.state_proposed", data);
25842
26871
  try {
25843
26872
  await client._v2ConfirmPendingProposal(groupId);
25844
26873
  } catch (exc) {
@@ -25850,7 +26879,7 @@ var GroupStateCoordinator = class {
25850
26879
  if (!isJsonObject(data) || !client._v2Session) return;
25851
26880
  const groupId = groupIdFromRecord(data);
25852
26881
  if (!groupId) return;
25853
- await client._dispatcher.publish("group.v2.state_retry_needed", data);
26882
+ client._dispatcher.enqueue("group.v2.state_retry_needed", data);
25854
26883
  try {
25855
26884
  await client._v2AutoProposeState(groupId, { leaderDelay: true });
25856
26885
  } catch (exc) {
@@ -25868,7 +26897,7 @@ var GroupStateCoordinator = class {
25868
26897
  }
25869
26898
  client._v2AutoProposeLastSnapshot?.delete?.(groupId);
25870
26899
  }
25871
- await client._dispatcher.publish("group.v2.state_confirmed", data);
26900
+ client._dispatcher.enqueue("group.v2.state_confirmed", data);
25872
26901
  }
25873
26902
  async publishV2GroupSecurityLevel(groupId, bootstrap) {
25874
26903
  const client = this.client;
@@ -25879,7 +26908,7 @@ var GroupStateCoordinator = class {
25879
26908
  const previous = securityLevels.get(gid);
25880
26909
  if (previous === level) return;
25881
26910
  securityLevels.set(gid, level);
25882
- await client._dispatcher.publish("group.v2.security_level", {
26911
+ client._dispatcher.enqueue("group.v2.security_level", {
25883
26912
  group_id: gid,
25884
26913
  level,
25885
26914
  warning: String(bootstrap.e2ee_security_warning ?? ""),
@@ -25974,7 +27003,7 @@ var GroupStateCoordinator = class {
25974
27003
  } catch {
25975
27004
  }
25976
27005
  client._clientLog.warn(`V2 state chain fork detected: group=${gid} local_chain=${localChain.slice(0, 16)}... server_chain=${serverChain.slice(0, 16)}...`);
25977
- await client._dispatcher.publish("group.v2.fork_detected", {
27006
+ client._dispatcher.enqueue("group.v2.fork_detected", {
25978
27007
  group_id: gid,
25979
27008
  local_chain: localChain,
25980
27009
  server_chain: serverChain
@@ -26508,7 +27537,7 @@ var GroupStateCoordinator = class {
26508
27537
  }
26509
27538
  if (mode !== "open" && mode !== "invite_code" && mode !== "invite_only") {
26510
27539
  client._clientLog.warn(`V2 state tamper detected: group=${groupId} pending_extra=${extra.sort().join(",")} mode=${mode}`);
26511
- await client._dispatcher.publish("group.v2.state_tampered", {
27540
+ client._dispatcher.enqueue("group.v2.state_tampered", {
26512
27541
  group_id: groupId,
26513
27542
  pending_extra: extra.sort(),
26514
27543
  mode
@@ -27458,6 +28487,7 @@ function buildDefaultAgentMd(aid, options = {}) {
27458
28487
  ].join("\n");
27459
28488
  }
27460
28489
  var HEAD_HTTP_TIMEOUT_MS = 15e3;
28490
+ var AGENT_MD_NEGATIVE_CACHE_TTL_MS = 6e4;
27461
28491
  var noopLogger = {
27462
28492
  error: () => {
27463
28493
  },
@@ -27798,6 +28828,24 @@ var AgentMdManager = class _AgentMdManager {
27798
28828
  ttl_days: Number(ttlDays) || 0
27799
28829
  };
27800
28830
  }
28831
+ const remoteMissingCached = String(before.remote_status ?? "").trim().toLowerCase() === "missing";
28832
+ if (!localFound && !remoteEtagCached && remoteMissingCached && _AgentMdManager.checkedAtFresh(checkedAtCached, ttlDays)) {
28833
+ return {
28834
+ aid: target,
28835
+ local_found: false,
28836
+ remote_found: false,
28837
+ local_etag: "",
28838
+ remote_etag: "",
28839
+ in_sync: false,
28840
+ needs_update: false,
28841
+ last_modified: "",
28842
+ status: 404,
28843
+ cached: true,
28844
+ verify_status: "",
28845
+ verify_error: "",
28846
+ ttl_days: Number(ttlDays) || 0
28847
+ };
28848
+ }
27801
28849
  const now = Date.now();
27802
28850
  let remote;
27803
28851
  try {
@@ -28287,14 +29335,16 @@ var AgentMdManager = class _AgentMdManager {
28287
29335
  async _scheduleFetchIfMissing(aid, record, source = "") {
28288
29336
  const target = String(aid ?? "").trim();
28289
29337
  if (!target || await this._hasLocalContent(target, record)) return;
29338
+ const cached = record ?? await this.loadRecord(target) ?? {};
29339
+ const checkedAt = Number(cached.checked_at ?? 0) || 0;
29340
+ if (String(cached.remote_status ?? "").trim().toLowerCase() === "missing" && checkedAt > 0 && Date.now() - checkedAt <= AGENT_MD_NEGATIVE_CACHE_TTL_MS) return;
28290
29341
  if (this._fetchInflight.has(target)) return;
28291
29342
  this._fetchInflight.add(target);
28292
29343
  try {
28293
29344
  await this.download(target);
28294
29345
  } catch (err) {
28295
29346
  await this.saveRecord(target, {
28296
- last_error: err instanceof Error ? err.message : String(err),
28297
- remote_status: "found"
29347
+ last_error: err instanceof Error ? err.message : String(err)
28298
29348
  });
28299
29349
  this._log.debug(`agent.md auto fetch failed: aid=${target} source=${source || "-"} err=${err instanceof Error ? err.message : String(err)}`);
28300
29350
  } finally {
@@ -28718,7 +29768,6 @@ var _AUNClient = class _AUNClient {
28718
29768
  __publicField(this, "_reconnectActive", false);
28719
29769
  __publicField(this, "_reconnectAbort", null);
28720
29770
  __publicField(this, "_reconnectTask", null);
28721
- __publicField(this, "_reconnectEventDispatchDepth", 0);
28722
29771
  __publicField(this, "_serverKicked", false);
28723
29772
  // 重连状态追踪(对齐 Python client.py)
28724
29773
  __publicField(this, "_nextRetryAt", null);
@@ -28898,7 +29947,7 @@ var _AUNClient = class _AUNClient {
28898
29947
  });
28899
29948
  for (const evt of ["message.ack", "storage.object_changed", "stream/created", "stream/closed"]) {
28900
29949
  this._dispatcher.subscribe(`_raw.${evt}`, (data) => {
28901
- this._dispatcher.publish(evt, data);
29950
+ this._dispatcher.enqueue(evt, data);
28902
29951
  });
28903
29952
  }
28904
29953
  this._dispatcher.subscribe("_raw.gateway.disconnect", async (data) => {
@@ -29352,6 +30401,53 @@ var _AUNClient = class _AUNClient {
29352
30401
  await this._agentMdManager.upload(await this._agentMdManager.readContent(target) ?? buildDefaultAgentMd(target));
29353
30402
  return true;
29354
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
+ }
29355
30451
  async _runGroupIdentityOperation(groupId, operation) {
29356
30452
  const aid = String(this._aid ?? this._currentAid?.aid ?? "").trim();
29357
30453
  const dot = aid.indexOf(".");
@@ -29985,8 +31081,8 @@ var _AUNClient = class _AUNClient {
29985
31081
  _markPublishedSeq(ns, seq2) {
29986
31082
  this._delivery.markPublishedSeq(ns, seq2);
29987
31083
  }
29988
- async _publishAppEvent(event, payload) {
29989
- await this._delivery.publishAppEvent(event, payload);
31084
+ async _publishAppEvent(event, payload, source = "direct", ns = "", batch) {
31085
+ await this._delivery.publishAppEvent(event, payload, source, ns, batch);
29990
31086
  }
29991
31087
  _echoTimestamp() {
29992
31088
  const now = /* @__PURE__ */ new Date();
@@ -30024,11 +31120,11 @@ var _AUNClient = class _AUNClient {
30024
31120
  async _drainOrderedMessages(ns, beforeSeq, pullResponse = false, persist = true) {
30025
31121
  await this._delivery.drainOrderedMessages(ns, beforeSeq, pullResponse, persist);
30026
31122
  }
30027
- async _publishOrderedMessage(event, ns, seq2, payload) {
30028
- return this._delivery.publishOrderedMessage(event, ns, seq2, payload);
31123
+ async _publishOrderedMessage(event, ns, seq2, payload, source = "push", batch) {
31124
+ return this._delivery.publishOrderedMessage(event, ns, seq2, payload, source, batch);
30029
31125
  }
30030
- async _publishPulledMessage(event, ns, seq2, payload, persist = true) {
30031
- return this._delivery.publishPulledMessage(event, ns, seq2, payload, persist);
31126
+ async _publishPulledMessage(event, ns, seq2, payload, persist = true, source = "pull", batch) {
31127
+ return this._delivery.publishPulledMessage(event, ns, seq2, payload, persist, source, batch);
30032
31128
  }
30033
31129
  _extractGroupIdFromResult(result) {
30034
31130
  const group = isJsonObject(result.group) ? result.group : null;
@@ -30059,7 +31155,7 @@ var _AUNClient = class _AUNClient {
30059
31155
  const groupId = d.group_id ?? d.group_aid ?? "";
30060
31156
  await this._delivery.handleGroupChangedEventSeq(d, groupId);
30061
31157
  } else {
30062
- await this._dispatcher.publish("group.changed", data);
31158
+ this._dispatcher.enqueue("group.changed", data);
30063
31159
  }
30064
31160
  this._clientLog.debug(`_onRawGroupChanged exit: elapsed=${Date.now() - tStart}ms group_id=${groupIdInit}`);
30065
31161
  } catch (err) {
@@ -30132,7 +31228,7 @@ var _AUNClient = class _AUNClient {
30132
31228
  const ok = await ecdsaVerifyDer(pubKey, sigBytes, signData);
30133
31229
  if (!ok) {
30134
31230
  this._clientLog.warn(`group event sig verify failed aid=%s method=%s${sigAid} ${method}`);
30135
- this._dispatcher.publish("signature.verification_failed", {
31231
+ this._dispatcher.enqueue("signature.verification_failed", {
30136
31232
  aid: sigAid,
30137
31233
  method,
30138
31234
  error: "ECDSA verification failed"
@@ -30141,7 +31237,7 @@ var _AUNClient = class _AUNClient {
30141
31237
  return ok;
30142
31238
  } catch (exc) {
30143
31239
  this._clientLog.warn(`group event sig verify exception:${String(exc)}`);
30144
- this._dispatcher.publish("signature.verification_failed", {
31240
+ this._dispatcher.enqueue("signature.verification_failed", {
30145
31241
  aid: sigAid,
30146
31242
  method,
30147
31243
  error: String(exc)
@@ -30359,7 +31455,7 @@ var _AUNClient = class _AUNClient {
30359
31455
  throw new StateError("connection attempt superseded");
30360
31456
  }
30361
31457
  } else {
30362
- await this._dispatcher.publish("state_change", statePayload);
31458
+ this._dispatcher.enqueue("state_change", statePayload);
30363
31459
  }
30364
31460
  this._assertReconnectOwner(reconnectOwner);
30365
31461
  this._lifecycle.assertConnectionAttemptOwner(connectionOwner);
@@ -30383,6 +31479,7 @@ var _AUNClient = class _AUNClient {
30383
31479
  if (backgroundSyncEnabled) {
30384
31480
  this._safeAsync(this._fillP2pGap());
30385
31481
  }
31482
+ this._safeAsync(this._checkIdentityAdmissionAfterConnect());
30386
31483
  this._clientLog.debug(`_connectOnce exit: elapsed=${Date.now() - tStart}ms aid=${this._aid ?? "-"}`);
30387
31484
  } catch (err) {
30388
31485
  this._clientLog.debug(`_connectOnce exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
@@ -30676,7 +31773,7 @@ var _AUNClient = class _AUNClient {
30676
31773
  if (this._sessionParams && identity.access_token) {
30677
31774
  this._sessionParams.access_token = identity.access_token;
30678
31775
  }
30679
- await this._dispatcher.publish("token.refreshed", {
31776
+ this._dispatcher.enqueue("token.refreshed", {
30680
31777
  aid: identity.aid,
30681
31778
  expires_at: identity.access_token_expires_at
30682
31779
  });
@@ -30685,7 +31782,7 @@ var _AUNClient = class _AUNClient {
30685
31782
  if (exc instanceof AuthError) {
30686
31783
  if (authErrorRequiresRelogin(exc)) {
30687
31784
  this._clientLog.warn(`token refresh requires relogin, stopping refresh loop and triggering reconnect: ${exc.message}`);
30688
- await this._dispatcher.publish("token.refresh_exhausted", {
31785
+ this._dispatcher.enqueue("token.refresh_exhausted", {
30689
31786
  aid: this._identity?.aid ?? null,
30690
31787
  consecutive_failures: 1,
30691
31788
  last_error: String(exc),
@@ -30698,7 +31795,7 @@ var _AUNClient = class _AUNClient {
30698
31795
  this._tokenRefreshFailures++;
30699
31796
  if (this._tokenRefreshFailures >= 3) {
30700
31797
  this._clientLog.warn(`token refresh failed ${this._tokenRefreshFailures} consecutive times, stopping refresh loop and triggering reconnect`);
30701
- await this._dispatcher.publish("token.refresh_exhausted", {
31798
+ this._dispatcher.enqueue("token.refresh_exhausted", {
30702
31799
  aid: this._identity?.aid ?? null,
30703
31800
  consecutive_failures: this._tokenRefreshFailures,
30704
31801
  last_error: String(exc)
@@ -30709,7 +31806,7 @@ var _AUNClient = class _AUNClient {
30709
31806
  }
30710
31807
  this._clientLog.warn(`token refresh failed (${this._tokenRefreshFailures}/3), next retry: ${String(exc)}`);
30711
31808
  } else {
30712
- this._dispatcher.publish("connection.error", { error: formatCaughtError2(exc) });
31809
+ this._dispatcher.enqueue("connection.error", { error: formatCaughtError2(exc) });
30713
31810
  }
30714
31811
  }
30715
31812
  scheduleRefresh();
@@ -30738,7 +31835,7 @@ var _AUNClient = class _AUNClient {
30738
31835
  this._serverKicked = !retryable;
30739
31836
  this._lastDisconnectInfo = { code, reason, detail };
30740
31837
  try {
30741
- await this._dispatcher.publish("gateway.disconnect", { code, reason, detail });
31838
+ this._dispatcher.enqueue("gateway.disconnect", { code, reason, detail });
30742
31839
  } catch (exc) {
30743
31840
  this._clientLog.debug(`publish gateway.disconnect failed: ${exc?.message ?? exc}`);
30744
31841
  }
@@ -30757,7 +31854,7 @@ var _AUNClient = class _AUNClient {
30757
31854
  } catch (exc) {
30758
31855
  this._clientLog.debug(`transport cleanup skipped: ${formatCaughtError2(exc)}`);
30759
31856
  }
30760
- await this._dispatcher.publish("state_change", {
31857
+ this._dispatcher.enqueue("state_change", {
30761
31858
  state: this._publicState(this._state),
30762
31859
  error
30763
31860
  });
@@ -30793,7 +31890,7 @@ var _AUNClient = class _AUNClient {
30793
31890
  if (disconnectInfo.code !== void 0 && disconnectInfo.code !== null) {
30794
31891
  eventPayload.code = disconnectInfo.code;
30795
31892
  }
30796
- await this._dispatcher.publish("state_change", eventPayload);
31893
+ this._dispatcher.enqueue("state_change", eventPayload);
30797
31894
  if (this._reconnectAbort === reconnectAbort) {
30798
31895
  this._reconnectAbort = null;
30799
31896
  this._reconnectActive = false;
@@ -30824,12 +31921,7 @@ var _AUNClient = class _AUNClient {
30824
31921
  }
30825
31922
  async _publishReconnectEvent(owner, event, payload) {
30826
31923
  if (!this._ownsReconnect(owner)) return false;
30827
- this._reconnectEventDispatchDepth += 1;
30828
- try {
30829
- await this._dispatcher.publish(event, payload);
30830
- } finally {
30831
- this._reconnectEventDispatchDepth -= 1;
30832
- }
31924
+ this._dispatcher.enqueue(event, payload);
30833
31925
  return this._ownsReconnect(owner);
30834
31926
  }
30835
31927
  async _cancelReconnectAndWait() {
@@ -30842,7 +31934,7 @@ var _AUNClient = class _AUNClient {
30842
31934
  } catch (exc) {
30843
31935
  this._clientLog.debug(`reconnect cancellation transport cleanup skipped: ${formatCaughtError2(exc)}`);
30844
31936
  }
30845
- if (task && this._reconnectEventDispatchDepth === 0) {
31937
+ if (task) {
30846
31938
  try {
30847
31939
  await task;
30848
31940
  } catch (exc) {
@@ -31274,7 +32366,7 @@ var _AUNClient = class _AUNClient {
31274
32366
  * undecryptable 事件、成功消费后触发 SPK 轮换。inline Push 在游标提交和
31275
32367
  * Head/正文绑定校验前调用本入口时必须显式关闭这些副作用。
31276
32368
  */
31277
- 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") {
31278
32370
  const session = this._v2Session;
31279
32371
  if (!session) return null;
31280
32372
  const envJson = msg.envelope_json;
@@ -31362,10 +32454,11 @@ var _AUNClient = class _AUNClient {
31362
32454
  _decrypt_stage: "spk_lookup",
31363
32455
  _envelope_type: String(envelope.type ?? ""),
31364
32456
  _suite: String(envelope.suite ?? ""),
31365
- _spk_id: spkId
32457
+ _spk_id: spkId,
32458
+ source
31366
32459
  };
31367
32460
  attachV2EnvelopeMetadata2(event, e2eeMeta);
31368
- await this._dispatcher.publish(undecryptableEvent, event);
32461
+ this._dispatcher.enqueue(undecryptableEvent, event);
31369
32462
  } catch {
31370
32463
  }
31371
32464
  }
@@ -31403,10 +32496,11 @@ var _AUNClient = class _AUNClient {
31403
32496
  _decrypt_error: "sender_ik_not_found",
31404
32497
  _decrypt_stage: "sender_ik",
31405
32498
  _envelope_type: String(envelope.type ?? ""),
31406
- _suite: String(envelope.suite ?? "")
32499
+ _suite: String(envelope.suite ?? ""),
32500
+ source
31407
32501
  };
31408
32502
  attachV2EnvelopeMetadata2(event, e2eeMeta);
31409
- await this._dispatcher.publish(undecryptableEvent, event);
32503
+ this._dispatcher.enqueue(undecryptableEvent, event);
31410
32504
  } catch {
31411
32505
  }
31412
32506
  }
@@ -31439,10 +32533,11 @@ var _AUNClient = class _AUNClient {
31439
32533
  _decrypt_error: String(exc),
31440
32534
  _decrypt_stage: "decrypt",
31441
32535
  _envelope_type: String(envelope.type ?? ""),
31442
- _suite: String(envelope.suite ?? "")
32536
+ _suite: String(envelope.suite ?? ""),
32537
+ source
31443
32538
  };
31444
32539
  attachV2EnvelopeMetadata2(event, e2eeMeta);
31445
- await this._dispatcher.publish(undecryptableEvent, event);
32540
+ this._dispatcher.enqueue(undecryptableEvent, event);
31446
32541
  } catch {
31447
32542
  }
31448
32543
  }
@@ -32017,7 +33112,7 @@ var RegisterFlow = class _RegisterFlow {
32017
33112
  return new Promise((resolve, reject) => {
32018
33113
  let ws;
32019
33114
  try {
32020
- ws = new WebSocket(gatewayUrl);
33115
+ ws = createTransportWebSocket(gatewayUrl);
32021
33116
  } catch {
32022
33117
  reject(new AuthError(`WebSocket \u8FDE\u63A5\u5931\u8D25: ${gatewayUrl}`));
32023
33118
  return;
@@ -32051,7 +33146,19 @@ var RegisterFlow = class _RegisterFlow {
32051
33146
  if (!receivedChallenge) {
32052
33147
  if (!isJsonObject(msg) || msg.method !== "challenge") return;
32053
33148
  receivedChallenge = true;
32054
- ws.send(JSON.stringify({ jsonrpc: "2.0", id: requestId, method, params: params2 }));
33149
+ const sendResult = ws.send(JSON.stringify({ jsonrpc: "2.0", id: requestId, method, params: params2 }));
33150
+ if (sendResult && typeof sendResult.then === "function") {
33151
+ void Promise.resolve(sendResult).catch((error) => {
33152
+ if (settled) return;
33153
+ settled = true;
33154
+ globalThis.clearTimeout(timeout);
33155
+ try {
33156
+ ws.close();
33157
+ } catch {
33158
+ }
33159
+ reject(error instanceof Error ? error : new AuthError(String(error)));
33160
+ });
33161
+ }
32055
33162
  return;
32056
33163
  }
32057
33164
  if (!isJsonObject(msg) || msg.id !== requestId) return;