@agentunion/fastaun-browser 0.5.9 → 0.5.10

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 (50) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/_packed_docs/CHANGELOG.md +32 -0
  3. package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +6 -5
  4. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +43 -8
  5. package/_packed_docs/sdk/09-group-rpc-manual.md +18 -3
  6. package/_packed_docs/sdk/09-message-rpc-manual.md +34 -10
  7. package/dist/agent-md.d.ts.map +1 -1
  8. package/dist/agent-md.js +23 -1
  9. package/dist/agent-md.js.map +1 -1
  10. package/dist/auth.d.ts.map +1 -1
  11. package/dist/auth.js +16 -2
  12. package/dist/auth.js.map +1 -1
  13. package/dist/bundle.js +812 -205
  14. package/dist/client/delivery.d.ts +23 -7
  15. package/dist/client/delivery.d.ts.map +1 -1
  16. package/dist/client/delivery.js +221 -80
  17. package/dist/client/delivery.js.map +1 -1
  18. package/dist/client/group-state.js +6 -6
  19. package/dist/client/group-state.js.map +1 -1
  20. package/dist/client/lifecycle.d.ts.map +1 -1
  21. package/dist/client/lifecycle.js +34 -38
  22. package/dist/client/lifecycle.js.map +1 -1
  23. package/dist/client/rpc-pipeline.d.ts +4 -0
  24. package/dist/client/rpc-pipeline.d.ts.map +1 -1
  25. package/dist/client/rpc-pipeline.js +56 -2
  26. package/dist/client/rpc-pipeline.js.map +1 -1
  27. package/dist/client/v2-e2ee.d.ts +1 -1
  28. package/dist/client/v2-e2ee.d.ts.map +1 -1
  29. package/dist/client/v2-e2ee.js +22 -6
  30. package/dist/client/v2-e2ee.js.map +1 -1
  31. package/dist/client.d.ts +0 -1
  32. package/dist/client.d.ts.map +1 -1
  33. package/dist/client.js +23 -32
  34. package/dist/client.js.map +1 -1
  35. package/dist/events.d.ts +31 -2
  36. package/dist/events.d.ts.map +1 -1
  37. package/dist/events.js +167 -7
  38. package/dist/events.js.map +1 -1
  39. package/dist/register-flow.d.ts.map +1 -1
  40. package/dist/register-flow.js +16 -2
  41. package/dist/register-flow.js.map +1 -1
  42. package/dist/transport.d.ts +10 -1
  43. package/dist/transport.d.ts.map +1 -1
  44. package/dist/transport.js +283 -29
  45. package/dist/transport.js.map +1 -1
  46. package/dist/version.d.ts +1 -1
  47. package/dist/version.d.ts.map +1 -1
  48. package/dist/version.js +1 -1
  49. package/dist/version.js.map +1 -1
  50. 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.10";
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
  }
@@ -1156,6 +1302,154 @@ var _noopLog3 = { error: () => {
1156
1302
  }, debug: () => {
1157
1303
  } };
1158
1304
  var _rpcIdCounter = 0;
1305
+ var WORKER_WEBSOCKET_SOURCE = `
1306
+ let socket = null;
1307
+ self.onmessage = (event) => {
1308
+ const command = event.data || {};
1309
+ if (command.type === 'connect') {
1310
+ try {
1311
+ socket = new WebSocket(command.url);
1312
+ socket.onopen = () => self.postMessage({ type: 'open' });
1313
+ socket.onmessage = (message) => self.postMessage({ type: 'message', data: message.data });
1314
+ socket.onerror = () => self.postMessage({ type: 'error', message: 'websocket error' });
1315
+ socket.onclose = (close) => self.postMessage({
1316
+ type: 'close', code: close.code, reason: close.reason || '', wasClean: close.wasClean === true,
1317
+ });
1318
+ } catch (error) {
1319
+ self.postMessage({ type: 'error', message: error instanceof Error ? error.message : String(error) });
1320
+ }
1321
+ return;
1322
+ }
1323
+ if (command.type === 'send') {
1324
+ try {
1325
+ if (!socket || socket.readyState !== WebSocket.OPEN) throw new Error('websocket is not open');
1326
+ socket.send(command.data);
1327
+ self.postMessage({ type: 'send_result', id: command.id, ok: true });
1328
+ } catch (error) {
1329
+ self.postMessage({
1330
+ type: 'send_result', id: command.id, ok: false,
1331
+ error: error instanceof Error ? error.message : String(error),
1332
+ });
1333
+ }
1334
+ return;
1335
+ }
1336
+ if (command.type === 'close' && socket) socket.close(command.code, command.reason);
1337
+ };
1338
+ `;
1339
+ var _WorkerWebSocketProxy = class _WorkerWebSocketProxy {
1340
+ constructor(url) {
1341
+ __publicField(this, "readyState", _WorkerWebSocketProxy.CONNECTING);
1342
+ __publicField(this, "onopen", null);
1343
+ __publicField(this, "onmessage", null);
1344
+ __publicField(this, "onerror", null);
1345
+ __publicField(this, "onclose", null);
1346
+ __publicField(this, "_worker");
1347
+ __publicField(this, "_workerUrl");
1348
+ __publicField(this, "_listeners", /* @__PURE__ */ new Map());
1349
+ __publicField(this, "_pendingSends", /* @__PURE__ */ new Map());
1350
+ __publicField(this, "_sendSeq", 0);
1351
+ this._workerUrl = URL.createObjectURL(new Blob([WORKER_WEBSOCKET_SOURCE], { type: "text/javascript" }));
1352
+ try {
1353
+ this._worker = new Worker(this._workerUrl);
1354
+ } catch (error) {
1355
+ URL.revokeObjectURL(this._workerUrl);
1356
+ throw error;
1357
+ }
1358
+ this._worker.onmessage = (event) => this._handleWorkerMessage(event.data);
1359
+ this._worker.onerror = (event) => this._emitError(event.message || "websocket worker error");
1360
+ this._worker.postMessage({ type: "connect", url });
1361
+ }
1362
+ send(data) {
1363
+ if (this.readyState !== _WorkerWebSocketProxy.OPEN) return Promise.reject(new Error("websocket is not open"));
1364
+ const id = ++this._sendSeq;
1365
+ return new Promise((resolve, reject) => {
1366
+ this._pendingSends.set(id, { resolve, reject });
1367
+ try {
1368
+ this._worker.postMessage({ type: "send", id, data });
1369
+ } catch (error) {
1370
+ this._pendingSends.delete(id);
1371
+ reject(error instanceof Error ? error : new Error(String(error)));
1372
+ }
1373
+ });
1374
+ }
1375
+ close(code, reason) {
1376
+ if (this.readyState === _WorkerWebSocketProxy.CLOSED) return;
1377
+ this.readyState = _WorkerWebSocketProxy.CLOSING;
1378
+ this._worker.postMessage({ type: "close", code, reason });
1379
+ }
1380
+ addEventListener(type, listener) {
1381
+ const callback = typeof listener === "function" ? listener : (event) => listener.handleEvent(event);
1382
+ const listeners = this._listeners.get(type) ?? /* @__PURE__ */ new Set();
1383
+ listeners.add(callback);
1384
+ this._listeners.set(type, listeners);
1385
+ }
1386
+ removeEventListener(type, listener) {
1387
+ if (typeof listener === "function") this._listeners.get(type)?.delete(listener);
1388
+ }
1389
+ _handleWorkerMessage(reply) {
1390
+ if (reply.type === "send_result" && reply.id !== void 0) {
1391
+ const pending = this._pendingSends.get(reply.id);
1392
+ if (!pending) return;
1393
+ this._pendingSends.delete(reply.id);
1394
+ if (reply.ok) pending.resolve();
1395
+ else pending.reject(new Error(reply.error || "websocket send failed"));
1396
+ return;
1397
+ }
1398
+ if (reply.type === "open") {
1399
+ this.readyState = _WorkerWebSocketProxy.OPEN;
1400
+ const event = new Event("open");
1401
+ this.onopen?.(event);
1402
+ this._emit("open", event);
1403
+ return;
1404
+ }
1405
+ if (reply.type === "message") {
1406
+ const event = new MessageEvent("message", { data: reply.data });
1407
+ this.onmessage?.(event);
1408
+ this._emit("message", event);
1409
+ return;
1410
+ }
1411
+ if (reply.type === "error") {
1412
+ this._emitError(reply.message || "websocket worker error");
1413
+ return;
1414
+ }
1415
+ if (reply.type === "close") {
1416
+ this.readyState = _WorkerWebSocketProxy.CLOSED;
1417
+ for (const pending of this._pendingSends.values()) pending.reject(new Error("websocket closed"));
1418
+ this._pendingSends.clear();
1419
+ const event = new CloseEvent("close", {
1420
+ code: reply.code ?? 1006,
1421
+ reason: reply.reason ?? "",
1422
+ wasClean: reply.wasClean === true
1423
+ });
1424
+ this.onclose?.(event);
1425
+ this._emit("close", event);
1426
+ this._worker.terminate();
1427
+ URL.revokeObjectURL(this._workerUrl);
1428
+ }
1429
+ }
1430
+ _emitError(message) {
1431
+ const event = new ErrorEvent("error", { message });
1432
+ this.onerror?.(event);
1433
+ this._emit("error", event);
1434
+ }
1435
+ _emit(type, event) {
1436
+ for (const listener of [...this._listeners.get(type) ?? []]) listener(event);
1437
+ }
1438
+ };
1439
+ __publicField(_WorkerWebSocketProxy, "CONNECTING", 0);
1440
+ __publicField(_WorkerWebSocketProxy, "OPEN", 1);
1441
+ __publicField(_WorkerWebSocketProxy, "CLOSING", 2);
1442
+ __publicField(_WorkerWebSocketProxy, "CLOSED", 3);
1443
+ var WorkerWebSocketProxy = _WorkerWebSocketProxy;
1444
+ function createTransportWebSocket(url) {
1445
+ if (typeof Worker === "function" && typeof Blob === "function" && typeof URL !== "undefined" && typeof URL.createObjectURL === "function" && typeof URL.revokeObjectURL === "function") {
1446
+ try {
1447
+ return new WorkerWebSocketProxy(url);
1448
+ } catch {
1449
+ }
1450
+ }
1451
+ return new WebSocket(url);
1452
+ }
1159
1453
  var TRACE_SPAN_DETAIL_FIELDS = [
1160
1454
  "method",
1161
1455
  "route",
@@ -1426,6 +1720,10 @@ var RPCTransport = class {
1426
1720
  __publicField(this, "_traceMode", "off");
1427
1721
  // Trace observer:observer(traceInfo) 在每次 RPC/事件携带 _trace 时调用
1428
1722
  __publicField(this, "_traceObserver", null);
1723
+ // 每个 transport 实例一个轻量级网络 actor。命令只在此处启动网络操作,
1724
+ // 不等待 RPC 响应,避免应用回调中的 send 反向占住收包路径。
1725
+ __publicField(this, "_actorTail", Promise.resolve());
1726
+ __publicField(this, "_actorBusy", false);
1429
1727
  this._dispatcher = opts.eventDispatcher;
1430
1728
  this._timeout = opts.timeout ?? 10;
1431
1729
  this._connectTimeout = opts.timeout ?? 10;
@@ -1455,15 +1753,18 @@ var RPCTransport = class {
1455
1753
  setMetaObserver(observer) {
1456
1754
  this._metaObserver = observer;
1457
1755
  }
1458
- async _notifyMetaObserver(message) {
1459
- if (this._metaObserver === null) return;
1756
+ _notifyMetaObserver(message) {
1757
+ const observer = this._metaObserver;
1758
+ if (observer === null) return;
1460
1759
  const meta = message._meta;
1461
1760
  if (!isJsonObject(meta)) return;
1462
- try {
1463
- await this._metaObserver(meta);
1464
- } catch (exc) {
1465
- this._log.debug(`meta_observer raised: ${String(exc)}`);
1466
- }
1761
+ this._dispatcher.enqueueTask(async () => {
1762
+ try {
1763
+ await observer(meta);
1764
+ } catch (exc) {
1765
+ this._log.debug(`meta_observer raised: ${String(exc)}`);
1766
+ }
1767
+ });
1467
1768
  }
1468
1769
  /** 设置 trace 模式:off / log / diag */
1469
1770
  setTraceMode(mode) {
@@ -1499,7 +1800,10 @@ var RPCTransport = class {
1499
1800
  * 连接到 WebSocket URL。
1500
1801
  * 等待首条消息,若为 challenge 则返回,否则进入消息路由。
1501
1802
  */
1502
- async connect(url) {
1803
+ connect(url) {
1804
+ return this._enqueueActorStart(() => this._connectImpl(url));
1805
+ }
1806
+ async _connectImpl(url) {
1503
1807
  const tStart = Date.now();
1504
1808
  this._log.debug(`connect enter: url=${url}`);
1505
1809
  const setup = await this._withConnectionSetup(async () => {
@@ -1508,7 +1812,7 @@ var RPCTransport = class {
1508
1812
  this._lastCloseCode = null;
1509
1813
  this._lastCloseReason = "";
1510
1814
  const handshake = new Promise((resolve, reject) => {
1511
- const ws = new WebSocket(url);
1815
+ const ws = createTransportWebSocket(url);
1512
1816
  this._ws = ws;
1513
1817
  this._closed = false;
1514
1818
  let initialResolved = false;
@@ -1637,7 +1941,10 @@ var RPCTransport = class {
1637
1941
  return setup.handshake;
1638
1942
  }
1639
1943
  /** 关闭连接 */
1640
- async close() {
1944
+ close() {
1945
+ return this._enqueueActor(() => this._closeImpl());
1946
+ }
1947
+ async _closeImpl() {
1641
1948
  await this._withConnectionSetup(() => this._closeUnlocked());
1642
1949
  }
1643
1950
  /** 已持有连接建立串行权时关闭当前 WebSocket。 */
@@ -1708,7 +2015,13 @@ var RPCTransport = class {
1708
2015
  * 发起 JSON-RPC 2.0 调用。
1709
2016
  * 返回 result 字段的值;若有 error 字段则抛出映射后的错误。
1710
2017
  */
1711
- async call(method, params2, timeout, trace, background = false) {
2018
+ call(method, params2, timeout, trace, background = false) {
2019
+ return this._enqueueActorStart(
2020
+ () => this._callImpl(method, params2, timeout, trace, background),
2021
+ () => new TimeoutError(`rpc cancelled: ${method}`, { retryable: true })
2022
+ );
2023
+ }
2024
+ _callImpl(method, params2, timeout, trace, background = false) {
1712
2025
  if (this._closed || !this._ws) {
1713
2026
  throw this._notConnectedError();
1714
2027
  }
@@ -1749,7 +2062,7 @@ var RPCTransport = class {
1749
2062
  this._drainRpcQueue();
1750
2063
  }, effectiveTimeout);
1751
2064
  const pending = {
1752
- resolve: async (response) => {
2065
+ resolve: (response) => {
1753
2066
  clearTimeout(timer);
1754
2067
  const elapsed = Date.now() - tStart;
1755
2068
  if (response.error !== void 0) {
@@ -1767,7 +2080,7 @@ var RPCTransport = class {
1767
2080
  if (traceId) {
1768
2081
  this._log.info(`[trace=${traceId}] rpc_recv method=${method} rpc_id=${rpcId} duration_ms=${elapsed} status=ok`);
1769
2082
  }
1770
- await this._notifyMetaObserver(response);
2083
+ this._notifyMetaObserver(response);
1771
2084
  const respTrace = response._trace;
1772
2085
  if (respTrace && typeof respTrace === "object" && !Array.isArray(respTrace)) {
1773
2086
  this._handleResponseTrace(method, "ok", elapsed, respTrace);
@@ -1815,7 +2128,10 @@ var RPCTransport = class {
1815
2128
  return Object.assign(promise, { cancel: () => cancelRpc?.() });
1816
2129
  }
1817
2130
  /** 发送 JSON-RPC 2.0 Notification,不分配 id,也不等待响应。 */
1818
- async notify(method, params2) {
2131
+ notify(method, params2) {
2132
+ return this._enqueueActorStart(() => this._notifyImpl(method, params2));
2133
+ }
2134
+ async _notifyImpl(method, params2) {
1819
2135
  if (this._closed || !this._ws) {
1820
2136
  throw this._notConnectedError();
1821
2137
  }
@@ -1844,6 +2160,58 @@ var RPCTransport = class {
1844
2160
  });
1845
2161
  return run;
1846
2162
  }
2163
+ _enqueueActor(operation) {
2164
+ this._actorBusy = true;
2165
+ const run = this._actorTail.then(async () => {
2166
+ return await operation();
2167
+ }, async () => {
2168
+ return await operation();
2169
+ });
2170
+ const tail = run.then(() => void 0, () => void 0);
2171
+ this._actorTail = tail;
2172
+ void tail.then(() => {
2173
+ if (this._actorTail === tail) this._actorBusy = false;
2174
+ });
2175
+ return run;
2176
+ }
2177
+ _enqueueActorStart(operation, cancellationError) {
2178
+ let resolveResult;
2179
+ let rejectResult;
2180
+ const result = new Promise((resolve, reject) => {
2181
+ resolveResult = resolve;
2182
+ rejectResult = reject;
2183
+ });
2184
+ let cancel;
2185
+ let cancelRequested = false;
2186
+ const launch = () => {
2187
+ if (cancelRequested) return;
2188
+ try {
2189
+ const inner = operation();
2190
+ cancel = inner.cancel;
2191
+ inner.then(resolveResult, rejectResult);
2192
+ } catch (err) {
2193
+ rejectResult(err);
2194
+ }
2195
+ };
2196
+ if (this._actorBusy) {
2197
+ const gate = this._actorTail.then(launch, launch);
2198
+ const tail = gate.then(() => void 0, () => void 0);
2199
+ this._actorTail = tail;
2200
+ void tail.then(() => {
2201
+ if (this._actorTail === tail) this._actorBusy = false;
2202
+ });
2203
+ } else {
2204
+ launch();
2205
+ }
2206
+ return Object.assign(result, {
2207
+ cancel: () => {
2208
+ if (cancelRequested) return;
2209
+ cancelRequested = true;
2210
+ if (cancel) cancel();
2211
+ else rejectResult(cancellationError?.() ?? new TimeoutError("rpc cancelled", { retryable: true }));
2212
+ }
2213
+ });
2214
+ }
1847
2215
  _sendText(payload, context, beforeSend) {
1848
2216
  return this._enqueueSend(async () => {
1849
2217
  if (beforeSend && !beforeSend()) {
@@ -1854,7 +2222,7 @@ var RPCTransport = class {
1854
2222
  }
1855
2223
  const ws = this._ws;
1856
2224
  try {
1857
- ws.send(payload);
2225
+ await Promise.resolve(ws.send(payload));
1858
2226
  } catch (err) {
1859
2227
  throw await this._sendFailureError(context, err, ws);
1860
2228
  }
@@ -1978,7 +2346,14 @@ var RPCTransport = class {
1978
2346
  const enriched = { ...respTrace, spans };
1979
2347
  this._log.info(traceDisplay(method, status, elapsedMs, respTrace, spans));
1980
2348
  if (this._traceObserver !== null) {
1981
- this._traceObserver({ type: "rpc", method, trace: enriched, status, duration_ms: elapsedMs });
2349
+ const observer = this._traceObserver;
2350
+ this._dispatcher.enqueueTask(async () => {
2351
+ try {
2352
+ await observer({ type: "rpc", method, trace: enriched, status, duration_ms: elapsedMs });
2353
+ } catch (err) {
2354
+ this._log.debug(`trace observer raised: ${err instanceof Error ? err.message : String(err)}`);
2355
+ }
2356
+ });
1982
2357
  }
1983
2358
  } catch (err) {
1984
2359
  this._log.debug(`trace handling raised: ${err instanceof Error ? err.message : String(err)}`);
@@ -2019,10 +2394,17 @@ var RPCTransport = class {
2019
2394
  this._backgroundRpcQueue = [];
2020
2395
  if (!wasClosed) {
2021
2396
  const error = new ConnectionError(`websocket closed: code=${event.code} reason=${event.reason}`);
2022
- this._dispatcher.publish("connection.error", { error });
2023
2397
  if (this._onDisconnect) {
2024
- this._onDisconnect(error, event.code).catch((exc) => this._log.warn("[aun_core.transport] disconnect callback exception:", exc));
2398
+ const onDisconnect = this._onDisconnect;
2399
+ this._dispatcher.enqueueTask(async () => {
2400
+ try {
2401
+ await onDisconnect(error, event.code);
2402
+ } catch (exc) {
2403
+ this._log.warn("[aun_core.transport] disconnect callback exception:", exc);
2404
+ }
2405
+ });
2025
2406
  }
2407
+ this._dispatcher.enqueue("connection.error", { error });
2026
2408
  }
2027
2409
  }
2028
2410
  _notConnectedError() {
@@ -2050,39 +2432,43 @@ var RPCTransport = class {
2050
2432
  if (method === "challenge") {
2051
2433
  this._challenge = message;
2052
2434
  this._log.debug("challenge received");
2053
- this._dispatcher.publish("connection.challenge", message.params ?? {});
2435
+ this._dispatcher.enqueue("connection.challenge", message.params ?? {});
2054
2436
  return;
2055
2437
  }
2056
2438
  if (method.startsWith("event/")) {
2057
2439
  const protocolEvent = method.slice(6);
2058
2440
  const sdkEvent = EVENT_NAME_MAP[protocolEvent] ?? protocolEvent;
2059
2441
  this._log.debug(`event recv: event=${sdkEvent} ${summarizeDict(message.params, DIAG_RESULT_FIELDS)}`);
2060
- void this._notifyMetaObserver(message);
2442
+ this._notifyMetaObserver(message);
2061
2443
  const params2 = message.params ?? {};
2062
2444
  if ("_trace" in params2) {
2063
2445
  const eventTrace = params2._trace;
2064
2446
  delete params2._trace;
2065
2447
  if (eventTrace && typeof eventTrace === "object" && !Array.isArray(eventTrace)) {
2066
2448
  if (this._traceObserver !== null) {
2067
- try {
2068
- this._traceObserver({ type: "event", event: sdkEvent, trace: eventTrace });
2069
- } catch {
2070
- }
2449
+ const observer = this._traceObserver;
2450
+ const tracePayload = eventTrace;
2451
+ this._dispatcher.enqueueTask(async () => {
2452
+ try {
2453
+ await observer({ type: "event", event: sdkEvent, trace: tracePayload });
2454
+ } catch {
2455
+ }
2456
+ });
2071
2457
  }
2072
2458
  const traceObj = eventTrace;
2073
2459
  this._log.info(`[trace=${String(traceObj.trace_id ?? "")}] event_recv event=${sdkEvent}`);
2074
2460
  }
2075
2461
  }
2076
2462
  if (sdkEvent.startsWith("app.")) {
2077
- this._dispatcher.publish(sdkEvent, params2);
2463
+ this._dispatcher.enqueue(sdkEvent, params2);
2078
2464
  return;
2079
2465
  }
2080
- this._dispatcher.publish(`_raw.${sdkEvent}`, params2);
2466
+ this._dispatcher.enqueue(`_raw.${sdkEvent}`, params2);
2081
2467
  return;
2082
2468
  }
2083
- void this._notifyMetaObserver(message);
2469
+ this._notifyMetaObserver(message);
2084
2470
  this._log.debug(`notification recv: method=${method || "<no-method>"}`);
2085
- this._dispatcher.publish("notification", message);
2471
+ this._dispatcher.enqueue("notification", message);
2086
2472
  }
2087
2473
  _decodeMessage(raw) {
2088
2474
  if (isJsonObject(raw)) {
@@ -2806,7 +3192,7 @@ var _AuthFlow = class _AuthFlow {
2806
3192
  return new Promise((resolve, reject) => {
2807
3193
  let ws;
2808
3194
  try {
2809
- ws = new WebSocket(gatewayUrl);
3195
+ ws = createTransportWebSocket(gatewayUrl);
2810
3196
  } catch (e) {
2811
3197
  reject(new AuthError(`WebSocket \u8FDE\u63A5\u5931\u8D25: ${gatewayUrl}`));
2812
3198
  return;
@@ -2849,7 +3235,19 @@ var _AuthFlow = class _AuthFlow {
2849
3235
  params: params2
2850
3236
  });
2851
3237
  this._log.debug(`short RPC request full: ${JSON.stringify(redactRpcLogPayload(JSON.parse(requestPayload)))}`);
2852
- ws.send(requestPayload);
3238
+ const sendResult = ws.send(requestPayload);
3239
+ if (sendResult && typeof sendResult.then === "function") {
3240
+ void Promise.resolve(sendResult).catch((error) => {
3241
+ if (settled) return;
3242
+ settled = true;
3243
+ globalThis.clearTimeout(timeout);
3244
+ try {
3245
+ ws.close();
3246
+ } catch {
3247
+ }
3248
+ reject(error instanceof Error ? error : new AuthError(String(error)));
3249
+ });
3250
+ }
2853
3251
  return;
2854
3252
  }
2855
3253
  if (!isJsonObject(msg) || msg.id !== requestId) return;
@@ -4507,6 +4905,8 @@ function p2pAppEventFromPlainPullMessage(message) {
4507
4905
  var MessageDeliveryEngine = class {
4508
4906
  constructor(runtime) {
4509
4907
  __publicField(this, "runtime");
4908
+ __publicField(this, "pendingPullDeliveryChanges", null);
4909
+ __publicField(this, "deliveryGeneration", 0);
4510
4910
  __publicField(this, "realtimeTailResults", null);
4511
4911
  __publicField(this, "realtimeSyncing", null);
4512
4912
  __publicField(this, "pendingP2pPullUpper", null);
@@ -4526,6 +4926,8 @@ var MessageDeliveryEngine = class {
4526
4926
  }
4527
4927
  resetInlineAckState() {
4528
4928
  this.inlineGeneration += 1;
4929
+ this.deliveryGeneration += 1;
4930
+ this.pendingPullDeliveryChanges = null;
4529
4931
  void this.runtime.client._rpcPipeline?.invalidatePulls?.();
4530
4932
  this.realtimeSyncing = null;
4531
4933
  this.pendingP2pPullUpper = null;
@@ -4573,6 +4975,67 @@ var MessageDeliveryEngine = class {
4573
4975
  if (!ns) return;
4574
4976
  this.schedulePendingPullIfNeeded(ns, "pull-gate-idle");
4575
4977
  }
4978
+ onPullWorkSettled() {
4979
+ const pipeline = this.runtime.client._rpcPipeline;
4980
+ if (pipeline?.hasAnyPullActivity?.() === true) return;
4981
+ void this.flushPullDeliveryChanges();
4982
+ }
4983
+ deliveryChangeNamespace(event, payload, ns = "") {
4984
+ if (event === "message.received") return "p2p";
4985
+ if (event !== "group.message_created") return "";
4986
+ if (!isJsonObject(payload)) return "";
4987
+ const aid = String(this.runtime.client._aid ?? "").trim().toLowerCase();
4988
+ const dot = aid.indexOf(".");
4989
+ const localIssuer = dot > 0 ? aid.slice(dot + 1) : "";
4990
+ const fallback = ns.startsWith("group:") ? ns.slice("group:".length) : "";
4991
+ const groupAid = normalizeGroupAid(payload.group_aid ?? payload.group_id ?? fallback, { localIssuer });
4992
+ return groupAid.includes(".") ? `group:${groupAid}` : "";
4993
+ }
4994
+ isDeliverableMessageBody(event, payload) {
4995
+ if (event !== "message.received" && event !== "group.message_created" || !isJsonObject(payload)) return false;
4996
+ const messageId = typeof payload.message_id === "string" && payload.message_id.trim().length > 0;
4997
+ const seq2 = typeof payload.seq === "number" && Number.isSafeInteger(payload.seq) && payload.seq > 0;
4998
+ return messageId || seq2;
4999
+ }
5000
+ recordDeliveryChange(changes, event, payload, ns, generation) {
5001
+ if (generation !== this.deliveryGeneration || !this.isDeliverableMessageBody(event, payload)) return;
5002
+ const namespace = this.deliveryChangeNamespace(event, payload, ns);
5003
+ if (!namespace) return;
5004
+ changes.set(namespace, (changes.get(namespace) ?? 0) + 1);
5005
+ }
5006
+ deliveryChangesPayload(changes) {
5007
+ return [...changes.entries()].filter(([, deliveredCount]) => deliveredCount > 0).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([namespace, deliveredCount]) => ({ namespace, delivered_count: deliveredCount }));
5008
+ }
5009
+ createDeliveryChangeBatch(trigger) {
5010
+ return { generation: this.deliveryGeneration, trigger, changes: /* @__PURE__ */ new Map(), closed: false };
5011
+ }
5012
+ async flushRealtimeDeliveryChanges(batch) {
5013
+ if (batch.closed) return;
5014
+ batch.closed = true;
5015
+ if (batch.generation !== this.deliveryGeneration) return;
5016
+ if (this.deliveryChangesPayload(batch.changes).length === 0) return;
5017
+ const pendingPull = this.pendingPullDeliveryChanges;
5018
+ this.pendingPullDeliveryChanges = null;
5019
+ if (pendingPull) {
5020
+ for (const [namespace, count] of pendingPull) {
5021
+ batch.changes.set(namespace, (batch.changes.get(namespace) ?? 0) + count);
5022
+ }
5023
+ }
5024
+ if (batch.generation !== this.deliveryGeneration) return;
5025
+ const changes = this.deliveryChangesPayload(batch.changes);
5026
+ if (changes.length === 0) return;
5027
+ this.runtime.client._dispatcher.enqueue("delivery.changed", { trigger: batch.trigger, changes });
5028
+ }
5029
+ async flushPullDeliveryChanges() {
5030
+ const generation = this.deliveryGeneration;
5031
+ const pending = this.pendingPullDeliveryChanges;
5032
+ if (!pending || pending.size === 0 || generation !== this.deliveryGeneration) return;
5033
+ this.pendingPullDeliveryChanges = null;
5034
+ if (generation !== this.deliveryGeneration) return;
5035
+ const changes = this.deliveryChangesPayload(pending);
5036
+ if (changes.length === 0) return;
5037
+ this.runtime.client._dispatcher.enqueue("delivery.changed", { trigger: "pull_drained", changes });
5038
+ }
4576
5039
  recordPendingPull(ns, seq2) {
4577
5040
  if (!ns || !Number.isSafeInteger(seq2) || seq2 <= 0) return;
4578
5041
  const pending = ns.startsWith("p2p:") ? this.pendingP2pPullUpper ?? /* @__PURE__ */ new Map() : this.pendingGroupPullUpper ?? /* @__PURE__ */ new Map();
@@ -5147,14 +5610,14 @@ var MessageDeliveryEngine = class {
5147
5610
  }
5148
5611
  return { rawCount: messages.length, publishedCount };
5149
5612
  }
5150
- enqueueOrderedMessage(ns, event, seq2, payload) {
5613
+ enqueueOrderedMessage(ns, event, seq2, payload, source = "push") {
5151
5614
  const client = this.runtime.client;
5152
5615
  let queue = client._pendingOrderedMsgs.get(ns);
5153
5616
  if (!queue) {
5154
5617
  queue = /* @__PURE__ */ new Map();
5155
5618
  client._pendingOrderedMsgs.set(ns, queue);
5156
5619
  }
5157
- queue.set(seq2, { event, payload });
5620
+ queue.set(seq2, { event, payload, source });
5158
5621
  if (queue.size > PENDING_ORDERED_LIMIT) {
5159
5622
  const drop = [...queue.keys()].sort((a, b) => a - b).slice(0, queue.size - PENDING_ORDERED_LIMIT);
5160
5623
  for (const oldSeq of drop) queue.delete(oldSeq);
@@ -5163,7 +5626,7 @@ var MessageDeliveryEngine = class {
5163
5626
  isGroupEventNamespace(ns) {
5164
5627
  return ns.startsWith("group_event:");
5165
5628
  }
5166
- async publishOrderedQueueItem(ns, event, seq2, payload, pullResponse = false) {
5629
+ async publishOrderedQueueItem(ns, event, seq2, payload, pullResponse = false, source = "push", batch) {
5167
5630
  const client = this.runtime.client;
5168
5631
  if (event === "group.changed" && this.isGroupEventNamespace(ns)) {
5169
5632
  await this.publishOrderedGroupChanged(payload);
@@ -5174,10 +5637,10 @@ var MessageDeliveryEngine = class {
5174
5637
  return;
5175
5638
  }
5176
5639
  if (pullResponse) {
5177
- await client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload));
5640
+ await client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload, source, ns, batch));
5178
5641
  return;
5179
5642
  }
5180
- await client._publishAppEvent(event, payload);
5643
+ await client._publishAppEvent(event, payload, source, ns, batch);
5181
5644
  }
5182
5645
  async publishOrderedGroupChanged(payload) {
5183
5646
  const client = this.runtime.client;
@@ -5320,6 +5783,7 @@ var MessageDeliveryEngine = class {
5320
5783
  setIfPresent("type", firstValue(body.type, params2.type, params2.message_type, params2.payload_type));
5321
5784
  setIfPresent("kind", firstValue(body.kind, params2.kind));
5322
5785
  setIfPresent("version", firstValue(body.version, params2.version));
5786
+ setIfPresent("message_id", firstValue(params2.message_id, body.message_id, resultObj.message_id));
5323
5787
  setIfPresent("timestamp", firstValue(params2.timestamp, resultObj.timestamp, resultObj.created_at, resultObj.t_server, Date.now()));
5324
5788
  envelope.encrypted = Boolean(encrypted);
5325
5789
  const context = this.envelopeMetadata(params2.context);
@@ -5597,8 +6061,9 @@ var MessageDeliveryEngine = class {
5597
6061
  }
5598
6062
  if (contig !== contigBefore) this.persistSeq(ns);
5599
6063
  }
5600
- async publishAppEvent(event, payload) {
6064
+ async publishAppEvent(event, payload, source = "", ns = "", batch) {
5601
6065
  const client = this.runtime.client;
6066
+ const generation = this.deliveryGeneration;
5602
6067
  if ((event === "message.received" || event === "group.message_created") && isJsonObject(payload)) {
5603
6068
  client._maybeAppendEchoTraceReceive(payload);
5604
6069
  }
@@ -5617,7 +6082,19 @@ var MessageDeliveryEngine = class {
5617
6082
  client._clientLog.debug(`agent_md etag inject skipped: ${String(exc)}`);
5618
6083
  }
5619
6084
  }
5620
- await client._dispatcher.publish(event, this.normalizePublishedMessagePayload(event, payload));
6085
+ client._dispatcher.enqueue(event, this.normalizePublishedMessagePayload(event, payload));
6086
+ if (generation !== this.deliveryGeneration) return;
6087
+ if (source !== "pull" && source !== "tail" && source !== "pending_retry" && source !== "push" && source !== "inline_push") return;
6088
+ if (source === "push" || source === "inline_push") {
6089
+ const localBatch = batch ?? this.createDeliveryChangeBatch(source);
6090
+ this.recordDeliveryChange(localBatch.changes, event, payload, ns, generation);
6091
+ if (!batch) await this.flushRealtimeDeliveryChanges(localBatch);
6092
+ } else {
6093
+ const changes = this.pendingPullDeliveryChanges ?? /* @__PURE__ */ new Map();
6094
+ this.pendingPullDeliveryChanges = changes;
6095
+ this.recordDeliveryChange(changes, event, payload, ns, generation);
6096
+ if (client._rpcPipeline?.hasAnyPullActivity?.() !== true) await this.flushPullDeliveryChanges();
6097
+ }
5621
6098
  }
5622
6099
  messageTargetsCurrentInstance(message) {
5623
6100
  if (!isJsonObject(message)) return true;
@@ -5750,7 +6227,7 @@ var MessageDeliveryEngine = class {
5750
6227
  const client = this.runtime.client;
5751
6228
  try {
5752
6229
  if (!isJsonObject(data)) {
5753
- await client._publishAppEvent("message.received", data);
6230
+ await client._publishAppEvent("message.received", data, "push");
5754
6231
  return;
5755
6232
  }
5756
6233
  const msg = { ...data };
@@ -5795,7 +6272,7 @@ var MessageDeliveryEngine = class {
5795
6272
  await client._publishEncryptedPushMessage("message.received", "message.undecryptable", "", seq2 ?? 0, msg, false);
5796
6273
  return;
5797
6274
  }
5798
- await client._publishAppEvent("message.received", msg);
6275
+ await client._publishAppEvent("message.received", msg, "push");
5799
6276
  }
5800
6277
  } catch (exc) {
5801
6278
  client._clientLog.warn(`P2P push processing failed:${String(exc)}`);
@@ -5834,7 +6311,7 @@ var MessageDeliveryEngine = class {
5834
6311
  const client = this.runtime.client;
5835
6312
  try {
5836
6313
  if (!isJsonObject(data)) {
5837
- await client._publishAppEvent("group.message_created", data);
6314
+ await client._publishAppEvent("group.message_created", data, "push");
5838
6315
  return;
5839
6316
  }
5840
6317
  const msg = { ...data };
@@ -5906,7 +6383,7 @@ var MessageDeliveryEngine = class {
5906
6383
  await client._publishEncryptedPushMessage("group.message_created", "group.message_undecryptable", "", seq2 ?? 0, msg, true);
5907
6384
  return;
5908
6385
  }
5909
- await client._publishAppEvent("group.message_created", msg);
6386
+ await client._publishAppEvent("group.message_created", msg, "push");
5910
6387
  }
5911
6388
  } catch (exc) {
5912
6389
  client._clientLog.warn(`group push processing failed:${String(exc)}`);
@@ -5990,11 +6467,16 @@ var MessageDeliveryEngine = class {
5990
6467
  P2P_GAP_FILL_RETRY_MAX_MS
5991
6468
  );
5992
6469
  client._gapFillDone.add(retryKey);
6470
+ const releasePullWork = client._rpcPipeline?.reservePullWork?.() ?? (() => {
6471
+ });
5993
6472
  client._clientLog.debug(`P2P message gap-fill retry scheduled: ns=${ns} attempt=${retryAttempt} delay_ms=${delayMs}`);
5994
6473
  globalThis.setTimeout(() => {
5995
6474
  client._gapFillDone.delete(retryKey);
5996
- if (client.state !== "ready" /* READY */ || client._closing) return;
5997
- client._safeAsync(this.fillP2pGap(retryAttempt));
6475
+ if (client.state !== "ready" /* READY */ || client._closing) {
6476
+ releasePullWork();
6477
+ return;
6478
+ }
6479
+ client._safeAsync(this.fillP2pGap(retryAttempt).finally(releasePullWork));
5998
6480
  }, delayMs);
5999
6481
  }
6000
6482
  async fillP2pGap(retryAttempt = 0) {
@@ -6547,7 +7029,7 @@ var MessageDeliveryEngine = class {
6547
7029
  const tracker = client._seqTracker;
6548
7030
  const snapshot = typeof tracker.snapshotNamespace === "function" ? tracker.snapshotNamespace(ns) : null;
6549
7031
  try {
6550
- const published = await this.publishPulledMessage(event, ns, seq2, payload);
7032
+ const published = await this.publishPulledMessage(event, ns, seq2, payload, true, "inline_push");
6551
7033
  if (!this.isInlineGenerationCurrent(generation)) return false;
6552
7034
  if (!published) return false;
6553
7035
  const needsPull = Boolean(tracker.onMessageSeq(ns, seq2));
@@ -7035,13 +7517,12 @@ var MessageDeliveryEngine = class {
7035
7517
  }
7036
7518
  }
7037
7519
  } catch (exc) {
7038
- client._dispatcher.publish("seq_tracker.persist_error", {
7520
+ client._dispatcher.enqueue("seq_tracker.persist_error", {
7039
7521
  phase: "restore",
7040
7522
  aid,
7041
7523
  device_id: deviceId,
7042
7524
  slot_id: slotId,
7043
7525
  error: String(exc)
7044
- }).catch(() => {
7045
7526
  });
7046
7527
  }
7047
7528
  }
@@ -7232,13 +7713,12 @@ var MessageDeliveryEngine = class {
7232
7713
  } catch (exc) {
7233
7714
  const error = formatDeliveryError(exc);
7234
7715
  client._clientLog.warn(`save SeqTracker state failed: ${error}`);
7235
- client._dispatcher.publish("seq_tracker.persist_error", {
7716
+ client._dispatcher.enqueue("seq_tracker.persist_error", {
7236
7717
  phase: "save",
7237
7718
  aid,
7238
7719
  device_id: deviceId,
7239
7720
  slot_id: slotId,
7240
7721
  error: String(error)
7241
- }).catch(() => {
7242
7722
  });
7243
7723
  if (throwOnError) throw exc;
7244
7724
  }
@@ -7343,7 +7823,7 @@ var MessageDeliveryEngine = class {
7343
7823
  }
7344
7824
  return params2;
7345
7825
  }
7346
- async drainOrderedMessages(ns, beforeSeq, pullResponse = false, persist = true) {
7826
+ async drainOrderedMessages(ns, beforeSeq, pullResponse = false, persist = true, batch, source = "pull") {
7347
7827
  const client = this.runtime.client;
7348
7828
  this.ensurePullOperationCurrent();
7349
7829
  const queue = client._pendingOrderedMsgs.get(ns);
@@ -7351,15 +7831,39 @@ var MessageDeliveryEngine = class {
7351
7831
  const contig = client._seqTracker.getContiguousSeq(ns);
7352
7832
  const ready = [...queue.keys()].filter((seq2) => seq2 <= contig && (beforeSeq === void 0 || seq2 < beforeSeq)).sort((a, b) => a - b);
7353
7833
  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;
7834
+ const drainBatches = /* @__PURE__ */ new Map();
7835
+ try {
7836
+ for (const seq2 of ready) {
7837
+ this.ensurePullOperationCurrent();
7838
+ const item = queue.get(seq2);
7839
+ queue.delete(seq2);
7840
+ if (!item || client._pushedSeqs.get(ns)?.has(seq2)) continue;
7841
+ const itemSource = item.source ?? source;
7842
+ let itemBatch = batch;
7843
+ if ((itemSource === "push" || itemSource === "inline_push") && (!batch || batch.trigger !== itemSource)) {
7844
+ itemBatch = drainBatches.get(itemSource);
7845
+ if (!itemBatch) {
7846
+ itemBatch = this.createDeliveryChangeBatch(itemSource);
7847
+ drainBatches.set(itemSource, itemBatch);
7848
+ }
7849
+ }
7850
+ await this.publishOrderedQueueItem(
7851
+ ns,
7852
+ item.event,
7853
+ seq2,
7854
+ item.payload,
7855
+ pullResponse,
7856
+ itemSource,
7857
+ itemBatch
7858
+ );
7859
+ this.ensurePullOperationCurrent();
7860
+ this.markPublishedSeq(ns, seq2);
7861
+ delivered = true;
7862
+ }
7863
+ } finally {
7864
+ for (const drainBatch of drainBatches.values()) {
7865
+ await this.flushRealtimeDeliveryChanges(drainBatch);
7866
+ }
7363
7867
  }
7364
7868
  if (queue.size === 0) {
7365
7869
  client._pendingOrderedMsgs.delete(ns);
@@ -7369,75 +7873,87 @@ var MessageDeliveryEngine = class {
7369
7873
  }
7370
7874
  }
7371
7875
  }
7372
- async publishOrderedMessage(event, ns, seq2, payload) {
7876
+ async publishOrderedMessage(event, ns, seq2, payload, source = "push", operationBatch) {
7373
7877
  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);
7878
+ const ownsBatch = !operationBatch && (source === "push" || source === "inline_push");
7879
+ const batch = operationBatch ?? (ownsBatch ? this.createDeliveryChangeBatch(source) : void 0);
7880
+ try {
7881
+ const seqNum = Number(seq2);
7882
+ if (!Number.isFinite(seqNum) || !Number.isInteger(seqNum) || seqNum <= 0) {
7883
+ await this.publishOrderedQueueItem(ns, event, seqNum, payload, false, source, batch);
7884
+ return true;
7885
+ }
7886
+ if (client._pushedSeqs.get(ns)?.has(seqNum)) {
7887
+ const queue2 = client._pendingOrderedMsgs.get(ns);
7888
+ queue2?.delete(seqNum);
7889
+ if (queue2 && queue2.size === 0) client._pendingOrderedMsgs.delete(ns);
7890
+ return false;
7891
+ }
7892
+ const contig = client._seqTracker.getContiguousSeq(ns);
7893
+ if (seqNum > contig) {
7894
+ this.enqueueOrderedMessage(ns, event, seqNum, payload, source);
7895
+ return false;
7896
+ }
7897
+ await this.drainOrderedMessages(ns, seqNum, true, true, batch, source);
7898
+ if (client._pushedSeqs.get(ns)?.has(seqNum)) return false;
7899
+ const queue = client._pendingOrderedMsgs.get(ns);
7900
+ queue?.delete(seqNum);
7901
+ if (queue && queue.size === 0) client._pendingOrderedMsgs.delete(ns);
7902
+ await this.publishOrderedQueueItem(ns, event, seqNum, payload, false, source, batch);
7903
+ this.markPublishedSeq(ns, seqNum);
7904
+ await this.drainOrderedMessages(ns, void 0, false, true, batch, source);
7905
+ if (!client._pendingOrderedMsgs.get(ns)) await this.saveSeqTrackerState();
7377
7906
  return true;
7907
+ } finally {
7908
+ if (ownsBatch && batch) await this.flushRealtimeDeliveryChanges(batch);
7378
7909
  }
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
7910
  }
7401
- async publishPulledMessage(event, ns, seq2, payload, persist = true) {
7911
+ async publishPulledMessage(event, ns, seq2, payload, persist = true, source = "pull", operationBatch) {
7402
7912
  const client = this.runtime.client;
7403
- this.ensurePullOperationCurrent();
7404
- const seqNum = Number(seq2);
7405
- if (!Number.isFinite(seqNum) || !Number.isInteger(seqNum) || seqNum <= 0 || !ns) {
7913
+ const ownsBatch = !operationBatch && (source === "push" || source === "inline_push");
7914
+ const batch = operationBatch ?? (ownsBatch ? this.createDeliveryChangeBatch(source) : void 0);
7915
+ try {
7916
+ this.ensurePullOperationCurrent();
7917
+ const seqNum = Number(seq2);
7918
+ if (!Number.isFinite(seqNum) || !Number.isInteger(seqNum) || seqNum <= 0 || !ns) {
7919
+ if (event === "message.recalled") {
7920
+ const published = await client._withPullResponseProcessing(
7921
+ ns,
7922
+ () => this.publishMessageRecallTombstone(seq2, payload)
7923
+ );
7924
+ this.ensurePullOperationCurrent();
7925
+ return published;
7926
+ }
7927
+ await client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload, source, ns, batch));
7928
+ this.ensurePullOperationCurrent();
7929
+ return true;
7930
+ }
7931
+ const queue = client._pendingOrderedMsgs.get(ns);
7932
+ if (client._pushedSeqs.get(ns)?.has(seqNum)) {
7933
+ queue?.delete(seqNum);
7934
+ if (queue && queue.size === 0) client._pendingOrderedMsgs.delete(ns);
7935
+ return false;
7936
+ }
7937
+ await this.drainOrderedMessages(ns, seqNum, false, persist, batch, source);
7938
+ this.ensurePullOperationCurrent();
7939
+ queue?.delete(seqNum);
7940
+ if (queue && queue.size === 0) client._pendingOrderedMsgs.delete(ns);
7406
7941
  if (event === "message.recalled") {
7407
7942
  const published = await client._withPullResponseProcessing(
7408
7943
  ns,
7409
- () => this.publishMessageRecallTombstone(seq2, payload)
7944
+ () => this.publishMessageRecallTombstone(seqNum, payload)
7410
7945
  );
7411
7946
  this.ensurePullOperationCurrent();
7947
+ this.markPublishedSeq(ns, seqNum);
7412
7948
  return published;
7413
7949
  }
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
- );
7950
+ await client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload, source, ns, batch));
7433
7951
  this.ensurePullOperationCurrent();
7434
7952
  this.markPublishedSeq(ns, seqNum);
7435
- return published;
7953
+ return true;
7954
+ } finally {
7955
+ if (ownsBatch && batch) await this.flushRealtimeDeliveryChanges(batch);
7436
7956
  }
7437
- await client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload));
7438
- this.ensurePullOperationCurrent();
7439
- this.markPublishedSeq(ns, seqNum);
7440
- return true;
7441
7957
  }
7442
7958
  };
7443
7959
 
@@ -7626,12 +8142,7 @@ var LifecycleController = class {
7626
8142
  async publishConnectionEvent(owner, event, payload) {
7627
8143
  const client = this.runtime.client;
7628
8144
  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
- }
8145
+ client._dispatcher.enqueue(event, payload);
7635
8146
  return this.ownsConnectionAttempt(owner);
7636
8147
  }
7637
8148
  async cancelConnectionAttemptAndWait() {
@@ -7939,14 +8450,9 @@ var LifecycleController = class {
7939
8450
  client._clientLog.warn(`reentrant close failed: ${err instanceof Error ? err.message : String(err)}`);
7940
8451
  });
7941
8452
  }
7942
- async publishLifecycleStopEvent(payload) {
8453
+ publishLifecycleStopEvent(payload) {
7943
8454
  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
- }
8455
+ client._dispatcher.enqueue("state_change", payload);
7950
8456
  }
7951
8457
  async withLifecycleStop(operation, kind) {
7952
8458
  const client = this.runtime.client;
@@ -8007,7 +8513,7 @@ var LifecycleController = class {
8007
8513
  client._stopBackgroundTasks();
8008
8514
  if (client._closing) return;
8009
8515
  this.runtime.lifecycle.resetForDisconnect("standby");
8010
- await this.publishLifecycleStopEvent({ state: client._publicState(client._state) });
8516
+ this.publishLifecycleStopEvent({ state: client._publicState(client._state) });
8011
8517
  client._clientLog.debug(`disconnect exit: elapsed=${Date.now() - tStart}ms`);
8012
8518
  }, "disconnect");
8013
8519
  } finally {
@@ -8017,6 +8523,7 @@ var LifecycleController = class {
8017
8523
  async close() {
8018
8524
  const client = this.runtime.client;
8019
8525
  const tStart = Date.now();
8526
+ const calledFromHandler = client._dispatcher.isDispatchingHandler();
8020
8527
  client._clientLog.debug(`close enter: state=${client._state}`);
8021
8528
  this.runtime.lifecycle.setClosing(true);
8022
8529
  client._delivery.resetInlineAckState();
@@ -8025,34 +8532,39 @@ var LifecycleController = class {
8025
8532
  return;
8026
8533
  }
8027
8534
  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;
8535
+ try {
8536
+ const pullInvalidation = client._rpcPipeline?.invalidatePulls?.();
8537
+ client._rpcPipeline?.stopPullGateWatchdogs?.();
8538
+ client._stopBackgroundTasks();
8539
+ if (client._state === "idle" || client._state === "closed") {
8540
+ const reconnectCancellation2 = client._cancelReconnectAndWait();
8541
+ const connectionCancellation2 = this.cancelConnectionAttemptAndWait();
8542
+ await Promise.all([reconnectCancellation2, connectionCancellation2]);
8543
+ await pullInvalidation;
8544
+ await client._saveSeqTrackerState();
8545
+ this.runtime.lifecycle.setState("closed");
8546
+ client._resetSeqTrackingState();
8547
+ client._clientLog.debug(`close exit: elapsed=${Date.now() - tStart}ms reason=already_idle`);
8548
+ return;
8549
+ }
8550
+ const reconnectCancellation = client._cancelReconnectAndWait();
8551
+ const connectionCancellation = this.cancelConnectionAttemptAndWait();
8552
+ try {
8553
+ await client._transport.call("auth.logout", {});
8554
+ } catch (err) {
8555
+ client._clientLog.warn(`auth.logout during close failed: ${err instanceof Error ? err.message : String(err)}`);
8556
+ }
8557
+ await client._transport.close();
8558
+ await Promise.all([pullInvalidation, reconnectCancellation, connectionCancellation]);
8036
8559
  await client._saveSeqTrackerState();
8037
8560
  this.runtime.lifecycle.setState("closed");
8561
+ this.publishLifecycleStopEvent({ state: client._publicState(client._state) });
8038
8562
  client._resetSeqTrackingState();
8039
- client._clientLog.debug(`close exit: elapsed=${Date.now() - tStart}ms reason=already_idle`);
8040
- return;
8563
+ client._clientLog.debug(`close exit: elapsed=${Date.now() - tStart}ms`);
8564
+ } finally {
8565
+ const closing = client._dispatcher.close();
8566
+ if (!calledFromHandler) await closing;
8041
8567
  }
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
8568
  }, "close");
8057
8569
  }
8058
8570
  };
@@ -14849,6 +15361,14 @@ var PROTECTED_HEADERS_METHODS = /* @__PURE__ */ new Set([
14849
15361
  "message.thought.put",
14850
15362
  "group.thought.put"
14851
15363
  ]);
15364
+ function generateMessageId() {
15365
+ if (typeof crypto.randomUUID === "function") return `m-${crypto.randomUUID().replace(/-/g, "")}`;
15366
+ const bytes = new Uint8Array(16);
15367
+ crypto.getRandomValues(bytes);
15368
+ bytes[6] = bytes[6] & 15 | 64;
15369
+ bytes[8] = bytes[8] & 63 | 128;
15370
+ return `m-${Array.from(bytes, (value) => value.toString(16).padStart(2, "0")).join("")}`;
15371
+ }
14852
15372
  var SIGNED_METHODS = /* @__PURE__ */ new Set([
14853
15373
  "message.send",
14854
15374
  "message.v2.put_peer_pk",
@@ -15111,6 +15631,7 @@ var RpcPipeline = class {
15111
15631
  __publicField(this, "runtime");
15112
15632
  __publicField(this, "pullGateStates", /* @__PURE__ */ new Map());
15113
15633
  __publicField(this, "inlineRealtimeScopes", /* @__PURE__ */ new Set());
15634
+ __publicField(this, "pullWorkTokens", /* @__PURE__ */ new Set());
15114
15635
  __publicField(this, "pullGeneration", 0);
15115
15636
  __publicField(this, "pullInvalidationWait", null);
15116
15637
  this.runtime = runtime;
@@ -15318,6 +15839,9 @@ var RpcPipeline = class {
15318
15839
  delete p._rpc_background;
15319
15840
  if (method === "message.send" || method === "group.send") {
15320
15841
  this.normalizeOutboundMessagePayload(p, method);
15842
+ if (!Object.prototype.hasOwnProperty.call(p, "message_id") || p.message_id == null) {
15843
+ p.message_id = generateMessageId();
15844
+ }
15321
15845
  }
15322
15846
  this.normalizeGroupCallIdentifier(method, p);
15323
15847
  this.validateOutboundCall(method, p);
@@ -15622,6 +16146,28 @@ var RpcPipeline = class {
15622
16146
  const matches = (job) => job !== null && job.namespace === ns;
15623
16147
  return matches(state.active) || state.foreground.some(matches) || state.background.some(matches);
15624
16148
  }
16149
+ hasAnyPullActivity() {
16150
+ if (this.pullWorkTokens.size > 0) return true;
16151
+ for (const state of this.pullGateStates.values()) {
16152
+ if (state.active || state.foreground.length > 0 || state.background.length > 0) return true;
16153
+ }
16154
+ return false;
16155
+ }
16156
+ reservePullWork() {
16157
+ const token = Symbol("pull-work");
16158
+ this.pullWorkTokens.add(token);
16159
+ let released = false;
16160
+ return () => {
16161
+ if (released) return;
16162
+ released = true;
16163
+ this.pullWorkTokens.delete(token);
16164
+ this.runtime.client._delivery?.onPullWorkSettled?.();
16165
+ };
16166
+ }
16167
+ releasePullWorkToken(token) {
16168
+ if (!this.pullWorkTokens.delete(token)) return;
16169
+ this.runtime.client._delivery?.onPullWorkSettled?.();
16170
+ }
15625
16171
  async tryRunInlineRealtime(ns, seq2, operation) {
15626
16172
  const normalized = String(ns ?? "").trim();
15627
16173
  if (!normalized.startsWith("p2p:") && !normalized.startsWith("group:") || !Number.isSafeInteger(seq2) || seq2 <= 0 || typeof operation !== "function") {
@@ -15663,7 +16209,8 @@ var RpcPipeline = class {
15663
16209
  invalidated: false,
15664
16210
  cancel: null,
15665
16211
  settled: Promise.resolve(),
15666
- settledResolve: null
16212
+ settledResolve: null,
16213
+ workToken: Symbol("inline-pull-work")
15667
16214
  };
15668
16215
  this.inlineRealtimeScopes.add(normalized);
15669
16216
  try {
@@ -15725,6 +16272,7 @@ var RpcPipeline = class {
15725
16272
  if (job.running) {
15726
16273
  waits.push(job.settled);
15727
16274
  } else {
16275
+ this.releasePullWorkToken(job.workToken);
15728
16276
  job.settledResolve?.();
15729
16277
  job.settledResolve = null;
15730
16278
  }
@@ -15737,6 +16285,7 @@ var RpcPipeline = class {
15737
16285
  this.drainPullGate(state);
15738
16286
  }
15739
16287
  const wait = Promise.all(waits).then(() => void 0);
16288
+ this.runtime.client._delivery?.onPullWorkSettled?.();
15740
16289
  let tracked;
15741
16290
  tracked = wait.finally(() => {
15742
16291
  if (this.pullInvalidationWait === tracked) this.pullInvalidationWait = null;
@@ -15751,7 +16300,14 @@ var RpcPipeline = class {
15751
16300
  if (this.pullInvalidationInProgress()) {
15752
16301
  throw new Error("pull invalidated");
15753
16302
  }
15754
- if (!key) return await this.executePullOperation(operation, background);
16303
+ if (!key) {
16304
+ const releasePullWork = this.reservePullWork();
16305
+ try {
16306
+ return await this.executePullOperation(operation, background);
16307
+ } finally {
16308
+ releasePullWork();
16309
+ }
16310
+ }
15755
16311
  const state = this.pullGateState(key);
15756
16312
  const namespace = this.pullScopeKey(key);
15757
16313
  const candidate = state.byKey.get(key);
@@ -15765,6 +16321,8 @@ var RpcPipeline = class {
15765
16321
  }
15766
16322
  return await existing.promise;
15767
16323
  }
16324
+ const workToken = Symbol("pull-work");
16325
+ this.pullWorkTokens.add(workToken);
15768
16326
  let resolve;
15769
16327
  let reject;
15770
16328
  let settledResolve;
@@ -15795,7 +16353,8 @@ var RpcPipeline = class {
15795
16353
  invalidated: false,
15796
16354
  cancel: null,
15797
16355
  settled,
15798
- settledResolve
16356
+ settledResolve,
16357
+ workToken
15799
16358
  };
15800
16359
  (background ? state.background : state.foreground).push(job);
15801
16360
  state.byKey.set(key, job);
@@ -15890,6 +16449,7 @@ var RpcPipeline = class {
15890
16449
  if (!job.timedOut && !job.invalidated) job.reject(error);
15891
16450
  }
15892
16451
  ).finally(() => {
16452
+ this.pullWorkTokens.delete(job.workToken);
15893
16453
  state.lifecycle.set(job.namespace, "idle");
15894
16454
  if (state.active === job) state.active = null;
15895
16455
  if (state.byKey.get(job.key) === job) state.byKey.delete(job.key);
@@ -15897,6 +16457,7 @@ var RpcPipeline = class {
15897
16457
  job.settledResolve?.();
15898
16458
  job.settledResolve = null;
15899
16459
  if (!job.invalidated) this.runtime.client._delivery?.onPullGateIdle?.(job.namespace);
16460
+ this.runtime.client._delivery?.onPullWorkSettled?.();
15900
16461
  });
15901
16462
  }
15902
16463
  armQueuedPullTimers(_state) {
@@ -23793,11 +24354,22 @@ var V2E2EECoordinator = class {
23793
24354
  const fetchKey = this.pendingSenderIKFetchKey(fromAid, senderDeviceId, groupId);
23794
24355
  if (!fromAid || client._v2SenderIKFetching.has(fetchKey)) return;
23795
24356
  client._v2SenderIKFetching.add(fetchKey);
23796
- client._safeAsync(this.resolveSenderIKPending(fromAid, senderDeviceId, groupId, fetchKey));
24357
+ const releasePullWork = client._rpcPipeline?.reservePullWork?.() ?? (() => {
24358
+ });
24359
+ const generation = client._delivery.captureInlineGeneration?.();
24360
+ client._safeAsync(this.resolveSenderIKPending(
24361
+ fromAid,
24362
+ senderDeviceId,
24363
+ groupId,
24364
+ fetchKey,
24365
+ generation
24366
+ ).finally(releasePullWork));
23797
24367
  }
23798
- async resolveSenderIKPending(fromAid, senderDeviceId, groupId, fetchKey) {
24368
+ async resolveSenderIKPending(fromAid, senderDeviceId, groupId, fetchKey, generation) {
23799
24369
  const client = this.client;
24370
+ const generationCurrent = () => generation === void 0 || client._delivery.isInlineGenerationCurrent?.(generation) !== false;
23800
24371
  try {
24372
+ if (!generationCurrent()) return;
23801
24373
  const session = client._v2Session;
23802
24374
  if (session && fromAid) {
23803
24375
  try {
@@ -23828,17 +24400,21 @@ var V2E2EECoordinator = class {
23828
24400
  await this.getV2SenderPubDer(fromAid, senderDeviceId);
23829
24401
  }
23830
24402
  }
24403
+ if (!generationCurrent()) return;
23831
24404
  const pendingItems = [...client._v2SenderIKPending.entries()].filter(([, entry]) => entry.fromAid === fromAid && entry.senderDeviceId === senderDeviceId && entry.groupId === groupId);
23832
24405
  for (const [key, entry] of pendingItems) {
24406
+ if (!generationCurrent()) return;
23833
24407
  let plaintext = null;
23834
24408
  const retryStatus = {};
23835
24409
  try {
23836
24410
  plaintext = await client._decryptV2Message(entry.msg, false, true, true, true, retryStatus);
23837
24411
  } catch (exc) {
24412
+ if (!generationCurrent()) return;
23838
24413
  client._clientLog.warn(`V2 sender IK pending retry raised: key=${key} err=${String(formatE2EEError(exc))}`);
23839
24414
  client._v2SenderIKPending.delete(key);
23840
24415
  continue;
23841
24416
  }
24417
+ if (!generationCurrent()) return;
23842
24418
  if (plaintext === null) {
23843
24419
  if (retryStatus.deferred) {
23844
24420
  client._clientLog.warn(`V2 pending retry still missing key material: key=${key}`);
@@ -23890,6 +24466,7 @@ var V2E2EECoordinator = class {
23890
24466
  return client.call("message.send", {
23891
24467
  to: toAid,
23892
24468
  payload: envelope,
24469
+ message_id: opts?.messageId,
23893
24470
  encrypt: false,
23894
24471
  _skip_send_result_envelope: true
23895
24472
  });
@@ -23966,7 +24543,8 @@ var V2E2EECoordinator = class {
23966
24543
  event.event,
23967
24544
  client._aid ? `p2p:${client._aid}` : "",
23968
24545
  seq2,
23969
- event.payload
24546
+ event.payload,
24547
+ source
23970
24548
  );
23971
24549
  } else {
23972
24550
  await client._publishAppEvent(event.event, event.payload, source);
@@ -24025,7 +24603,8 @@ var V2E2EECoordinator = class {
24025
24603
  "group.message_created",
24026
24604
  `group:${groupId}`,
24027
24605
  seq2,
24028
- plaintext
24606
+ plaintext,
24607
+ source
24029
24608
  );
24030
24609
  } else {
24031
24610
  await client._publishAppEvent("group.message_created", plaintext, source);
@@ -24193,7 +24772,7 @@ var V2E2EECoordinator = class {
24193
24772
  attachGatewayProximity(v1Msg, msg);
24194
24773
  const appEvent = client._delivery.p2pAppEventForMessage(v1Msg);
24195
24774
  if (ns) await client._publishPulledMessage(appEvent.event, ns, seq2, appEvent.payload, false);
24196
- else await client._publishAppEvent(appEvent.event, appEvent.payload);
24775
+ else await client._publishAppEvent(appEvent.event, appEvent.payload, "pull");
24197
24776
  decrypted.push(v1Msg);
24198
24777
  } else {
24199
24778
  client._clientLog.debug(`message.v2.pull skipping V1 envelope seq=${seq2} payload_type=${payloadType || "<none>"} (V1 E2EE removed)`);
@@ -24226,7 +24805,7 @@ var V2E2EECoordinator = class {
24226
24805
  await client._publishPulledMessage("message.received", ns, seq2, plaintext, false);
24227
24806
  decrypted.push(plaintext);
24228
24807
  } else {
24229
- await client._publishAppEvent("message.received", plaintext);
24808
+ await client._publishAppEvent("message.received", plaintext, "pull");
24230
24809
  decrypted.push(plaintext);
24231
24810
  }
24232
24811
  }
@@ -24402,7 +24981,8 @@ var V2E2EECoordinator = class {
24402
24981
  });
24403
24982
  return client.call("group.v2.send", withExplicitGroupAid({
24404
24983
  group_id: gid,
24405
- envelope
24984
+ envelope,
24985
+ message_id: opts?.messageId
24406
24986
  }, groupAid));
24407
24987
  };
24408
24988
  try {
@@ -25838,7 +26418,7 @@ var GroupStateCoordinator = class {
25838
26418
  if (!isJsonObject(data) || !client._v2Session) return;
25839
26419
  const groupId = groupIdFromRecord(data);
25840
26420
  if (!groupId) return;
25841
- await client._dispatcher.publish("group.v2.state_proposed", data);
26421
+ client._dispatcher.enqueue("group.v2.state_proposed", data);
25842
26422
  try {
25843
26423
  await client._v2ConfirmPendingProposal(groupId);
25844
26424
  } catch (exc) {
@@ -25850,7 +26430,7 @@ var GroupStateCoordinator = class {
25850
26430
  if (!isJsonObject(data) || !client._v2Session) return;
25851
26431
  const groupId = groupIdFromRecord(data);
25852
26432
  if (!groupId) return;
25853
- await client._dispatcher.publish("group.v2.state_retry_needed", data);
26433
+ client._dispatcher.enqueue("group.v2.state_retry_needed", data);
25854
26434
  try {
25855
26435
  await client._v2AutoProposeState(groupId, { leaderDelay: true });
25856
26436
  } catch (exc) {
@@ -25868,7 +26448,7 @@ var GroupStateCoordinator = class {
25868
26448
  }
25869
26449
  client._v2AutoProposeLastSnapshot?.delete?.(groupId);
25870
26450
  }
25871
- await client._dispatcher.publish("group.v2.state_confirmed", data);
26451
+ client._dispatcher.enqueue("group.v2.state_confirmed", data);
25872
26452
  }
25873
26453
  async publishV2GroupSecurityLevel(groupId, bootstrap) {
25874
26454
  const client = this.client;
@@ -25879,7 +26459,7 @@ var GroupStateCoordinator = class {
25879
26459
  const previous = securityLevels.get(gid);
25880
26460
  if (previous === level) return;
25881
26461
  securityLevels.set(gid, level);
25882
- await client._dispatcher.publish("group.v2.security_level", {
26462
+ client._dispatcher.enqueue("group.v2.security_level", {
25883
26463
  group_id: gid,
25884
26464
  level,
25885
26465
  warning: String(bootstrap.e2ee_security_warning ?? ""),
@@ -25974,7 +26554,7 @@ var GroupStateCoordinator = class {
25974
26554
  } catch {
25975
26555
  }
25976
26556
  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", {
26557
+ client._dispatcher.enqueue("group.v2.fork_detected", {
25978
26558
  group_id: gid,
25979
26559
  local_chain: localChain,
25980
26560
  server_chain: serverChain
@@ -26508,7 +27088,7 @@ var GroupStateCoordinator = class {
26508
27088
  }
26509
27089
  if (mode !== "open" && mode !== "invite_code" && mode !== "invite_only") {
26510
27090
  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", {
27091
+ client._dispatcher.enqueue("group.v2.state_tampered", {
26512
27092
  group_id: groupId,
26513
27093
  pending_extra: extra.sort(),
26514
27094
  mode
@@ -27458,6 +28038,7 @@ function buildDefaultAgentMd(aid, options = {}) {
27458
28038
  ].join("\n");
27459
28039
  }
27460
28040
  var HEAD_HTTP_TIMEOUT_MS = 15e3;
28041
+ var AGENT_MD_NEGATIVE_CACHE_TTL_MS = 6e4;
27461
28042
  var noopLogger = {
27462
28043
  error: () => {
27463
28044
  },
@@ -27798,6 +28379,24 @@ var AgentMdManager = class _AgentMdManager {
27798
28379
  ttl_days: Number(ttlDays) || 0
27799
28380
  };
27800
28381
  }
28382
+ const remoteMissingCached = String(before.remote_status ?? "").trim().toLowerCase() === "missing";
28383
+ if (!localFound && !remoteEtagCached && remoteMissingCached && _AgentMdManager.checkedAtFresh(checkedAtCached, ttlDays)) {
28384
+ return {
28385
+ aid: target,
28386
+ local_found: false,
28387
+ remote_found: false,
28388
+ local_etag: "",
28389
+ remote_etag: "",
28390
+ in_sync: false,
28391
+ needs_update: false,
28392
+ last_modified: "",
28393
+ status: 404,
28394
+ cached: true,
28395
+ verify_status: "",
28396
+ verify_error: "",
28397
+ ttl_days: Number(ttlDays) || 0
28398
+ };
28399
+ }
27801
28400
  const now = Date.now();
27802
28401
  let remote;
27803
28402
  try {
@@ -28287,14 +28886,16 @@ var AgentMdManager = class _AgentMdManager {
28287
28886
  async _scheduleFetchIfMissing(aid, record, source = "") {
28288
28887
  const target = String(aid ?? "").trim();
28289
28888
  if (!target || await this._hasLocalContent(target, record)) return;
28889
+ const cached = record ?? await this.loadRecord(target) ?? {};
28890
+ const checkedAt = Number(cached.checked_at ?? 0) || 0;
28891
+ if (String(cached.remote_status ?? "").trim().toLowerCase() === "missing" && checkedAt > 0 && Date.now() - checkedAt <= AGENT_MD_NEGATIVE_CACHE_TTL_MS) return;
28290
28892
  if (this._fetchInflight.has(target)) return;
28291
28893
  this._fetchInflight.add(target);
28292
28894
  try {
28293
28895
  await this.download(target);
28294
28896
  } catch (err) {
28295
28897
  await this.saveRecord(target, {
28296
- last_error: err instanceof Error ? err.message : String(err),
28297
- remote_status: "found"
28898
+ last_error: err instanceof Error ? err.message : String(err)
28298
28899
  });
28299
28900
  this._log.debug(`agent.md auto fetch failed: aid=${target} source=${source || "-"} err=${err instanceof Error ? err.message : String(err)}`);
28300
28901
  } finally {
@@ -28718,7 +29319,6 @@ var _AUNClient = class _AUNClient {
28718
29319
  __publicField(this, "_reconnectActive", false);
28719
29320
  __publicField(this, "_reconnectAbort", null);
28720
29321
  __publicField(this, "_reconnectTask", null);
28721
- __publicField(this, "_reconnectEventDispatchDepth", 0);
28722
29322
  __publicField(this, "_serverKicked", false);
28723
29323
  // 重连状态追踪(对齐 Python client.py)
28724
29324
  __publicField(this, "_nextRetryAt", null);
@@ -28898,7 +29498,7 @@ var _AUNClient = class _AUNClient {
28898
29498
  });
28899
29499
  for (const evt of ["message.ack", "storage.object_changed", "stream/created", "stream/closed"]) {
28900
29500
  this._dispatcher.subscribe(`_raw.${evt}`, (data) => {
28901
- this._dispatcher.publish(evt, data);
29501
+ this._dispatcher.enqueue(evt, data);
28902
29502
  });
28903
29503
  }
28904
29504
  this._dispatcher.subscribe("_raw.gateway.disconnect", async (data) => {
@@ -29985,8 +30585,8 @@ var _AUNClient = class _AUNClient {
29985
30585
  _markPublishedSeq(ns, seq2) {
29986
30586
  this._delivery.markPublishedSeq(ns, seq2);
29987
30587
  }
29988
- async _publishAppEvent(event, payload) {
29989
- await this._delivery.publishAppEvent(event, payload);
30588
+ async _publishAppEvent(event, payload, source = "", ns = "", batch) {
30589
+ await this._delivery.publishAppEvent(event, payload, source, ns, batch);
29990
30590
  }
29991
30591
  _echoTimestamp() {
29992
30592
  const now = /* @__PURE__ */ new Date();
@@ -30024,11 +30624,11 @@ var _AUNClient = class _AUNClient {
30024
30624
  async _drainOrderedMessages(ns, beforeSeq, pullResponse = false, persist = true) {
30025
30625
  await this._delivery.drainOrderedMessages(ns, beforeSeq, pullResponse, persist);
30026
30626
  }
30027
- async _publishOrderedMessage(event, ns, seq2, payload) {
30028
- return this._delivery.publishOrderedMessage(event, ns, seq2, payload);
30627
+ async _publishOrderedMessage(event, ns, seq2, payload, source = "push", batch) {
30628
+ return this._delivery.publishOrderedMessage(event, ns, seq2, payload, source, batch);
30029
30629
  }
30030
- async _publishPulledMessage(event, ns, seq2, payload, persist = true) {
30031
- return this._delivery.publishPulledMessage(event, ns, seq2, payload, persist);
30630
+ async _publishPulledMessage(event, ns, seq2, payload, persist = true, source = "pull", batch) {
30631
+ return this._delivery.publishPulledMessage(event, ns, seq2, payload, persist, source, batch);
30032
30632
  }
30033
30633
  _extractGroupIdFromResult(result) {
30034
30634
  const group = isJsonObject(result.group) ? result.group : null;
@@ -30059,7 +30659,7 @@ var _AUNClient = class _AUNClient {
30059
30659
  const groupId = d.group_id ?? d.group_aid ?? "";
30060
30660
  await this._delivery.handleGroupChangedEventSeq(d, groupId);
30061
30661
  } else {
30062
- await this._dispatcher.publish("group.changed", data);
30662
+ this._dispatcher.enqueue("group.changed", data);
30063
30663
  }
30064
30664
  this._clientLog.debug(`_onRawGroupChanged exit: elapsed=${Date.now() - tStart}ms group_id=${groupIdInit}`);
30065
30665
  } catch (err) {
@@ -30132,7 +30732,7 @@ var _AUNClient = class _AUNClient {
30132
30732
  const ok = await ecdsaVerifyDer(pubKey, sigBytes, signData);
30133
30733
  if (!ok) {
30134
30734
  this._clientLog.warn(`group event sig verify failed aid=%s method=%s${sigAid} ${method}`);
30135
- this._dispatcher.publish("signature.verification_failed", {
30735
+ this._dispatcher.enqueue("signature.verification_failed", {
30136
30736
  aid: sigAid,
30137
30737
  method,
30138
30738
  error: "ECDSA verification failed"
@@ -30141,7 +30741,7 @@ var _AUNClient = class _AUNClient {
30141
30741
  return ok;
30142
30742
  } catch (exc) {
30143
30743
  this._clientLog.warn(`group event sig verify exception:${String(exc)}`);
30144
- this._dispatcher.publish("signature.verification_failed", {
30744
+ this._dispatcher.enqueue("signature.verification_failed", {
30145
30745
  aid: sigAid,
30146
30746
  method,
30147
30747
  error: String(exc)
@@ -30359,7 +30959,7 @@ var _AUNClient = class _AUNClient {
30359
30959
  throw new StateError("connection attempt superseded");
30360
30960
  }
30361
30961
  } else {
30362
- await this._dispatcher.publish("state_change", statePayload);
30962
+ this._dispatcher.enqueue("state_change", statePayload);
30363
30963
  }
30364
30964
  this._assertReconnectOwner(reconnectOwner);
30365
30965
  this._lifecycle.assertConnectionAttemptOwner(connectionOwner);
@@ -30676,7 +31276,7 @@ var _AUNClient = class _AUNClient {
30676
31276
  if (this._sessionParams && identity.access_token) {
30677
31277
  this._sessionParams.access_token = identity.access_token;
30678
31278
  }
30679
- await this._dispatcher.publish("token.refreshed", {
31279
+ this._dispatcher.enqueue("token.refreshed", {
30680
31280
  aid: identity.aid,
30681
31281
  expires_at: identity.access_token_expires_at
30682
31282
  });
@@ -30685,7 +31285,7 @@ var _AUNClient = class _AUNClient {
30685
31285
  if (exc instanceof AuthError) {
30686
31286
  if (authErrorRequiresRelogin(exc)) {
30687
31287
  this._clientLog.warn(`token refresh requires relogin, stopping refresh loop and triggering reconnect: ${exc.message}`);
30688
- await this._dispatcher.publish("token.refresh_exhausted", {
31288
+ this._dispatcher.enqueue("token.refresh_exhausted", {
30689
31289
  aid: this._identity?.aid ?? null,
30690
31290
  consecutive_failures: 1,
30691
31291
  last_error: String(exc),
@@ -30698,7 +31298,7 @@ var _AUNClient = class _AUNClient {
30698
31298
  this._tokenRefreshFailures++;
30699
31299
  if (this._tokenRefreshFailures >= 3) {
30700
31300
  this._clientLog.warn(`token refresh failed ${this._tokenRefreshFailures} consecutive times, stopping refresh loop and triggering reconnect`);
30701
- await this._dispatcher.publish("token.refresh_exhausted", {
31301
+ this._dispatcher.enqueue("token.refresh_exhausted", {
30702
31302
  aid: this._identity?.aid ?? null,
30703
31303
  consecutive_failures: this._tokenRefreshFailures,
30704
31304
  last_error: String(exc)
@@ -30709,7 +31309,7 @@ var _AUNClient = class _AUNClient {
30709
31309
  }
30710
31310
  this._clientLog.warn(`token refresh failed (${this._tokenRefreshFailures}/3), next retry: ${String(exc)}`);
30711
31311
  } else {
30712
- this._dispatcher.publish("connection.error", { error: formatCaughtError2(exc) });
31312
+ this._dispatcher.enqueue("connection.error", { error: formatCaughtError2(exc) });
30713
31313
  }
30714
31314
  }
30715
31315
  scheduleRefresh();
@@ -30738,7 +31338,7 @@ var _AUNClient = class _AUNClient {
30738
31338
  this._serverKicked = !retryable;
30739
31339
  this._lastDisconnectInfo = { code, reason, detail };
30740
31340
  try {
30741
- await this._dispatcher.publish("gateway.disconnect", { code, reason, detail });
31341
+ this._dispatcher.enqueue("gateway.disconnect", { code, reason, detail });
30742
31342
  } catch (exc) {
30743
31343
  this._clientLog.debug(`publish gateway.disconnect failed: ${exc?.message ?? exc}`);
30744
31344
  }
@@ -30757,7 +31357,7 @@ var _AUNClient = class _AUNClient {
30757
31357
  } catch (exc) {
30758
31358
  this._clientLog.debug(`transport cleanup skipped: ${formatCaughtError2(exc)}`);
30759
31359
  }
30760
- await this._dispatcher.publish("state_change", {
31360
+ this._dispatcher.enqueue("state_change", {
30761
31361
  state: this._publicState(this._state),
30762
31362
  error
30763
31363
  });
@@ -30793,7 +31393,7 @@ var _AUNClient = class _AUNClient {
30793
31393
  if (disconnectInfo.code !== void 0 && disconnectInfo.code !== null) {
30794
31394
  eventPayload.code = disconnectInfo.code;
30795
31395
  }
30796
- await this._dispatcher.publish("state_change", eventPayload);
31396
+ this._dispatcher.enqueue("state_change", eventPayload);
30797
31397
  if (this._reconnectAbort === reconnectAbort) {
30798
31398
  this._reconnectAbort = null;
30799
31399
  this._reconnectActive = false;
@@ -30824,12 +31424,7 @@ var _AUNClient = class _AUNClient {
30824
31424
  }
30825
31425
  async _publishReconnectEvent(owner, event, payload) {
30826
31426
  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
- }
31427
+ this._dispatcher.enqueue(event, payload);
30833
31428
  return this._ownsReconnect(owner);
30834
31429
  }
30835
31430
  async _cancelReconnectAndWait() {
@@ -30842,7 +31437,7 @@ var _AUNClient = class _AUNClient {
30842
31437
  } catch (exc) {
30843
31438
  this._clientLog.debug(`reconnect cancellation transport cleanup skipped: ${formatCaughtError2(exc)}`);
30844
31439
  }
30845
- if (task && this._reconnectEventDispatchDepth === 0) {
31440
+ if (task) {
30846
31441
  try {
30847
31442
  await task;
30848
31443
  } catch (exc) {
@@ -31365,7 +31960,7 @@ var _AUNClient = class _AUNClient {
31365
31960
  _spk_id: spkId
31366
31961
  };
31367
31962
  attachV2EnvelopeMetadata2(event, e2eeMeta);
31368
- await this._dispatcher.publish(undecryptableEvent, event);
31963
+ this._dispatcher.enqueue(undecryptableEvent, event);
31369
31964
  } catch {
31370
31965
  }
31371
31966
  }
@@ -31406,7 +32001,7 @@ var _AUNClient = class _AUNClient {
31406
32001
  _suite: String(envelope.suite ?? "")
31407
32002
  };
31408
32003
  attachV2EnvelopeMetadata2(event, e2eeMeta);
31409
- await this._dispatcher.publish(undecryptableEvent, event);
32004
+ this._dispatcher.enqueue(undecryptableEvent, event);
31410
32005
  } catch {
31411
32006
  }
31412
32007
  }
@@ -31442,7 +32037,7 @@ var _AUNClient = class _AUNClient {
31442
32037
  _suite: String(envelope.suite ?? "")
31443
32038
  };
31444
32039
  attachV2EnvelopeMetadata2(event, e2eeMeta);
31445
- await this._dispatcher.publish(undecryptableEvent, event);
32040
+ this._dispatcher.enqueue(undecryptableEvent, event);
31446
32041
  } catch {
31447
32042
  }
31448
32043
  }
@@ -32017,7 +32612,7 @@ var RegisterFlow = class _RegisterFlow {
32017
32612
  return new Promise((resolve, reject) => {
32018
32613
  let ws;
32019
32614
  try {
32020
- ws = new WebSocket(gatewayUrl);
32615
+ ws = createTransportWebSocket(gatewayUrl);
32021
32616
  } catch {
32022
32617
  reject(new AuthError(`WebSocket \u8FDE\u63A5\u5931\u8D25: ${gatewayUrl}`));
32023
32618
  return;
@@ -32051,7 +32646,19 @@ var RegisterFlow = class _RegisterFlow {
32051
32646
  if (!receivedChallenge) {
32052
32647
  if (!isJsonObject(msg) || msg.method !== "challenge") return;
32053
32648
  receivedChallenge = true;
32054
- ws.send(JSON.stringify({ jsonrpc: "2.0", id: requestId, method, params: params2 }));
32649
+ const sendResult = ws.send(JSON.stringify({ jsonrpc: "2.0", id: requestId, method, params: params2 }));
32650
+ if (sendResult && typeof sendResult.then === "function") {
32651
+ void Promise.resolve(sendResult).catch((error) => {
32652
+ if (settled) return;
32653
+ settled = true;
32654
+ globalThis.clearTimeout(timeout);
32655
+ try {
32656
+ ws.close();
32657
+ } catch {
32658
+ }
32659
+ reject(error instanceof Error ? error : new AuthError(String(error)));
32660
+ });
32661
+ }
32055
32662
  return;
32056
32663
  }
32057
32664
  if (!isJsonObject(msg) || msg.id !== requestId) return;