@agentunion/fastaun-browser 0.5.8 → 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 (58) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/_packed_docs/CHANGELOG.md +62 -0
  3. package/_packed_docs/agent.md/examples//347/276/244/347/273/204-/345/274/200/345/217/221/345/233/242/351/230/237.md +21 -0
  4. package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +6 -5
  5. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +43 -8
  6. package/_packed_docs/sdk/09-group-rpc-manual.md +18 -3
  7. package/_packed_docs/sdk/09-message-rpc-manual.md +34 -10
  8. package/dist/agent-md.d.ts.map +1 -1
  9. package/dist/agent-md.js +24 -1
  10. package/dist/agent-md.js.map +1 -1
  11. package/dist/aid-store.d.ts.map +1 -1
  12. package/dist/aid-store.js +1 -3
  13. package/dist/aid-store.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 +1247 -269
  18. package/dist/client/delivery.d.ts +30 -7
  19. package/dist/client/delivery.d.ts.map +1 -1
  20. package/dist/client/delivery.js +354 -92
  21. package/dist/client/delivery.js.map +1 -1
  22. package/dist/client/group-state.js +8 -8
  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 +41 -34
  26. package/dist/client/lifecycle.js.map +1 -1
  27. package/dist/client/rpc-pipeline.d.ts +12 -0
  28. package/dist/client/rpc-pipeline.d.ts.map +1 -1
  29. package/dist/client/rpc-pipeline.js +205 -36
  30. package/dist/client/rpc-pipeline.js.map +1 -1
  31. package/dist/client/v2-e2ee.d.ts +1 -1
  32. package/dist/client/v2-e2ee.d.ts.map +1 -1
  33. package/dist/client/v2-e2ee.js +72 -18
  34. package/dist/client/v2-e2ee.js.map +1 -1
  35. package/dist/client.d.ts +0 -1
  36. package/dist/client.d.ts.map +1 -1
  37. package/dist/client.js +128 -45
  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/facades.d.ts.map +1 -1
  44. package/dist/facades.js +7 -3
  45. package/dist/facades.js.map +1 -1
  46. package/dist/register-flow.d.ts.map +1 -1
  47. package/dist/register-flow.js +16 -2
  48. package/dist/register-flow.js.map +1 -1
  49. package/dist/transport.d.ts +10 -1
  50. package/dist/transport.d.ts.map +1 -1
  51. package/dist/transport.js +283 -29
  52. package/dist/transport.js.map +1 -1
  53. package/dist/version.d.ts +1 -1
  54. package/dist/version.d.ts.map +1 -1
  55. package/dist/version.js +1 -1
  56. package/dist/version.js.map +1 -1
  57. package/package.json +1 -1
  58. package/_packed_docs//345/217/221/345/270/203/346/212/245/345/221/212-0.5.6.md +0 -260
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.8";
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);
974
+ } catch (exc) {
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;
839
983
  } catch (exc) {
840
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,10 +4905,15 @@ 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);
4513
4913
  __publicField(this, "pendingGroupPullUpper", null);
4914
+ __publicField(this, "pendingPullNoProgressAcks", null);
4915
+ __publicField(this, "onlineUnreadHintTargets", null);
4916
+ __publicField(this, "onlineUnreadHintOwners", null);
4514
4917
  __publicField(this, "realtimeAcking", null);
4515
4918
  __publicField(this, "pendingP2PInlineAcks", null);
4516
4919
  __publicField(this, "pendingGroupInlineAcks", null);
@@ -4523,9 +4926,15 @@ var MessageDeliveryEngine = class {
4523
4926
  }
4524
4927
  resetInlineAckState() {
4525
4928
  this.inlineGeneration += 1;
4929
+ this.deliveryGeneration += 1;
4930
+ this.pendingPullDeliveryChanges = null;
4931
+ void this.runtime.client._rpcPipeline?.invalidatePulls?.();
4526
4932
  this.realtimeSyncing = null;
4527
4933
  this.pendingP2pPullUpper = null;
4528
4934
  this.pendingGroupPullUpper = null;
4935
+ this.pendingPullNoProgressAcks = null;
4936
+ this.onlineUnreadHintTargets = null;
4937
+ this.onlineUnreadHintOwners = null;
4529
4938
  this.realtimeTailResults = null;
4530
4939
  this.realtimeAcking = null;
4531
4940
  this.pendingP2PInlineAcks = null;
@@ -4535,6 +4944,16 @@ var MessageDeliveryEngine = class {
4535
4944
  isInlineGenerationCurrent(generation) {
4536
4945
  return generation === this.inlineGeneration;
4537
4946
  }
4947
+ isPullOperationCurrent() {
4948
+ const client = this.runtime.client;
4949
+ const generation = client._pullOperationGeneration;
4950
+ if (generation === void 0) return true;
4951
+ const pipeline = client._rpcPipeline;
4952
+ return typeof pipeline?.isPullGenerationCurrent === "function" ? pipeline.isPullGenerationCurrent(generation) : true;
4953
+ }
4954
+ ensurePullOperationCurrent() {
4955
+ if (!this.isPullOperationCurrent()) throw new Error("pull invalidated");
4956
+ }
4538
4957
  captureInlineGeneration() {
4539
4958
  return this.inlineGeneration;
4540
4959
  }
@@ -4556,6 +4975,67 @@ var MessageDeliveryEngine = class {
4556
4975
  if (!ns) return;
4557
4976
  this.schedulePendingPullIfNeeded(ns, "pull-gate-idle");
4558
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
+ }
4559
5039
  recordPendingPull(ns, seq2) {
4560
5040
  if (!ns || !Number.isSafeInteger(seq2) || seq2 <= 0) return;
4561
5041
  const pending = ns.startsWith("p2p:") ? this.pendingP2pPullUpper ?? /* @__PURE__ */ new Map() : this.pendingGroupPullUpper ?? /* @__PURE__ */ new Map();
@@ -4565,7 +5045,15 @@ var MessageDeliveryEngine = class {
4565
5045
  }
4566
5046
  consumePendingPull(ns, throughSeq) {
4567
5047
  const pending = ns.startsWith("p2p:") ? this.pendingP2pPullUpper : this.pendingGroupPullUpper;
4568
- if ((pending?.get(ns) ?? 0) <= throughSeq) pending?.delete(ns);
5048
+ if ((pending?.get(ns) ?? 0) <= throughSeq) {
5049
+ pending?.delete(ns);
5050
+ this.pendingPullNoProgressAcks?.delete(ns);
5051
+ }
5052
+ }
5053
+ markPendingPullNoProgress(ns, ack) {
5054
+ const blocked = this.pendingPullNoProgressAcks ?? /* @__PURE__ */ new Map();
5055
+ this.pendingPullNoProgressAcks = blocked;
5056
+ blocked.set(ns, ack);
4569
5057
  }
4570
5058
  schedulePendingPullIfNeeded(ns, reason) {
4571
5059
  const pending = ns.startsWith("p2p:") ? this.pendingP2pPullUpper : this.pendingGroupPullUpper;
@@ -4573,15 +5061,20 @@ var MessageDeliveryEngine = class {
4573
5061
  const upper = pending.get(ns) ?? 0;
4574
5062
  if (upper <= 0) {
4575
5063
  pending.delete(ns);
5064
+ this.pendingPullNoProgressAcks?.delete(ns);
4576
5065
  return false;
4577
5066
  }
4578
5067
  const client = this.runtime.client;
4579
5068
  const contiguous = client._seqTracker.getContiguousSeq(ns);
4580
5069
  if (upper <= contiguous) {
4581
5070
  pending.delete(ns);
5071
+ this.pendingPullNoProgressAcks?.delete(ns);
4582
5072
  client._clientLog?.debug(`pending pull upper already covered: ns=${ns}, upper_seq=${upper}, contiguous=${contiguous}, reason=${reason}`);
4583
5073
  return false;
4584
5074
  }
5075
+ const blockedAck = this.pendingPullNoProgressAcks?.get(ns);
5076
+ if (blockedAck !== void 0 && contiguous <= blockedAck) return false;
5077
+ this.pendingPullNoProgressAcks?.delete(ns);
4585
5078
  if (client.state !== "ready" /* READY */ || client._closing || this.realtimeSyncing?.has(ns) || client._rpcPipeline?.hasPullActivity?.(ns, false)) return false;
4586
5079
  pending.delete(ns);
4587
5080
  client._clientLog?.debug(`pending push follow-up pull scheduled: ns=${ns}, upper_seq=${upper}, reason=${reason}`);
@@ -4947,6 +5440,15 @@ var MessageDeliveryEngine = class {
4947
5440
  value = row.effective_ack_seq;
4948
5441
  } else if (Object.prototype.hasOwnProperty.call(row, "ack_seq")) {
4949
5442
  value = row.ack_seq;
5443
+ } else if (Object.prototype.hasOwnProperty.call(row, "cursor")) {
5444
+ const cursor = row.cursor;
5445
+ const cursorRow = isJsonObject(cursor) ? cursor : null;
5446
+ if (cursorRow) {
5447
+ if (!Object.prototype.hasOwnProperty.call(cursorRow, "current_seq")) return 0;
5448
+ value = cursorRow.current_seq;
5449
+ } else {
5450
+ value = cursor;
5451
+ }
4950
5452
  } else {
4951
5453
  return requestedSeq;
4952
5454
  }
@@ -4961,6 +5463,7 @@ var MessageDeliveryEngine = class {
4961
5463
  }
4962
5464
  async confirmPlainForwardAck(ns, method, ackSeq, groupId = "") {
4963
5465
  const client = this.runtime.client;
5466
+ this.ensurePullOperationCurrent();
4964
5467
  const coordinator = this.forwardCoordinator();
4965
5468
  const generation = this.captureInlineGeneration();
4966
5469
  coordinator.recordForwardAck(ns, ackSeq);
@@ -4977,6 +5480,7 @@ var MessageDeliveryEngine = class {
4977
5480
  _rpc_background: true
4978
5481
  };
4979
5482
  const result = await client._rpcPipeline.rawCall(method, params2, { background: true });
5483
+ this.ensurePullOperationCurrent();
4980
5484
  const actualAckSeq = this.resolveForwardAckSeq(result, ackSeq);
4981
5485
  if (actualAckSeq < ackSeq) {
4982
5486
  throw new Error(`${method} server ACK watermark ${actualAckSeq} is below requested ${ackSeq}`);
@@ -4993,6 +5497,7 @@ var MessageDeliveryEngine = class {
4993
5497
  throw new Error(`${method} response must be an object`);
4994
5498
  }
4995
5499
  const client = this.runtime.client;
5500
+ this.ensurePullOperationCurrent();
4996
5501
  const response = result;
4997
5502
  const messages = deduplicatePlainForwardPageMessages(response.messages, afterSeq);
4998
5503
  const coordinator = this.forwardCoordinator();
@@ -5020,24 +5525,29 @@ var MessageDeliveryEngine = class {
5020
5525
  let committed = false;
5021
5526
  try {
5022
5527
  for (const rawMessage of messages) {
5528
+ this.ensurePullOperationCurrent();
5023
5529
  const seq2 = positiveSafeSequenceHint(rawMessage.seq);
5024
5530
  if (method === "message.pull") {
5025
5531
  const appEvent = p2pAppEventFromPlainPullMessage(rawMessage);
5026
5532
  if (await this.publishPulledMessage(appEvent.event, ns, seq2, appEvent.payload, false)) {
5027
5533
  publishedCount += 1;
5028
5534
  }
5535
+ this.ensurePullOperationCurrent();
5029
5536
  continue;
5030
5537
  }
5031
5538
  const message = normalizeGroupMentionMode(rawMessage);
5032
5539
  if (this.recallEventFromGroupMessage(message)) {
5033
5540
  if (await this.publishGroupRecallTombstone(groupId, seq2, message)) {
5541
+ this.ensurePullOperationCurrent();
5034
5542
  this.markPublishedSeq(ns, seq2);
5035
5543
  publishedCount += 1;
5036
5544
  }
5037
5545
  } else if (await this.publishPulledMessage("group.message_created", ns, seq2, message, false)) {
5546
+ this.ensurePullOperationCurrent();
5038
5547
  publishedCount += 1;
5039
5548
  }
5040
5549
  }
5550
+ this.ensurePullOperationCurrent();
5041
5551
  if (messages.length > 0) client._seqTracker.onPullResult(ns, messages, afterSeq);
5042
5552
  const commitTarget = Math.max(
5043
5553
  client._seqTracker.getContiguousSeq(ns),
@@ -5049,17 +5559,20 @@ var MessageDeliveryEngine = class {
5049
5559
  client._seqTracker.forceContiguousSeq(ns, commitTarget);
5050
5560
  }
5051
5561
  if (client._seqTracker.getContiguousSeq(ns) !== pageContigBefore) {
5562
+ this.ensurePullOperationCurrent();
5052
5563
  await this.drainOrderedMessages(ns, void 0, false, false);
5564
+ this.ensurePullOperationCurrent();
5053
5565
  await client._commitSeqTrackerState(ns);
5054
5566
  }
5055
5567
  committed = true;
5056
5568
  } catch (exc) {
5057
- if (!committed && typeof client._seqTracker.restoreNamespaceSnapshot === "function") {
5569
+ if (this.isPullOperationCurrent() && !committed && typeof client._seqTracker.restoreNamespaceSnapshot === "function") {
5058
5570
  client._seqTracker.restoreNamespaceSnapshot(ns, pageTrackerSnapshot);
5059
5571
  this.dropSeqTrackerPending(ns);
5060
5572
  }
5061
5573
  throw exc;
5062
5574
  }
5575
+ this.ensurePullOperationCurrent();
5063
5576
  const committedAck = client._seqTracker.getContiguousSeq(ns);
5064
5577
  if (deferredServerCursor > 0 && committedAck >= deferredServerCursor) {
5065
5578
  coordinator.clearForwardCursor(ns, committedAck);
@@ -5092,18 +5605,19 @@ var MessageDeliveryEngine = class {
5092
5605
  if (clampedAckSeq < pendingAckSeq) {
5093
5606
  throw new Error(`${ackMethod} cannot confirm pending Forward watermark ${pendingAckSeq}`);
5094
5607
  }
5608
+ this.ensurePullOperationCurrent();
5095
5609
  await this.confirmPlainForwardAck(ns, ackMethod, pendingAckSeq, groupId);
5096
5610
  }
5097
5611
  return { rawCount: messages.length, publishedCount };
5098
5612
  }
5099
- enqueueOrderedMessage(ns, event, seq2, payload) {
5613
+ enqueueOrderedMessage(ns, event, seq2, payload, source = "push") {
5100
5614
  const client = this.runtime.client;
5101
5615
  let queue = client._pendingOrderedMsgs.get(ns);
5102
5616
  if (!queue) {
5103
5617
  queue = /* @__PURE__ */ new Map();
5104
5618
  client._pendingOrderedMsgs.set(ns, queue);
5105
5619
  }
5106
- queue.set(seq2, { event, payload });
5620
+ queue.set(seq2, { event, payload, source });
5107
5621
  if (queue.size > PENDING_ORDERED_LIMIT) {
5108
5622
  const drop = [...queue.keys()].sort((a, b) => a - b).slice(0, queue.size - PENDING_ORDERED_LIMIT);
5109
5623
  for (const oldSeq of drop) queue.delete(oldSeq);
@@ -5112,7 +5626,7 @@ var MessageDeliveryEngine = class {
5112
5626
  isGroupEventNamespace(ns) {
5113
5627
  return ns.startsWith("group_event:");
5114
5628
  }
5115
- async publishOrderedQueueItem(ns, event, seq2, payload, pullResponse = false) {
5629
+ async publishOrderedQueueItem(ns, event, seq2, payload, pullResponse = false, source = "push", batch) {
5116
5630
  const client = this.runtime.client;
5117
5631
  if (event === "group.changed" && this.isGroupEventNamespace(ns)) {
5118
5632
  await this.publishOrderedGroupChanged(payload);
@@ -5123,10 +5637,10 @@ var MessageDeliveryEngine = class {
5123
5637
  return;
5124
5638
  }
5125
5639
  if (pullResponse) {
5126
- await client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload));
5640
+ await client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload, source, ns, batch));
5127
5641
  return;
5128
5642
  }
5129
- await client._publishAppEvent(event, payload);
5643
+ await client._publishAppEvent(event, payload, source, ns, batch);
5130
5644
  }
5131
5645
  async publishOrderedGroupChanged(payload) {
5132
5646
  const client = this.runtime.client;
@@ -5269,6 +5783,7 @@ var MessageDeliveryEngine = class {
5269
5783
  setIfPresent("type", firstValue(body.type, params2.type, params2.message_type, params2.payload_type));
5270
5784
  setIfPresent("kind", firstValue(body.kind, params2.kind));
5271
5785
  setIfPresent("version", firstValue(body.version, params2.version));
5786
+ setIfPresent("message_id", firstValue(params2.message_id, body.message_id, resultObj.message_id));
5272
5787
  setIfPresent("timestamp", firstValue(params2.timestamp, resultObj.timestamp, resultObj.created_at, resultObj.t_server, Date.now()));
5273
5788
  envelope.encrypted = Boolean(encrypted);
5274
5789
  const context = this.envelopeMetadata(params2.context);
@@ -5546,8 +6061,9 @@ var MessageDeliveryEngine = class {
5546
6061
  }
5547
6062
  if (contig !== contigBefore) this.persistSeq(ns);
5548
6063
  }
5549
- async publishAppEvent(event, payload) {
6064
+ async publishAppEvent(event, payload, source = "", ns = "", batch) {
5550
6065
  const client = this.runtime.client;
6066
+ const generation = this.deliveryGeneration;
5551
6067
  if ((event === "message.received" || event === "group.message_created") && isJsonObject(payload)) {
5552
6068
  client._maybeAppendEchoTraceReceive(payload);
5553
6069
  }
@@ -5566,7 +6082,19 @@ var MessageDeliveryEngine = class {
5566
6082
  client._clientLog.debug(`agent_md etag inject skipped: ${String(exc)}`);
5567
6083
  }
5568
6084
  }
5569
- 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
+ }
5570
6098
  }
5571
6099
  messageTargetsCurrentInstance(message) {
5572
6100
  if (!isJsonObject(message)) return true;
@@ -5699,7 +6227,7 @@ var MessageDeliveryEngine = class {
5699
6227
  const client = this.runtime.client;
5700
6228
  try {
5701
6229
  if (!isJsonObject(data)) {
5702
- await client._publishAppEvent("message.received", data);
6230
+ await client._publishAppEvent("message.received", data, "push");
5703
6231
  return;
5704
6232
  }
5705
6233
  const msg = { ...data };
@@ -5744,7 +6272,7 @@ var MessageDeliveryEngine = class {
5744
6272
  await client._publishEncryptedPushMessage("message.received", "message.undecryptable", "", seq2 ?? 0, msg, false);
5745
6273
  return;
5746
6274
  }
5747
- await client._publishAppEvent("message.received", msg);
6275
+ await client._publishAppEvent("message.received", msg, "push");
5748
6276
  }
5749
6277
  } catch (exc) {
5750
6278
  client._clientLog.warn(`P2P push processing failed:${String(exc)}`);
@@ -5783,7 +6311,7 @@ var MessageDeliveryEngine = class {
5783
6311
  const client = this.runtime.client;
5784
6312
  try {
5785
6313
  if (!isJsonObject(data)) {
5786
- await client._publishAppEvent("group.message_created", data);
6314
+ await client._publishAppEvent("group.message_created", data, "push");
5787
6315
  return;
5788
6316
  }
5789
6317
  const msg = { ...data };
@@ -5855,7 +6383,7 @@ var MessageDeliveryEngine = class {
5855
6383
  await client._publishEncryptedPushMessage("group.message_created", "group.message_undecryptable", "", seq2 ?? 0, msg, true);
5856
6384
  return;
5857
6385
  }
5858
- await client._publishAppEvent("group.message_created", msg);
6386
+ await client._publishAppEvent("group.message_created", msg, "push");
5859
6387
  }
5860
6388
  } catch (exc) {
5861
6389
  client._clientLog.warn(`group push processing failed:${String(exc)}`);
@@ -5939,11 +6467,16 @@ var MessageDeliveryEngine = class {
5939
6467
  P2P_GAP_FILL_RETRY_MAX_MS
5940
6468
  );
5941
6469
  client._gapFillDone.add(retryKey);
6470
+ const releasePullWork = client._rpcPipeline?.reservePullWork?.() ?? (() => {
6471
+ });
5942
6472
  client._clientLog.debug(`P2P message gap-fill retry scheduled: ns=${ns} attempt=${retryAttempt} delay_ms=${delayMs}`);
5943
6473
  globalThis.setTimeout(() => {
5944
6474
  client._gapFillDone.delete(retryKey);
5945
- if (client.state !== "ready" /* READY */ || client._closing) return;
5946
- 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));
5947
6480
  }, delayMs);
5948
6481
  }
5949
6482
  async fillP2pGap(retryAttempt = 0) {
@@ -6234,9 +6767,30 @@ var MessageDeliveryEngine = class {
6234
6767
  const groupId = String(data.group_id ?? "").trim();
6235
6768
  if (!groupId) return;
6236
6769
  const ns = `group:${groupId}`;
6237
- client._safeAsync(this.runGroupForwardRecovery(groupId, ns, 50, true).catch((exc) => {
6238
- client._clientLog?.debug(`online unread hint background Forward failed: ns=${ns} err=${formatDeliveryError(exc)}`);
6239
- }));
6770
+ const targets = this.onlineUnreadHintTargets ?? /* @__PURE__ */ new Map();
6771
+ const owners = this.onlineUnreadHintOwners ?? /* @__PURE__ */ new Set();
6772
+ this.onlineUnreadHintTargets = targets;
6773
+ this.onlineUnreadHintOwners = owners;
6774
+ targets.set(ns, Math.max(targets.get(ns) ?? 0, positiveSafeSequenceHint(data.seq)));
6775
+ if (owners.has(ns)) return;
6776
+ owners.add(ns);
6777
+ client._safeAsync((async () => {
6778
+ try {
6779
+ while (true) {
6780
+ const throughSeq = targets.get(ns) ?? 0;
6781
+ const ackBefore = this.syncState(ns).ack;
6782
+ await this.runGroupForwardRecovery(groupId, ns, 50, true, throughSeq);
6783
+ const ackAfter = this.syncState(ns).ack;
6784
+ const target = targets.get(ns) ?? 0;
6785
+ if (ackAfter >= target || ackAfter <= ackBefore) return;
6786
+ }
6787
+ } catch (exc) {
6788
+ client._clientLog?.debug(`online unread hint background Forward failed: ns=${ns} err=${formatDeliveryError(exc)}`);
6789
+ } finally {
6790
+ if (this.onlineUnreadHintTargets === targets) targets.delete(ns);
6791
+ if (this.onlineUnreadHintOwners === owners) owners.delete(ns);
6792
+ }
6793
+ })());
6240
6794
  }
6241
6795
  enqueueOnlineUnreadEventHint(data) {
6242
6796
  const client = this.runtime.client;
@@ -6266,17 +6820,19 @@ var MessageDeliveryEngine = class {
6266
6820
  const key = pipeline.pullGateKeyForCall("message.pull", request);
6267
6821
  return await pipeline.runPullSerialized(key, invoke, background);
6268
6822
  }
6269
- async runGroupForwardRecovery(groupId, ns, pageLimit, background = false) {
6823
+ async runGroupForwardRecovery(groupId, ns, pageLimit, background = false, throughSeq = 0) {
6270
6824
  const client = this.runtime.client;
6825
+ const state = this.syncState(ns);
6826
+ const maxPages = this.forwardMaxPages(state.ack, throughSeq, pageLimit);
6271
6827
  const request = {
6272
6828
  group_id: groupId,
6273
- after_seq: this.syncState(ns).ack,
6829
+ after_seq: state.ack,
6274
6830
  limit: pageLimit,
6275
- max_pages: 1
6831
+ max_pages: maxPages
6276
6832
  };
6277
6833
  const invoke = async () => {
6278
6834
  const after = this.syncState(ns).ack;
6279
- const messages = await client._pullGroupV2(groupId, after, pageLimit, { gateLocked: true, maxPages: 1 });
6835
+ const messages = await client._pullGroupV2(groupId, after, pageLimit, { gateLocked: true, maxPages });
6280
6836
  return { messages, raw_count: messages.length };
6281
6837
  };
6282
6838
  const pipeline = client._rpcPipeline;
@@ -6290,6 +6846,10 @@ var MessageDeliveryEngine = class {
6290
6846
  const ack = Number(tracker.getContiguousSeq(ns) || 0);
6291
6847
  return { ack, tail: ack, head: Math.max(ack, Number(tracker.getMaxSeenSeq?.(ns) || 0)) };
6292
6848
  }
6849
+ forwardMaxPages(ack, throughSeq, pageLimit) {
6850
+ const limit = Number.isSafeInteger(pageLimit) && pageLimit > 0 ? pageLimit : 1;
6851
+ return Math.max(1, Math.ceil(Math.max(0, throughSeq - ack) / limit));
6852
+ }
6293
6853
  inlineMessage(data) {
6294
6854
  if (!isJsonObject(data)) return null;
6295
6855
  const inline = data.inline_message;
@@ -6469,7 +7029,7 @@ var MessageDeliveryEngine = class {
6469
7029
  const tracker = client._seqTracker;
6470
7030
  const snapshot = typeof tracker.snapshotNamespace === "function" ? tracker.snapshotNamespace(ns) : null;
6471
7031
  try {
6472
- const published = await this.publishPulledMessage(event, ns, seq2, payload);
7032
+ const published = await this.publishPulledMessage(event, ns, seq2, payload, true, "inline_push");
6473
7033
  if (!this.isInlineGenerationCurrent(generation)) return false;
6474
7034
  if (!published) return false;
6475
7035
  const needsPull = Boolean(tracker.onMessageSeq(ns, seq2));
@@ -6621,17 +7181,28 @@ var MessageDeliveryEngine = class {
6621
7181
  if (forceTail || order[0] === "tail") result = await run(true, false, void 0, false, forceTail);
6622
7182
  const state = this.syncState(ns);
6623
7183
  const tailMissedDuringPull = tailCompleted && Number(client._seqTracker.getMaxSeenSeq(ns) || 0) > state.head;
6624
- const pendingThroughSeq = tailMissedDuringPull ? Number(client._seqTracker.getMaxSeenSeq(ns) || 0) : 0;
7184
+ const maxSeen = Number(client._seqTracker.getMaxSeenSeq(ns) || 0);
7185
+ const pendingThroughSeq = tailMissedDuringPull ? maxSeen : 0;
6625
7186
  const tailForward = tailCompleted && (state.ack < state.tail - 1 || tailMissedDuringPull);
6626
7187
  const backgroundWindowForward = order[0] === "forward" && !headForward;
6627
7188
  if (headForward || tailForward || client._sessionOptions?.background_sync !== false && backgroundWindowForward) {
7189
+ const forwardTarget = Math.max(
7190
+ headForward ? Math.max(pushSeq, maxSeen) : 0,
7191
+ tailForward || backgroundWindowForward ? state.tail - 1 : 0,
7192
+ tailMissedDuringPull ? maxSeen : 0
7193
+ );
7194
+ const forwardMaxPages = this.forwardMaxPages(state.ack, forwardTarget, pageLimit);
6628
7195
  result = await run(
6629
7196
  false,
6630
7197
  !(headForward || tailForward),
6631
- headForward ? 2 : order[0] === "tail" ? 1 : void 0,
7198
+ forwardMaxPages,
6632
7199
  headForward || tailMissedDuringPull
6633
7200
  );
6634
- if (pendingThroughSeq > 0) this.consumePendingPull(ns, pendingThroughSeq);
7201
+ if (pendingThroughSeq > 0) {
7202
+ const committedAck = this.syncState(ns).ack;
7203
+ if (committedAck >= pendingThroughSeq) this.consumePendingPull(ns, pendingThroughSeq);
7204
+ else if (committedAck <= state.ack) this.markPendingPullNoProgress(ns, committedAck);
7205
+ }
6635
7206
  }
6636
7207
  const finalState = this.syncState(ns);
6637
7208
  if (tailCompleted && finalState.ack < finalState.tail - 1) {
@@ -6678,17 +7249,28 @@ var MessageDeliveryEngine = class {
6678
7249
  if (forceTail || order[0] === "tail") result = await run(true, false, void 0, false, forceTail);
6679
7250
  const state = this.syncState(ns);
6680
7251
  const tailMissedDuringPull = tailCompleted && Number(client._seqTracker.getMaxSeenSeq(ns) || 0) > state.head;
6681
- const pendingThroughSeq = tailMissedDuringPull ? Number(client._seqTracker.getMaxSeenSeq(ns) || 0) : 0;
7252
+ const maxSeen = Number(client._seqTracker.getMaxSeenSeq(ns) || 0);
7253
+ const pendingThroughSeq = tailMissedDuringPull ? maxSeen : 0;
6682
7254
  const tailForward = tailCompleted && (state.ack < state.tail - 1 || tailMissedDuringPull);
6683
7255
  const backgroundWindowForward = order[0] === "forward" && !headForward;
6684
7256
  if (headForward || tailForward || client._sessionOptions?.background_sync !== false && backgroundWindowForward) {
7257
+ const forwardTarget = Math.max(
7258
+ headForward ? Math.max(pushSeq, maxSeen) : 0,
7259
+ tailForward || backgroundWindowForward ? state.tail - 1 : 0,
7260
+ tailMissedDuringPull ? maxSeen : 0
7261
+ );
7262
+ const forwardMaxPages = this.forwardMaxPages(state.ack, forwardTarget, pageLimit);
6685
7263
  result = await run(
6686
7264
  false,
6687
7265
  !(headForward || tailForward),
6688
- headForward ? 2 : order[0] === "tail" ? 1 : void 0,
7266
+ forwardMaxPages,
6689
7267
  headForward || tailMissedDuringPull
6690
7268
  );
6691
- if (pendingThroughSeq > 0) this.consumePendingPull(ns, pendingThroughSeq);
7269
+ if (pendingThroughSeq > 0) {
7270
+ const committedAck = this.syncState(ns).ack;
7271
+ if (committedAck >= pendingThroughSeq) this.consumePendingPull(ns, pendingThroughSeq);
7272
+ else if (committedAck <= state.ack) this.markPendingPullNoProgress(ns, committedAck);
7273
+ }
6692
7274
  }
6693
7275
  const finalState = this.syncState(ns);
6694
7276
  if (tailCompleted && finalState.ack < finalState.tail - 1) {
@@ -6935,13 +7517,12 @@ var MessageDeliveryEngine = class {
6935
7517
  }
6936
7518
  }
6937
7519
  } catch (exc) {
6938
- client._dispatcher.publish("seq_tracker.persist_error", {
7520
+ client._dispatcher.enqueue("seq_tracker.persist_error", {
6939
7521
  phase: "restore",
6940
7522
  aid,
6941
7523
  device_id: deviceId,
6942
7524
  slot_id: slotId,
6943
7525
  error: String(exc)
6944
- }).catch(() => {
6945
7526
  });
6946
7527
  }
6947
7528
  }
@@ -7132,13 +7713,12 @@ var MessageDeliveryEngine = class {
7132
7713
  } catch (exc) {
7133
7714
  const error = formatDeliveryError(exc);
7134
7715
  client._clientLog.warn(`save SeqTracker state failed: ${error}`);
7135
- client._dispatcher.publish("seq_tracker.persist_error", {
7716
+ client._dispatcher.enqueue("seq_tracker.persist_error", {
7136
7717
  phase: "save",
7137
7718
  aid,
7138
7719
  device_id: deviceId,
7139
7720
  slot_id: slotId,
7140
7721
  error: String(error)
7141
- }).catch(() => {
7142
7722
  });
7143
7723
  if (throwOnError) throw exc;
7144
7724
  }
@@ -7243,88 +7823,137 @@ var MessageDeliveryEngine = class {
7243
7823
  }
7244
7824
  return params2;
7245
7825
  }
7246
- async drainOrderedMessages(ns, beforeSeq, pullResponse = false, persist = true) {
7826
+ async drainOrderedMessages(ns, beforeSeq, pullResponse = false, persist = true, batch, source = "pull") {
7247
7827
  const client = this.runtime.client;
7828
+ this.ensurePullOperationCurrent();
7248
7829
  const queue = client._pendingOrderedMsgs.get(ns);
7249
7830
  if (!queue || queue.size === 0) return;
7250
7831
  const contig = client._seqTracker.getContiguousSeq(ns);
7251
7832
  const ready = [...queue.keys()].filter((seq2) => seq2 <= contig && (beforeSeq === void 0 || seq2 < beforeSeq)).sort((a, b) => a - b);
7252
7833
  let delivered = false;
7253
- for (const seq2 of ready) {
7254
- const item = queue.get(seq2);
7255
- queue.delete(seq2);
7256
- if (!item || client._pushedSeqs.get(ns)?.has(seq2)) continue;
7257
- await this.publishOrderedQueueItem(ns, item.event, seq2, item.payload, pullResponse);
7258
- this.markPublishedSeq(ns, seq2);
7259
- 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
+ }
7260
7867
  }
7261
7868
  if (queue.size === 0) {
7262
7869
  client._pendingOrderedMsgs.delete(ns);
7263
- if (delivered && persist) await this.saveSeqTrackerState();
7870
+ if (delivered && persist) {
7871
+ this.ensurePullOperationCurrent();
7872
+ await this.saveSeqTrackerState();
7873
+ }
7264
7874
  }
7265
7875
  }
7266
- async publishOrderedMessage(event, ns, seq2, payload) {
7876
+ async publishOrderedMessage(event, ns, seq2, payload, source = "push", operationBatch) {
7267
7877
  const client = this.runtime.client;
7268
- const seqNum = Number(seq2);
7269
- if (!Number.isFinite(seqNum) || !Number.isInteger(seqNum) || seqNum <= 0) {
7270
- 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();
7271
7906
  return true;
7907
+ } finally {
7908
+ if (ownsBatch && batch) await this.flushRealtimeDeliveryChanges(batch);
7272
7909
  }
7273
- if (client._pushedSeqs.get(ns)?.has(seqNum)) {
7274
- const queue2 = client._pendingOrderedMsgs.get(ns);
7275
- queue2?.delete(seqNum);
7276
- if (queue2 && queue2.size === 0) client._pendingOrderedMsgs.delete(ns);
7277
- return false;
7278
- }
7279
- const contig = client._seqTracker.getContiguousSeq(ns);
7280
- if (seqNum > contig) {
7281
- this.enqueueOrderedMessage(ns, event, seqNum, payload);
7282
- return false;
7283
- }
7284
- await this.drainOrderedMessages(ns, seqNum, true);
7285
- if (client._pushedSeqs.get(ns)?.has(seqNum)) return false;
7286
- const queue = client._pendingOrderedMsgs.get(ns);
7287
- queue?.delete(seqNum);
7288
- if (queue && queue.size === 0) client._pendingOrderedMsgs.delete(ns);
7289
- await this.publishOrderedQueueItem(ns, event, seqNum, payload);
7290
- this.markPublishedSeq(ns, seqNum);
7291
- await this.drainOrderedMessages(ns);
7292
- if (!client._pendingOrderedMsgs.get(ns)) await this.saveSeqTrackerState();
7293
- return true;
7294
7910
  }
7295
- async publishPulledMessage(event, ns, seq2, payload, persist = true) {
7911
+ async publishPulledMessage(event, ns, seq2, payload, persist = true, source = "pull", operationBatch) {
7296
7912
  const client = this.runtime.client;
7297
- const seqNum = Number(seq2);
7298
- 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);
7299
7941
  if (event === "message.recalled") {
7300
- return await client._withPullResponseProcessing(
7942
+ const published = await client._withPullResponseProcessing(
7301
7943
  ns,
7302
- () => this.publishMessageRecallTombstone(seq2, payload)
7944
+ () => this.publishMessageRecallTombstone(seqNum, payload)
7303
7945
  );
7946
+ this.ensurePullOperationCurrent();
7947
+ this.markPublishedSeq(ns, seqNum);
7948
+ return published;
7304
7949
  }
7305
- await client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload));
7306
- return true;
7307
- }
7308
- const queue = client._pendingOrderedMsgs.get(ns);
7309
- if (client._pushedSeqs.get(ns)?.has(seqNum)) {
7310
- queue?.delete(seqNum);
7311
- if (queue && queue.size === 0) client._pendingOrderedMsgs.delete(ns);
7312
- return false;
7313
- }
7314
- await this.drainOrderedMessages(ns, seqNum, false, persist);
7315
- queue?.delete(seqNum);
7316
- if (queue && queue.size === 0) client._pendingOrderedMsgs.delete(ns);
7317
- if (event === "message.recalled") {
7318
- const published = await client._withPullResponseProcessing(
7319
- ns,
7320
- () => this.publishMessageRecallTombstone(seqNum, payload)
7321
- );
7950
+ await client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload, source, ns, batch));
7951
+ this.ensurePullOperationCurrent();
7322
7952
  this.markPublishedSeq(ns, seqNum);
7323
- return published;
7953
+ return true;
7954
+ } finally {
7955
+ if (ownsBatch && batch) await this.flushRealtimeDeliveryChanges(batch);
7324
7956
  }
7325
- await client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload));
7326
- this.markPublishedSeq(ns, seqNum);
7327
- return true;
7328
7957
  }
7329
7958
  };
7330
7959
 
@@ -7513,12 +8142,7 @@ var LifecycleController = class {
7513
8142
  async publishConnectionEvent(owner, event, payload) {
7514
8143
  const client = this.runtime.client;
7515
8144
  this.assertConnectionAttemptOwner(owner);
7516
- client._connectEventDispatchDepth = Number(client._connectEventDispatchDepth ?? 0) + 1;
7517
- try {
7518
- await client._dispatcher.publish(event, payload);
7519
- } finally {
7520
- client._connectEventDispatchDepth -= 1;
7521
- }
8145
+ client._dispatcher.enqueue(event, payload);
7522
8146
  return this.ownsConnectionAttempt(owner);
7523
8147
  }
7524
8148
  async cancelConnectionAttemptAndWait() {
@@ -7826,14 +8450,9 @@ var LifecycleController = class {
7826
8450
  client._clientLog.warn(`reentrant close failed: ${err instanceof Error ? err.message : String(err)}`);
7827
8451
  });
7828
8452
  }
7829
- async publishLifecycleStopEvent(payload) {
8453
+ publishLifecycleStopEvent(payload) {
7830
8454
  const client = this.runtime.client;
7831
- client._lifecycleStopEventDispatchDepth = Number(client._lifecycleStopEventDispatchDepth ?? 0) + 1;
7832
- try {
7833
- await client._dispatcher.publish("state_change", payload);
7834
- } finally {
7835
- client._lifecycleStopEventDispatchDepth -= 1;
7836
- }
8455
+ client._dispatcher.enqueue("state_change", payload);
7837
8456
  }
7838
8457
  async withLifecycleStop(operation, kind) {
7839
8458
  const client = this.runtime.client;
@@ -7884,14 +8503,17 @@ var LifecycleController = class {
7884
8503
  return;
7885
8504
  }
7886
8505
  client._delivery.resetInlineAckState();
8506
+ const pullInvalidation = client._rpcPipeline?.invalidatePulls?.();
8507
+ client._rpcPipeline?.stopPullGateWatchdogs?.();
8508
+ await client._transport.close();
8509
+ await pullInvalidation;
7887
8510
  await client._cancelReconnectAndWait();
7888
8511
  await this.cancelConnectionAttemptAndWait();
7889
8512
  await client._saveSeqTrackerState();
7890
8513
  client._stopBackgroundTasks();
7891
- await client._transport.close();
7892
8514
  if (client._closing) return;
7893
8515
  this.runtime.lifecycle.resetForDisconnect("standby");
7894
- await this.publishLifecycleStopEvent({ state: client._publicState(client._state) });
8516
+ this.publishLifecycleStopEvent({ state: client._publicState(client._state) });
7895
8517
  client._clientLog.debug(`disconnect exit: elapsed=${Date.now() - tStart}ms`);
7896
8518
  }, "disconnect");
7897
8519
  } finally {
@@ -7901,6 +8523,7 @@ var LifecycleController = class {
7901
8523
  async close() {
7902
8524
  const client = this.runtime.client;
7903
8525
  const tStart = Date.now();
8526
+ const calledFromHandler = client._dispatcher.isDispatchingHandler();
7904
8527
  client._clientLog.debug(`close enter: state=${client._state}`);
7905
8528
  this.runtime.lifecycle.setClosing(true);
7906
8529
  client._delivery.resetInlineAckState();
@@ -7909,26 +8532,39 @@ var LifecycleController = class {
7909
8532
  return;
7910
8533
  }
7911
8534
  return this.withLifecycleStop(async () => {
7912
- await client._cancelReconnectAndWait();
7913
- await this.cancelConnectionAttemptAndWait();
7914
- await client._saveSeqTrackerState();
7915
- client._stopBackgroundTasks();
7916
- if (client._state === "idle" || client._state === "closed") {
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]);
8559
+ await client._saveSeqTrackerState();
7917
8560
  this.runtime.lifecycle.setState("closed");
8561
+ this.publishLifecycleStopEvent({ state: client._publicState(client._state) });
7918
8562
  client._resetSeqTrackingState();
7919
- client._clientLog.debug(`close exit: elapsed=${Date.now() - tStart}ms reason=already_idle`);
7920
- return;
7921
- }
7922
- try {
7923
- await client._transport.call("auth.logout", {});
7924
- } catch (err) {
7925
- client._clientLog.warn(`auth.logout during close failed: ${err instanceof Error ? err.message : String(err)}`);
8563
+ client._clientLog.debug(`close exit: elapsed=${Date.now() - tStart}ms`);
8564
+ } finally {
8565
+ const closing = client._dispatcher.close();
8566
+ if (!calledFromHandler) await closing;
7926
8567
  }
7927
- await client._transport.close();
7928
- this.runtime.lifecycle.setState("closed");
7929
- await this.publishLifecycleStopEvent({ state: client._publicState(client._state) });
7930
- client._resetSeqTrackingState();
7931
- client._clientLog.debug(`close exit: elapsed=${Date.now() - tStart}ms`);
7932
8568
  }, "close");
7933
8569
  }
7934
8570
  };
@@ -14725,6 +15361,14 @@ var PROTECTED_HEADERS_METHODS = /* @__PURE__ */ new Set([
14725
15361
  "message.thought.put",
14726
15362
  "group.thought.put"
14727
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
+ }
14728
15372
  var SIGNED_METHODS = /* @__PURE__ */ new Set([
14729
15373
  "message.send",
14730
15374
  "message.v2.put_peer_pk",
@@ -14829,6 +15473,7 @@ var SIGNED_METHODS = /* @__PURE__ */ new Set([
14829
15473
  "group.resume"
14830
15474
  ]);
14831
15475
  var SIGNING_KEY_CACHE_MAX = 32;
15476
+ var IDENTITY_ADMISSION_RETRY_DELAYS_MS = [0, 50, 100, 200];
14832
15477
  var signingKeyCache = /* @__PURE__ */ new Map();
14833
15478
  var certFingerprintCache = /* @__PURE__ */ new Map();
14834
15479
  function cacheGet(cache, key) {
@@ -14883,6 +15528,13 @@ async function signingCertFingerprint(certPem) {
14883
15528
  }
14884
15529
  var PULL_GATE_STALE_MS = 3e4;
14885
15530
  var PULL_GATE_OPERATION_TIMEOUT_MS = 3e3;
15531
+ function sameIdentityAdmissionRejection(error, original) {
15532
+ const errorCode2 = Number(error?.code);
15533
+ const originalCode = Number(original?.code);
15534
+ const errorMessage3 = String(error instanceof Error ? error.message : error?.message ?? "").trim().toLowerCase();
15535
+ const originalMessage = String(original instanceof Error ? original.message : original?.message ?? "").trim().toLowerCase();
15536
+ return errorCode2 === -32003 && originalCode === -32003 && errorMessage3 === originalMessage;
15537
+ }
14886
15538
  var NON_IDEMPOTENT_TIMEOUT = 35;
14887
15539
  var NON_IDEMPOTENT_METHODS = /* @__PURE__ */ new Set([
14888
15540
  "message.send",
@@ -14979,6 +15631,9 @@ var RpcPipeline = class {
14979
15631
  __publicField(this, "runtime");
14980
15632
  __publicField(this, "pullGateStates", /* @__PURE__ */ new Map());
14981
15633
  __publicField(this, "inlineRealtimeScopes", /* @__PURE__ */ new Set());
15634
+ __publicField(this, "pullWorkTokens", /* @__PURE__ */ new Set());
15635
+ __publicField(this, "pullGeneration", 0);
15636
+ __publicField(this, "pullInvalidationWait", null);
14982
15637
  this.runtime = runtime;
14983
15638
  }
14984
15639
  async call(method, params2) {
@@ -15184,6 +15839,9 @@ var RpcPipeline = class {
15184
15839
  delete p._rpc_background;
15185
15840
  if (method === "message.send" || method === "group.send") {
15186
15841
  this.normalizeOutboundMessagePayload(p, method);
15842
+ if (!Object.prototype.hasOwnProperty.call(p, "message_id") || p.message_id == null) {
15843
+ p.message_id = generateMessageId();
15844
+ }
15187
15845
  }
15188
15846
  this.normalizeGroupCallIdentifier(method, p);
15189
15847
  this.validateOutboundCall(method, p);
@@ -15348,19 +16006,21 @@ var RpcPipeline = class {
15348
16006
  if (method === "message.pull" || method === "message.v2.pull" || method === "message.history") {
15349
16007
  if (!client._aid) return "";
15350
16008
  const mode = method === "message.history" ? "history" : params2.window_mode === "tail" ? "tail" : "forward";
15351
- const cursor = mode === "history" ? `before=${String(params2.before_seq)}` : `after=${String(params2.after_seq ?? 0)}|force=${String(Boolean(params2.force))}`;
15352
- return `p2p:${client._aid}|${mode}|${cursor}|limit=${String(params2.limit)}`;
16009
+ const cursor = mode === "history" ? `before=${String(params2.before_seq)}` : `after=${String(params2.after_seq ?? 0)}`;
16010
+ return `p2p:${client._aid}|mode=${mode}|cursor=${cursor}|force=${String(Boolean(params2.force))}|limit=${String(params2.limit)}`;
15353
16011
  }
15354
16012
  if (method === "group.pull" || method === "group.v2.pull" || method === "group.history") {
15355
16013
  const gid = String(params2.group_id ?? params2.group_aid ?? "").trim();
15356
16014
  if (!gid) return "";
15357
16015
  const mode = method === "group.history" ? "history" : params2.window_mode === "tail" ? "tail" : "forward";
15358
- const cursor = mode === "history" ? `before=${String(params2.before_seq)}` : `after=${String(params2.after_seq ?? params2.after_message_seq ?? 0)}|force=${String(Boolean(params2.force))}`;
15359
- return `group:${gid}|${mode}|${cursor}|limit=${String(params2.limit)}`;
16016
+ const cursor = mode === "history" ? `before=${String(params2.before_seq)}` : `after=${String(params2.after_seq ?? params2.after_message_seq ?? 0)}`;
16017
+ const explicitCursor = this.explicitGroupCursorParams(params2);
16018
+ const cursorSuffix = Object.keys(explicitCursor).length > 0 ? `|cursor_params=${stableStringify(explicitCursor)}` : "";
16019
+ return `group:${gid}|mode=${mode}|cursor=${cursor}${cursorSuffix}|force=${String(Boolean(params2.force))}|limit=${String(params2.limit)}`;
15360
16020
  }
15361
16021
  if (method === "group.pull_events") {
15362
16022
  const gid = String(params2.group_id ?? params2.group_aid ?? "").trim();
15363
- return gid ? `group_event:${gid}|forward|after=${String(params2.after_event_seq ?? 0)}|limit=${String(params2.limit)}` : "";
16023
+ return gid ? `group_event:${gid}|mode=forward|cursor=after=${String(params2.after_event_seq ?? 0)}|force=${String(Boolean(params2.force))}|limit=${String(params2.limit)}` : "";
15364
16024
  }
15365
16025
  return "";
15366
16026
  }
@@ -15380,8 +16040,8 @@ var RpcPipeline = class {
15380
16040
  if (!state) {
15381
16041
  state = { active: null, foreground: [], background: [], byKey: /* @__PURE__ */ new Map(), lifecycle: /* @__PURE__ */ new Map(), watchdog: null };
15382
16042
  this.pullGateStates.set(name, state);
15383
- this.startPullGateWatchdog(state, name);
15384
16043
  }
16044
+ this.startPullGateWatchdog(state, name);
15385
16045
  return state;
15386
16046
  }
15387
16047
  pullGateOperationTimeoutMs() {
@@ -15398,6 +16058,13 @@ var RpcPipeline = class {
15398
16058
  state.watchdog = timer;
15399
16059
  timer.unref?.();
15400
16060
  }
16061
+ stopPullGateWatchdogs() {
16062
+ for (const state of this.pullGateStates.values()) {
16063
+ if (state.watchdog === null) continue;
16064
+ clearInterval(state.watchdog);
16065
+ state.watchdog = null;
16066
+ }
16067
+ }
15401
16068
  checkPullGate(state, name) {
15402
16069
  const active = state.active;
15403
16070
  if (!active) {
@@ -15407,17 +16074,12 @@ var RpcPipeline = class {
15407
16074
  if (!active.pullingStartedAt || Date.now() - active.pullingStartedAt < this.pullGateOperationTimeoutMs()) return;
15408
16075
  if (active.timedOut) return;
15409
16076
  active.timedOut = true;
16077
+ active.invalidated = true;
16078
+ this.pullGeneration += 1;
15410
16079
  active.cancel?.();
15411
16080
  const err = new TimeoutError(`pull gate timeout: ${active.namespace}`, { retryable: true });
15412
16081
  this.runtime.client._clientLog?.warn(`pull gate watchdog timeout: gate=${name} key=${active.key}`);
15413
- state.lifecycle.set(active.namespace, "idle");
15414
- if (state.active === active) state.active = null;
15415
- if (state.byKey.get(active.key) === active) state.byKey.delete(active.key);
15416
- active.resolve = () => {
15417
- };
15418
16082
  active.reject(err);
15419
- this.drainPullGate(state);
15420
- this.runtime.client._delivery?.onPullGateIdle?.(active.namespace);
15421
16083
  }
15422
16084
  realtimePullNamespace(key) {
15423
16085
  for (const marker of ["|tail|", "|forward|"]) {
@@ -15436,7 +16098,7 @@ var RpcPipeline = class {
15436
16098
  if (typeof cancel !== "function" || !key) return request;
15437
16099
  const state = this.pullGateStates.get(this.pullGateName(key));
15438
16100
  const active = state?.active;
15439
- if (!active || active.namespace !== this.pullScopeKey(key) || active.timedOut) return request;
16101
+ if (!active || active.key !== key || active.timedOut || active.invalidated) return request;
15440
16102
  const boundCancel = () => cancel.call(request);
15441
16103
  active.cancel = boundCancel;
15442
16104
  return request.finally(() => {
@@ -15484,6 +16146,28 @@ var RpcPipeline = class {
15484
16146
  const matches = (job) => job !== null && job.namespace === ns;
15485
16147
  return matches(state.active) || state.foreground.some(matches) || state.background.some(matches);
15486
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
+ }
15487
16171
  async tryRunInlineRealtime(ns, seq2, operation) {
15488
16172
  const normalized = String(ns ?? "").trim();
15489
16173
  if (!normalized.startsWith("p2p:") && !normalized.startsWith("group:") || !Number.isSafeInteger(seq2) || seq2 <= 0 || typeof operation !== "function") {
@@ -15522,7 +16206,11 @@ var RpcPipeline = class {
15522
16206
  resumeResolve: null,
15523
16207
  pullingStartedAt: 0,
15524
16208
  timedOut: false,
15525
- cancel: null
16209
+ invalidated: false,
16210
+ cancel: null,
16211
+ settled: Promise.resolve(),
16212
+ settledResolve: null,
16213
+ workToken: Symbol("inline-pull-work")
15526
16214
  };
15527
16215
  this.inlineRealtimeScopes.add(normalized);
15528
16216
  try {
@@ -15562,14 +16250,68 @@ var RpcPipeline = class {
15562
16250
  gate.inflight = false;
15563
16251
  gate.startedAt = 0;
15564
16252
  }
16253
+ isPullGenerationCurrent(generation) {
16254
+ return generation === this.pullGeneration;
16255
+ }
16256
+ invalidatePulls() {
16257
+ if (this.pullInvalidationWait) return this.pullInvalidationWait;
16258
+ this.pullGeneration += 1;
16259
+ const error = new Error("pull invalidated");
16260
+ const waits = [];
16261
+ for (const state of this.pullGateStates.values()) {
16262
+ const jobs = /* @__PURE__ */ new Set();
16263
+ if (state.active) jobs.add(state.active);
16264
+ for (const job of state.foreground) jobs.add(job);
16265
+ for (const job of state.background) jobs.add(job);
16266
+ for (const job of jobs) {
16267
+ job.invalidated = true;
16268
+ if (!job.timedOut) job.reject(error);
16269
+ job.cancel?.();
16270
+ job.resumeResolve?.();
16271
+ job.resumeResolve = null;
16272
+ if (job.running) {
16273
+ waits.push(job.settled);
16274
+ } else {
16275
+ this.releasePullWorkToken(job.workToken);
16276
+ job.settledResolve?.();
16277
+ job.settledResolve = null;
16278
+ }
16279
+ }
16280
+ state.foreground = state.foreground.filter((job) => !jobs.has(job));
16281
+ state.background = state.background.filter((job) => !jobs.has(job));
16282
+ for (const [key, job] of state.byKey.entries()) {
16283
+ if (jobs.has(job) && !job.running) state.byKey.delete(key);
16284
+ }
16285
+ this.drainPullGate(state);
16286
+ }
16287
+ const wait = Promise.all(waits).then(() => void 0);
16288
+ this.runtime.client._delivery?.onPullWorkSettled?.();
16289
+ let tracked;
16290
+ tracked = wait.finally(() => {
16291
+ if (this.pullInvalidationWait === tracked) this.pullInvalidationWait = null;
16292
+ });
16293
+ this.pullInvalidationWait = tracked;
16294
+ return tracked;
16295
+ }
16296
+ pullInvalidationInProgress() {
16297
+ return this.pullInvalidationWait !== null;
16298
+ }
15565
16299
  async runPullSerialized(key, operation, background = false) {
15566
- if (!key) return await this.executePullOperation(operation, background);
16300
+ if (this.pullInvalidationInProgress()) {
16301
+ throw new Error("pull invalidated");
16302
+ }
16303
+ if (!key) {
16304
+ const releasePullWork = this.reservePullWork();
16305
+ try {
16306
+ return await this.executePullOperation(operation, background);
16307
+ } finally {
16308
+ releasePullWork();
16309
+ }
16310
+ }
15567
16311
  const state = this.pullGateState(key);
15568
16312
  const namespace = this.pullScopeKey(key);
15569
- const active = state.active?.namespace === namespace ? state.active : null;
15570
- const queuedForeground = state.foreground.find((job2) => job2.namespace === namespace);
15571
- const queuedBackground = state.background.find((job2) => job2.namespace === namespace);
15572
- const existing = background ? active ?? queuedForeground ?? queuedBackground : queuedForeground ?? (active && !active.background ? active : null) ?? queuedBackground;
16313
+ const candidate = state.byKey.get(key);
16314
+ const existing = candidate && !candidate.timedOut && !candidate.invalidated ? candidate : null;
15573
16315
  if (existing) {
15574
16316
  if (!background && existing.background && existing !== state.active) {
15575
16317
  const index = state.background.indexOf(existing);
@@ -15579,12 +16321,18 @@ var RpcPipeline = class {
15579
16321
  }
15580
16322
  return await existing.promise;
15581
16323
  }
16324
+ const workToken = Symbol("pull-work");
16325
+ this.pullWorkTokens.add(workToken);
15582
16326
  let resolve;
15583
16327
  let reject;
16328
+ let settledResolve;
15584
16329
  const promise = new Promise((res, rej) => {
15585
16330
  resolve = res;
15586
16331
  reject = rej;
15587
16332
  });
16333
+ const settled = new Promise((resolveSettled) => {
16334
+ settledResolve = resolveSettled;
16335
+ });
15588
16336
  void promise.catch(() => {
15589
16337
  });
15590
16338
  const job = {
@@ -15602,7 +16350,11 @@ var RpcPipeline = class {
15602
16350
  resumeResolve: null,
15603
16351
  pullingStartedAt: 0,
15604
16352
  timedOut: false,
15605
- cancel: null
16353
+ invalidated: false,
16354
+ cancel: null,
16355
+ settled,
16356
+ settledResolve,
16357
+ workToken
15606
16358
  };
15607
16359
  (background ? state.background : state.foreground).push(job);
15608
16360
  state.byKey.set(key, job);
@@ -15611,14 +16363,30 @@ var RpcPipeline = class {
15611
16363
  return await promise;
15612
16364
  }
15613
16365
  async executePullOperation(operation, background) {
15614
- if (background) return await this.runtime.client._withBackgroundRpc(operation);
15615
- return await operation();
16366
+ const client = this.runtime.client;
16367
+ const hadPrevious = Object.prototype.hasOwnProperty.call(client, "_pullOperationGeneration");
16368
+ const previous = client._pullOperationGeneration;
16369
+ client._pullOperationGeneration = this.pullGeneration;
16370
+ try {
16371
+ if (background) return await client._withBackgroundRpc(operation);
16372
+ return await operation();
16373
+ } finally {
16374
+ if (hadPrevious) client._pullOperationGeneration = previous;
16375
+ else delete client._pullOperationGeneration;
16376
+ }
16377
+ }
16378
+ pullOperationIsCurrent() {
16379
+ const generation = this.runtime.client._pullOperationGeneration;
16380
+ return generation === void 0 || this.isPullGenerationCurrent(generation);
16381
+ }
16382
+ throwIfPullInvalidated() {
16383
+ if (!this.pullOperationIsCurrent()) throw new Error("pull invalidated");
15616
16384
  }
15617
16385
  async yieldPullGate(key, nextKey, background) {
15618
16386
  if (!key) return;
15619
16387
  const state = this.pullGateState(key);
15620
16388
  const namespace = this.pullScopeKey(key);
15621
- const job = state.active?.namespace === namespace ? state.active : [...state.foreground, ...state.background].find((candidate) => candidate.namespace === namespace);
16389
+ const job = state.active?.key === key ? state.active : null;
15622
16390
  if (!job || state.active !== job) return;
15623
16391
  const replacementKey = String(nextKey || key);
15624
16392
  if (state.byKey.get(job.key) === job) state.byKey.delete(job.key);
@@ -15634,7 +16402,10 @@ var RpcPipeline = class {
15634
16402
  state.active = null;
15635
16403
  (job.background ? state.background : state.foreground).push(job);
15636
16404
  this.drainPullGate(state);
15637
- if (job.resume) await job.resume;
16405
+ if (job.resume) {
16406
+ await job.resume;
16407
+ if (job.invalidated) throw new Error("pull invalidated");
16408
+ }
15638
16409
  }
15639
16410
  getPullLifecycle(namespace) {
15640
16411
  const ns = this.pullScopeKey(namespace);
@@ -15670,13 +16441,23 @@ var RpcPipeline = class {
15670
16441
  return;
15671
16442
  }
15672
16443
  job.running = true;
15673
- void this.executePullOperation(job.operation, job.background).then(job.resolve, job.reject).finally(() => {
15674
- if (job.timedOut) return;
16444
+ void this.executePullOperation(job.operation, job.background).then(
16445
+ (value) => {
16446
+ if (!job.timedOut && !job.invalidated) job.resolve(value);
16447
+ },
16448
+ (error) => {
16449
+ if (!job.timedOut && !job.invalidated) job.reject(error);
16450
+ }
16451
+ ).finally(() => {
16452
+ this.pullWorkTokens.delete(job.workToken);
15675
16453
  state.lifecycle.set(job.namespace, "idle");
15676
16454
  if (state.active === job) state.active = null;
15677
16455
  if (state.byKey.get(job.key) === job) state.byKey.delete(job.key);
15678
16456
  this.drainPullGate(state);
15679
- this.runtime.client._delivery?.onPullGateIdle?.(job.namespace);
16457
+ job.settledResolve?.();
16458
+ job.settledResolve = null;
16459
+ if (!job.invalidated) this.runtime.client._delivery?.onPullGateIdle?.(job.namespace);
16460
+ this.runtime.client._delivery?.onPullWorkSettled?.();
15680
16461
  });
15681
16462
  }
15682
16463
  armQueuedPullTimers(_state) {
@@ -15699,7 +16480,9 @@ var RpcPipeline = class {
15699
16480
  else request = client._transport.call(method, payload);
15700
16481
  return this.bindActivePullCancellation(method, payload, request);
15701
16482
  };
15702
- return await this.transportCallWithIdentityRecovery(invokeTransport, method, payload);
16483
+ const result = await this.transportCallWithIdentityRecovery(invokeTransport, method, payload);
16484
+ this.throwIfPullInvalidated();
16485
+ return result;
15703
16486
  }
15704
16487
  async transportCallWithIdentityRecovery(operation, method, params2) {
15705
16488
  try {
@@ -15707,11 +16490,22 @@ var RpcPipeline = class {
15707
16490
  } catch (err) {
15708
16491
  const recover = this.runtime.client._recoverIdentityAdmission;
15709
16492
  if (typeof recover !== "function" || !await recover.call(this.runtime.client, err, method, params2)) throw err;
15710
- return await operation();
16493
+ let lastError = err;
16494
+ for (const delay of IDENTITY_ADMISSION_RETRY_DELAYS_MS) {
16495
+ if (delay > 0) await new Promise((resolve) => setTimeout(resolve, delay));
16496
+ try {
16497
+ return await operation();
16498
+ } catch (retryError) {
16499
+ if (!sameIdentityAdmissionRejection(retryError, err)) throw retryError;
16500
+ lastError = retryError;
16501
+ }
16502
+ }
16503
+ throw lastError;
15711
16504
  }
15712
16505
  }
15713
16506
  async postprocessResult(method, params2, result, options = {}) {
15714
16507
  const client = this.runtime.client;
16508
+ this.throwIfPullInvalidated();
15715
16509
  let next = result;
15716
16510
  if ((method === "group.send" || method === "group.v2.send") && isJsonObject(next)) {
15717
16511
  next = normalizeGroupMentionMode(next);
@@ -17412,17 +18206,21 @@ var GroupFacade = class extends RpcFacade {
17412
18206
  }
17413
18207
  create(params2) {
17414
18208
  const split = splitAidStore(params2);
17415
- if (typeof this.client.createGroup === "function") {
18209
+ if (split.params.group_name === void 0 && split.params.groupName !== void 0) {
18210
+ split.params.group_name = split.params.groupName;
18211
+ delete split.params.groupName;
18212
+ }
18213
+ if (split.aidStore && typeof this.client.createGroup === "function") {
17416
18214
  return this.client.createGroup(split.params, { aidStore: split.aidStore });
17417
18215
  }
17418
18216
  return this.call("group.create", split.params);
17419
18217
  }
17420
18218
  bindAid(params2) {
17421
- return this.bindGroupAid(params2);
18219
+ return this.call("group.bind_aid", splitAidStore(params2).params);
17422
18220
  }
17423
18221
  bindGroupAid(params2) {
17424
18222
  const split = splitAidStore(params2);
17425
- if (typeof this.client.bindGroupAid === "function") {
18223
+ if (split.aidStore && typeof this.client.bindGroupAid === "function") {
17426
18224
  return this.client.bindGroupAid(split.params, { aidStore: split.aidStore });
17427
18225
  }
17428
18226
  return this.call("group.bind_group_aid", split.params);
@@ -22795,6 +23593,10 @@ var V2Session = class {
22795
23593
  };
22796
23594
 
22797
23595
  // src/client/v2-e2ee.ts
23596
+ function pullGateKeyForClient(client, method, params2, fallback) {
23597
+ const key = client._rpcPipeline?.pullGateKeyForCall?.(method, params2);
23598
+ return typeof key === "string" && key ? key : fallback;
23599
+ }
22798
23600
  var V2_BOOTSTRAP_TTL_MS = 60 * 60 * 1e3;
22799
23601
  var V2_RETRYABLE_CODES = /* @__PURE__ */ new Set([-33011, -33012, -33050, -33052, -33054]);
22800
23602
  var V2_GROUP_STALE_BOOTSTRAP_CODE = -33054;
@@ -23552,11 +24354,22 @@ var V2E2EECoordinator = class {
23552
24354
  const fetchKey = this.pendingSenderIKFetchKey(fromAid, senderDeviceId, groupId);
23553
24355
  if (!fromAid || client._v2SenderIKFetching.has(fetchKey)) return;
23554
24356
  client._v2SenderIKFetching.add(fetchKey);
23555
- 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));
23556
24367
  }
23557
- async resolveSenderIKPending(fromAid, senderDeviceId, groupId, fetchKey) {
24368
+ async resolveSenderIKPending(fromAid, senderDeviceId, groupId, fetchKey, generation) {
23558
24369
  const client = this.client;
24370
+ const generationCurrent = () => generation === void 0 || client._delivery.isInlineGenerationCurrent?.(generation) !== false;
23559
24371
  try {
24372
+ if (!generationCurrent()) return;
23560
24373
  const session = client._v2Session;
23561
24374
  if (session && fromAid) {
23562
24375
  try {
@@ -23587,17 +24400,21 @@ var V2E2EECoordinator = class {
23587
24400
  await this.getV2SenderPubDer(fromAid, senderDeviceId);
23588
24401
  }
23589
24402
  }
24403
+ if (!generationCurrent()) return;
23590
24404
  const pendingItems = [...client._v2SenderIKPending.entries()].filter(([, entry]) => entry.fromAid === fromAid && entry.senderDeviceId === senderDeviceId && entry.groupId === groupId);
23591
24405
  for (const [key, entry] of pendingItems) {
24406
+ if (!generationCurrent()) return;
23592
24407
  let plaintext = null;
23593
24408
  const retryStatus = {};
23594
24409
  try {
23595
24410
  plaintext = await client._decryptV2Message(entry.msg, false, true, true, true, retryStatus);
23596
24411
  } catch (exc) {
24412
+ if (!generationCurrent()) return;
23597
24413
  client._clientLog.warn(`V2 sender IK pending retry raised: key=${key} err=${String(formatE2EEError(exc))}`);
23598
24414
  client._v2SenderIKPending.delete(key);
23599
24415
  continue;
23600
24416
  }
24417
+ if (!generationCurrent()) return;
23601
24418
  if (plaintext === null) {
23602
24419
  if (retryStatus.deferred) {
23603
24420
  client._clientLog.warn(`V2 pending retry still missing key material: key=${key}`);
@@ -23649,6 +24466,7 @@ var V2E2EECoordinator = class {
23649
24466
  return client.call("message.send", {
23650
24467
  to: toAid,
23651
24468
  payload: envelope,
24469
+ message_id: opts?.messageId,
23652
24470
  encrypt: false,
23653
24471
  _skip_send_result_envelope: true
23654
24472
  });
@@ -23725,7 +24543,8 @@ var V2E2EECoordinator = class {
23725
24543
  event.event,
23726
24544
  client._aid ? `p2p:${client._aid}` : "",
23727
24545
  seq2,
23728
- event.payload
24546
+ event.payload,
24547
+ source
23729
24548
  );
23730
24549
  } else {
23731
24550
  await client._publishAppEvent(event.event, event.payload, source);
@@ -23784,7 +24603,8 @@ var V2E2EECoordinator = class {
23784
24603
  "group.message_created",
23785
24604
  `group:${groupId}`,
23786
24605
  seq2,
23787
- plaintext
24606
+ plaintext,
24607
+ source
23788
24608
  );
23789
24609
  } else {
23790
24610
  await client._publishAppEvent("group.message_created", plaintext, source);
@@ -23854,7 +24674,11 @@ var V2E2EECoordinator = class {
23854
24674
  const ns = client._aid ? `p2p:${client._aid}` : "";
23855
24675
  if (opts?.windowMode === "tail") {
23856
24676
  if (!opts.gateLocked) {
23857
- const key = `${ns}|tail|after=${afterSeq}|limit=${limit}`;
24677
+ const key = pullGateKeyForClient(client, "message.v2.pull", {
24678
+ window_mode: "tail",
24679
+ after_seq: afterSeq,
24680
+ limit
24681
+ }, `${ns}|tail|after=${afterSeq}|limit=${limit}`);
23858
24682
  return await client._runPullSerialized(key, () => this.pullV2(afterSeq, limit, {
23859
24683
  ...opts ?? {},
23860
24684
  gateLocked: true
@@ -23864,7 +24688,11 @@ var V2E2EECoordinator = class {
23864
24688
  return Array.isArray(result.messages) ? result.messages : [];
23865
24689
  }
23866
24690
  if (ns && !opts?.gateLocked) {
23867
- const key = `${ns}|forward|after=${afterSeq}|force=${String(Boolean(opts?.force))}|limit=${limit}`;
24691
+ const key = pullGateKeyForClient(client, "message.v2.pull", {
24692
+ after_seq: afterSeq,
24693
+ force: opts?.force === true,
24694
+ limit
24695
+ }, `${ns}|forward|after=${afterSeq}|force=${String(Boolean(opts?.force))}|limit=${limit}`);
23868
24696
  return await client._runPullSerialized(key, () => this.pullV2(afterSeq, limit, {
23869
24697
  ...opts ?? {},
23870
24698
  gateLocked: true
@@ -23872,7 +24700,11 @@ var V2E2EECoordinator = class {
23872
24700
  }
23873
24701
  const decrypted = [];
23874
24702
  let nextAfterSeq = opts?.force ? afterSeq : afterSeq || (ns ? client._seqTracker.getContiguousSeq(ns) : 0);
23875
- let pullGateKey = ns ? `${ns}|forward|after=${afterSeq}|force=${String(Boolean(opts?.force))}|limit=${limit}` : "";
24703
+ let pullGateKey = ns ? pullGateKeyForClient(client, "message.v2.pull", {
24704
+ after_seq: afterSeq,
24705
+ force: opts?.force === true,
24706
+ limit
24707
+ }, `${ns}|forward|after=${afterSeq}|force=${String(Boolean(opts?.force))}|limit=${limit}`) : "";
23876
24708
  const deferredServerCursor = ns ? this.pendingForwardCursor(ns, client._seqTracker.getContiguousSeq(ns)) : 0;
23877
24709
  const deferredForwardAck = ns ? this.pendingForwardAck(ns, client._seqTracker.getContiguousSeq(ns)) : 0;
23878
24710
  let pageCount = 0;
@@ -23917,6 +24749,7 @@ var V2E2EECoordinator = class {
23917
24749
  const pageTrackerSnapshot = ns && typeof client._seqTracker.snapshotNamespace === "function" ? client._seqTracker.snapshotNamespace(ns) : null;
23918
24750
  const pageMaxSeq = seqs.length > 0 ? Math.max(...seqs) : nextAfterSeq;
23919
24751
  const deferredKeyFetches = /* @__PURE__ */ new Map();
24752
+ let blockedSeq = 0;
23920
24753
  for (const msg of messages) {
23921
24754
  const seq2 = Number(msg.seq ?? 0);
23922
24755
  if (!Number.isFinite(seq2) || seq2 <= 0) continue;
@@ -23939,7 +24772,7 @@ var V2E2EECoordinator = class {
23939
24772
  attachGatewayProximity(v1Msg, msg);
23940
24773
  const appEvent = client._delivery.p2pAppEventForMessage(v1Msg);
23941
24774
  if (ns) await client._publishPulledMessage(appEvent.event, ns, seq2, appEvent.payload, false);
23942
- else await client._publishAppEvent(appEvent.event, appEvent.payload);
24775
+ else await client._publishAppEvent(appEvent.event, appEvent.payload, "pull");
23943
24776
  decrypted.push(v1Msg);
23944
24777
  } else {
23945
24778
  client._clientLog.debug(`message.v2.pull skipping V1 envelope seq=${seq2} payload_type=${payloadType || "<none>"} (V1 E2EE removed)`);
@@ -23956,6 +24789,9 @@ var V2E2EECoordinator = class {
23956
24789
  }
23957
24790
  const deferStatus = {};
23958
24791
  const plaintext = await client._decryptV2Message(msg, true, true, true, true, deferStatus, true);
24792
+ if (deferStatus.deferred) {
24793
+ blockedSeq = blockedSeq > 0 ? Math.min(blockedSeq, seq2) : seq2;
24794
+ }
23959
24795
  if (deferStatus.deferred && deferStatus.fromAid) {
23960
24796
  const key = `${deferStatus.fromAid}\0${deferStatus.senderDeviceId ?? ""}\0${deferStatus.groupId ?? ""}`;
23961
24797
  deferredKeyFetches.set(key, {
@@ -23969,7 +24805,7 @@ var V2E2EECoordinator = class {
23969
24805
  await client._publishPulledMessage("message.received", ns, seq2, plaintext, false);
23970
24806
  decrypted.push(plaintext);
23971
24807
  } else {
23972
- await client._publishAppEvent("message.received", plaintext);
24808
+ await client._publishAppEvent("message.received", plaintext, "pull");
23973
24809
  decrypted.push(plaintext);
23974
24810
  }
23975
24811
  }
@@ -23978,7 +24814,7 @@ var V2E2EECoordinator = class {
23978
24814
  const serverAckSeq = parsedServerAckSeq ?? 0;
23979
24815
  const retentionFloor = Math.max(0, Number(result.retention_floor_seq ?? 0));
23980
24816
  if (ns) {
23981
- const commitTarget = Math.max(
24817
+ const commitTarget = blockedSeq > 0 ? pageContigBefore : Math.max(
23982
24818
  pageContigBefore,
23983
24819
  Number.isFinite(retentionFloor) ? retentionFloor : 0,
23984
24820
  pageCount === 1 ? deferredServerCursor : 0,
@@ -24020,7 +24856,7 @@ var V2E2EECoordinator = class {
24020
24856
  this.recordForwardCursor(ns, serverAckSeq, ackSeq);
24021
24857
  }
24022
24858
  const knownServerAckSeq = hasServerAckSeq ? serverAckSeq : pageCount === 1 ? deferredServerCursor : 0;
24023
- const ackNeeded = ackSeq > 0 && ackSeq > lastAutoAckSeq && (hasServerAckSeq && ackSeq > serverAckSeq || contigAdvanced && ackSeq > knownServerAckSeq);
24859
+ const ackNeeded = blockedSeq <= 0 && ackSeq > 0 && ackSeq > lastAutoAckSeq && (hasServerAckSeq && ackSeq > serverAckSeq || contigAdvanced && ackSeq > knownServerAckSeq);
24024
24860
  if (ackNeeded) {
24025
24861
  this.recordForwardAck(ns, ackSeq);
24026
24862
  const nextAfter2 = Math.max(pageMaxSeq, nextAfterSeq);
@@ -24036,6 +24872,7 @@ var V2E2EECoordinator = class {
24036
24872
  }
24037
24873
  const nextAfter = Math.max(pageMaxSeq, nextAfterSeq);
24038
24874
  const rawCount = messages.length;
24875
+ if (blockedSeq > 0) break;
24039
24876
  const shouldContinue = shouldContinueForwardPage({
24040
24877
  rawCount,
24041
24878
  nextAfterSeq,
@@ -24048,7 +24885,11 @@ var V2E2EECoordinator = class {
24048
24885
  });
24049
24886
  if (!shouldContinue) break;
24050
24887
  if (pullGateKey && opts?.gateLocked && client._rpcPipeline?.yieldPullGate) {
24051
- const nextKey = `${ns}|forward|after=${nextAfter}|force=${String(Boolean(opts?.force))}|limit=${limit}`;
24888
+ const nextKey = pullGateKeyForClient(client, "message.v2.pull", {
24889
+ after_seq: nextAfter,
24890
+ force: opts?.force === true,
24891
+ limit
24892
+ }, `${ns}|forward|after=${nextAfter}|force=${String(Boolean(opts?.force))}|limit=${limit}`);
24052
24893
  await client._rpcPipeline.yieldPullGate(pullGateKey, nextKey, true);
24053
24894
  pullGateKey = nextKey;
24054
24895
  }
@@ -24140,7 +24981,8 @@ var V2E2EECoordinator = class {
24140
24981
  });
24141
24982
  return client.call("group.v2.send", withExplicitGroupAid({
24142
24983
  group_id: gid,
24143
- envelope
24984
+ envelope,
24985
+ message_id: opts?.messageId
24144
24986
  }, groupAid));
24145
24987
  };
24146
24988
  try {
@@ -24268,7 +25110,13 @@ var V2E2EECoordinator = class {
24268
25110
  const ns = `group:${gid}`;
24269
25111
  if (opts?.windowMode === "tail") {
24270
25112
  if (!opts.gateLocked) {
24271
- const key = `${ns}|tail|after=${afterSeq}|limit=${limit}`;
25113
+ const key = pullGateKeyForClient(client, "group.v2.pull", {
25114
+ group_id: gid,
25115
+ window_mode: "tail",
25116
+ after_seq: afterSeq,
25117
+ limit,
25118
+ _group_cursor_params: opts?.cursorParams
25119
+ }, `${ns}|tail|after=${afterSeq}|limit=${limit}`);
24272
25120
  return await client._runPullSerialized(key, () => this.pullGroupV2(gid, afterSeq, limit, {
24273
25121
  ...opts ?? {},
24274
25122
  gateLocked: true
@@ -24284,7 +25132,13 @@ var V2E2EECoordinator = class {
24284
25132
  return Array.isArray(result.messages) ? result.messages : [];
24285
25133
  }
24286
25134
  if (!opts?.gateLocked) {
24287
- const key = `${ns}|forward|after=${afterSeq}|force=${String(Boolean(opts?.force))}|limit=${limit}`;
25135
+ const key = pullGateKeyForClient(client, "group.v2.pull", {
25136
+ group_id: gid,
25137
+ after_seq: afterSeq,
25138
+ force: opts?.force === true,
25139
+ limit,
25140
+ _group_cursor_params: opts?.cursorParams
25141
+ }, `${ns}|forward|after=${afterSeq}|force=${String(Boolean(opts?.force))}|limit=${limit}`);
24288
25142
  return await client._runPullSerialized(key, () => this.pullGroupV2(gid, afterSeq, limit, {
24289
25143
  ...opts ?? {},
24290
25144
  gateLocked: true
@@ -24294,7 +25148,13 @@ var V2E2EECoordinator = class {
24294
25148
  const wireGroupId = String(opts?.wireGroupId ?? groupId ?? "").trim() || gid;
24295
25149
  const cursorParams = opts?.cursorParams ?? {};
24296
25150
  const ownsCursor = opts?.ownsCursor !== false;
24297
- let pullGateKey = ownsCursor ? `${ns}|forward|after=${afterSeq}|force=${String(Boolean(opts?.force))}|limit=${limit}` : "";
25151
+ let pullGateKey = ownsCursor ? pullGateKeyForClient(client, "group.v2.pull", {
25152
+ group_id: gid,
25153
+ after_seq: afterSeq,
25154
+ force: opts?.force === true,
25155
+ limit,
25156
+ _group_cursor_params: cursorParams
25157
+ }, `${ns}|forward|after=${afterSeq}|force=${String(Boolean(opts?.force))}|limit=${limit}`) : "";
24298
25158
  let nextAfterSeq = opts?.explicitAfterSeq || opts?.force ? afterSeq : afterSeq || client._seqTracker.getContiguousSeq(ns);
24299
25159
  const deferredServerCursor = ownsCursor ? this.pendingForwardCursor(ns, client._seqTracker.getContiguousSeq(ns)) : 0;
24300
25160
  const deferredForwardAck = ownsCursor ? this.pendingForwardAck(ns, client._seqTracker.getContiguousSeq(ns)) : 0;
@@ -24347,6 +25207,7 @@ var V2E2EECoordinator = class {
24347
25207
  const pageTrackerSnapshot = typeof client._seqTracker.snapshotNamespace === "function" ? client._seqTracker.snapshotNamespace(ns) : null;
24348
25208
  const pageMaxSeq = seqs.length > 0 ? Math.max(...seqs) : nextAfterSeq;
24349
25209
  const deferredKeyFetches = /* @__PURE__ */ new Map();
25210
+ let blockedSeq = 0;
24350
25211
  for (const msg of messages) {
24351
25212
  const seq2 = Number(msg.seq ?? 0);
24352
25213
  if (!Number.isFinite(seq2) || seq2 <= 0) continue;
@@ -24411,6 +25272,9 @@ var V2E2EECoordinator = class {
24411
25272
  }
24412
25273
  const deferStatus = {};
24413
25274
  let plaintext = await client._decryptV2Message(msg, true, true, true, true, deferStatus, true);
25275
+ if (deferStatus.deferred) {
25276
+ blockedSeq = blockedSeq > 0 ? Math.min(blockedSeq, seq2) : seq2;
25277
+ }
24414
25278
  if (deferStatus.deferred && deferStatus.fromAid) {
24415
25279
  const key = `${deferStatus.fromAid}\0${deferStatus.senderDeviceId ?? ""}\0${deferStatus.groupId ?? ""}`;
24416
25280
  deferredKeyFetches.set(key, {
@@ -24439,7 +25303,7 @@ var V2E2EECoordinator = class {
24439
25303
  Number(cursor?.join_seq ?? 0)
24440
25304
  );
24441
25305
  const effectiveFloor = Math.max(retentionFloor, visibilityFloor);
24442
- const commitTarget = Math.max(
25306
+ const commitTarget = blockedSeq > 0 ? pageContigBefore : Math.max(
24443
25307
  pageContigBefore,
24444
25308
  Number.isFinite(effectiveFloor) ? effectiveFloor : 0,
24445
25309
  ownsCursor && pageCount === 1 ? deferredServerCursor : 0,
@@ -24483,7 +25347,7 @@ var V2E2EECoordinator = class {
24483
25347
  this.recordForwardCursor(ns, cursorCurrentSeq, ackSeq);
24484
25348
  }
24485
25349
  const knownServerCursorSeq = hasServerCursor ? cursorCurrentSeq : ownsCursor && pageCount === 1 ? deferredServerCursor : 0;
24486
- const ackNeeded = ackSeq > 0 && ackSeq > lastAutoAckSeq && ownsCursor && (hasServerCursor && ackSeq > cursorCurrentSeq || contigAdvanced && ackSeq > knownServerCursorSeq);
25350
+ const ackNeeded = blockedSeq <= 0 && ackSeq > 0 && ackSeq > lastAutoAckSeq && ownsCursor && (hasServerCursor && ackSeq > cursorCurrentSeq || contigAdvanced && ackSeq > knownServerCursorSeq);
24487
25351
  if (ackNeeded) {
24488
25352
  this.recordForwardAck(ns, ackSeq);
24489
25353
  const nextAfter2 = Math.max(pageMaxSeq, nextAfterSeq);
@@ -24499,6 +25363,7 @@ var V2E2EECoordinator = class {
24499
25363
  const nextAfter = Math.max(pageMaxSeq, nextAfterSeq);
24500
25364
  if (!ownsCursor) break;
24501
25365
  const rawCount = messages.length;
25366
+ if (blockedSeq > 0) break;
24502
25367
  const shouldContinue = shouldContinueForwardPage({
24503
25368
  rawCount,
24504
25369
  nextAfterSeq,
@@ -24511,7 +25376,13 @@ var V2E2EECoordinator = class {
24511
25376
  });
24512
25377
  if (!shouldContinue) break;
24513
25378
  if (pullGateKey && opts?.gateLocked && client._rpcPipeline?.yieldPullGate) {
24514
- const nextKey = `${ns}|forward|after=${nextAfter}|force=${String(Boolean(opts?.force))}|limit=${limit}`;
25379
+ const nextKey = pullGateKeyForClient(client, "group.v2.pull", {
25380
+ group_id: gid,
25381
+ after_seq: nextAfter,
25382
+ force: opts?.force === true,
25383
+ limit,
25384
+ _group_cursor_params: cursorParams
25385
+ }, `${ns}|forward|after=${nextAfter}|force=${String(Boolean(opts?.force))}|limit=${limit}`);
24515
25386
  await client._rpcPipeline.yieldPullGate(pullGateKey, nextKey, true);
24516
25387
  pullGateKey = nextKey;
24517
25388
  }
@@ -25547,7 +26418,7 @@ var GroupStateCoordinator = class {
25547
26418
  if (!isJsonObject(data) || !client._v2Session) return;
25548
26419
  const groupId = groupIdFromRecord(data);
25549
26420
  if (!groupId) return;
25550
- await client._dispatcher.publish("group.v2.state_proposed", data);
26421
+ client._dispatcher.enqueue("group.v2.state_proposed", data);
25551
26422
  try {
25552
26423
  await client._v2ConfirmPendingProposal(groupId);
25553
26424
  } catch (exc) {
@@ -25559,7 +26430,7 @@ var GroupStateCoordinator = class {
25559
26430
  if (!isJsonObject(data) || !client._v2Session) return;
25560
26431
  const groupId = groupIdFromRecord(data);
25561
26432
  if (!groupId) return;
25562
- await client._dispatcher.publish("group.v2.state_retry_needed", data);
26433
+ client._dispatcher.enqueue("group.v2.state_retry_needed", data);
25563
26434
  try {
25564
26435
  await client._v2AutoProposeState(groupId, { leaderDelay: true });
25565
26436
  } catch (exc) {
@@ -25577,7 +26448,7 @@ var GroupStateCoordinator = class {
25577
26448
  }
25578
26449
  client._v2AutoProposeLastSnapshot?.delete?.(groupId);
25579
26450
  }
25580
- await client._dispatcher.publish("group.v2.state_confirmed", data);
26451
+ client._dispatcher.enqueue("group.v2.state_confirmed", data);
25581
26452
  }
25582
26453
  async publishV2GroupSecurityLevel(groupId, bootstrap) {
25583
26454
  const client = this.client;
@@ -25588,7 +26459,7 @@ var GroupStateCoordinator = class {
25588
26459
  const previous = securityLevels.get(gid);
25589
26460
  if (previous === level) return;
25590
26461
  securityLevels.set(gid, level);
25591
- await client._dispatcher.publish("group.v2.security_level", {
26462
+ client._dispatcher.enqueue("group.v2.security_level", {
25592
26463
  group_id: gid,
25593
26464
  level,
25594
26465
  warning: String(bootstrap.e2ee_security_warning ?? ""),
@@ -25683,7 +26554,7 @@ var GroupStateCoordinator = class {
25683
26554
  } catch {
25684
26555
  }
25685
26556
  client._clientLog.warn(`V2 state chain fork detected: group=${gid} local_chain=${localChain.slice(0, 16)}... server_chain=${serverChain.slice(0, 16)}...`);
25686
- await client._dispatcher.publish("group.v2.fork_detected", {
26557
+ client._dispatcher.enqueue("group.v2.fork_detected", {
25687
26558
  group_id: gid,
25688
26559
  local_chain: localChain,
25689
26560
  server_chain: serverChain
@@ -26084,7 +26955,7 @@ var GroupStateCoordinator = class {
26084
26955
  const proposalId = isJsonObject(proposeResult) ? String(proposeResult.proposal_id ?? "").trim() : "";
26085
26956
  if (proposalId) {
26086
26957
  try {
26087
- await client.call("group.v2.confirm_state", { proposal_id: proposalId });
26958
+ await client.call("group.v2.confirm_state", { proposal_id: proposalId, group_id: groupId });
26088
26959
  client._v2AutoProposeLastSnapshot.set(groupId, membershipSnapshot);
26089
26960
  client._clientLog.debug(`V2 auto confirm_state: group=${groupId} proposal=${proposalId}`);
26090
26961
  } catch (confirmExc) {
@@ -26137,7 +27008,7 @@ var GroupStateCoordinator = class {
26137
27008
  return false;
26138
27009
  }
26139
27010
  if (!await this.verifyPendingProposalAgainstBase(groupId, proposal, stateResp)) return false;
26140
- await client.call("group.v2.confirm_state", { proposal_id: proposalId });
27011
+ await client.call("group.v2.confirm_state", { proposal_id: proposalId, group_id: groupId });
26141
27012
  client._clientLog.info(`V2 confirmed pending proposal: group=${groupId} proposal=${proposalId}`);
26142
27013
  return true;
26143
27014
  }
@@ -26217,7 +27088,7 @@ var GroupStateCoordinator = class {
26217
27088
  }
26218
27089
  if (mode !== "open" && mode !== "invite_code" && mode !== "invite_only") {
26219
27090
  client._clientLog.warn(`V2 state tamper detected: group=${groupId} pending_extra=${extra.sort().join(",")} mode=${mode}`);
26220
- await client._dispatcher.publish("group.v2.state_tampered", {
27091
+ client._dispatcher.enqueue("group.v2.state_tampered", {
26221
27092
  group_id: groupId,
26222
27093
  pending_extra: extra.sort(),
26223
27094
  mode
@@ -27167,6 +28038,7 @@ function buildDefaultAgentMd(aid, options = {}) {
27167
28038
  ].join("\n");
27168
28039
  }
27169
28040
  var HEAD_HTTP_TIMEOUT_MS = 15e3;
28041
+ var AGENT_MD_NEGATIVE_CACHE_TTL_MS = 6e4;
27170
28042
  var noopLogger = {
27171
28043
  error: () => {
27172
28044
  },
@@ -27400,6 +28272,7 @@ var AgentMdManager = class _AgentMdManager {
27400
28272
  if (content !== void 0 && content !== null) {
27401
28273
  const text3 = String(content ?? "");
27402
28274
  if (text3.length === 0) throw new ValidationError("uploadAgentMd requires non-empty content");
28275
+ validateAgentMdDocument(text3, { expectedAid: target });
27403
28276
  await this.saveRecord(target, {
27404
28277
  content: text3,
27405
28278
  local_etag: await _AgentMdManager.contentEtag(text3),
@@ -27506,6 +28379,24 @@ var AgentMdManager = class _AgentMdManager {
27506
28379
  ttl_days: Number(ttlDays) || 0
27507
28380
  };
27508
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
+ }
27509
28400
  const now = Date.now();
27510
28401
  let remote;
27511
28402
  try {
@@ -27995,14 +28886,16 @@ var AgentMdManager = class _AgentMdManager {
27995
28886
  async _scheduleFetchIfMissing(aid, record, source = "") {
27996
28887
  const target = String(aid ?? "").trim();
27997
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;
27998
28892
  if (this._fetchInflight.has(target)) return;
27999
28893
  this._fetchInflight.add(target);
28000
28894
  try {
28001
28895
  await this.download(target);
28002
28896
  } catch (err) {
28003
28897
  await this.saveRecord(target, {
28004
- last_error: err instanceof Error ? err.message : String(err),
28005
- remote_status: "found"
28898
+ last_error: err instanceof Error ? err.message : String(err)
28006
28899
  });
28007
28900
  this._log.debug(`agent.md auto fetch failed: aid=${target} source=${source || "-"} err=${err instanceof Error ? err.message : String(err)}`);
28008
28901
  } finally {
@@ -28426,7 +29319,6 @@ var _AUNClient = class _AUNClient {
28426
29319
  __publicField(this, "_reconnectActive", false);
28427
29320
  __publicField(this, "_reconnectAbort", null);
28428
29321
  __publicField(this, "_reconnectTask", null);
28429
- __publicField(this, "_reconnectEventDispatchDepth", 0);
28430
29322
  __publicField(this, "_serverKicked", false);
28431
29323
  // 重连状态追踪(对齐 Python client.py)
28432
29324
  __publicField(this, "_nextRetryAt", null);
@@ -28606,7 +29498,7 @@ var _AUNClient = class _AUNClient {
28606
29498
  });
28607
29499
  for (const evt of ["message.ack", "storage.object_changed", "stream/created", "stream/closed"]) {
28608
29500
  this._dispatcher.subscribe(`_raw.${evt}`, (data) => {
28609
- this._dispatcher.publish(evt, data);
29501
+ this._dispatcher.enqueue(evt, data);
28610
29502
  });
28611
29503
  }
28612
29504
  this._dispatcher.subscribe("_raw.gateway.disconnect", async (data) => {
@@ -28877,6 +29769,7 @@ var _AUNClient = class _AUNClient {
28877
29769
  this._peerCache.clear();
28878
29770
  this._certCache.clear();
28879
29771
  this._gatewayUrl = null;
29772
+ this._gatewayCandidates = [];
28880
29773
  this._deviceId = aid.deviceId || getDeviceId();
28881
29774
  this._slotId = aid.slotId || "default";
28882
29775
  this._logger = new AUNLogger({ debug: aid.debug, aunPath: nextConfig.aunPath });
@@ -29056,7 +29949,7 @@ var _AUNClient = class _AUNClient {
29056
29949
  if (!message.toLowerCase().startsWith(agentPrefix)) return false;
29057
29950
  const target = message.slice(agentPrefix.length).trim();
29058
29951
  if (!target || target.toLowerCase() !== String(this._aid ?? "").trim().toLowerCase()) return false;
29059
- await this._agentMdManager.upload(buildDefaultAgentMd(target));
29952
+ await this._agentMdManager.upload(await this._agentMdManager.readContent(target) ?? buildDefaultAgentMd(target));
29060
29953
  return true;
29061
29954
  }
29062
29955
  async _runGroupIdentityOperation(groupId, operation) {
@@ -29085,6 +29978,10 @@ var _AUNClient = class _AUNClient {
29085
29978
  }
29086
29979
  this._aidStore = store;
29087
29980
  const payload = { ...params2 };
29981
+ if (payload.group_name === void 0 && payload.groupName !== void 0) {
29982
+ payload.group_name = payload.groupName;
29983
+ delete payload.groupName;
29984
+ }
29088
29985
  const isNamed = Boolean(String(payload.group_name ?? "").trim());
29089
29986
  payload._defer_group_ready_postprocess = true;
29090
29987
  if (!isNamed) {
@@ -29112,7 +30009,24 @@ var _AUNClient = class _AUNClient {
29112
30009
  delete postprocessParams2._defer_group_ready_postprocess;
29113
30010
  return await this._groupState.postprocessResult("group.create", postprocessParams2, result2);
29114
30011
  }
29115
- const keyPair = await new CryptoProvider().generateIdentity();
30012
+ const pendingKey = `create:${String(payload.group_name).trim().toLowerCase()}`;
30013
+ const keystore = store._keystore;
30014
+ let keyPair = null;
30015
+ if (keystore && typeof keystore.loadPendingGroupBind === "function") {
30016
+ keyPair = await keystore.loadPendingGroupBind(pendingKey);
30017
+ }
30018
+ if (!keyPair || !keyPair.public_key_der_b64 || !keyPair.private_key_pem) {
30019
+ const generated = await new CryptoProvider().generateIdentity();
30020
+ keyPair = {
30021
+ private_key_pem: generated.private_key_pem,
30022
+ public_key_der_b64: generated.public_key_der_b64,
30023
+ curve: generated.curve
30024
+ };
30025
+ if (keystore && typeof keystore.savePendingGroupBind === "function") {
30026
+ await keystore.savePendingGroupBind(pendingKey, keyPair);
30027
+ }
30028
+ }
30029
+ if (!keyPair) throw new ValidationError("createGroup: failed to generate or load key pair");
29116
30030
  payload.public_key = keyPair.public_key_der_b64;
29117
30031
  payload.curve = keyPair.curve;
29118
30032
  const result = await this.call("group.create", payload);
@@ -29138,6 +30052,9 @@ var _AUNClient = class _AUNClient {
29138
30052
  throw new ValidationError("createGroup requires current owner AID for group agent.md upload");
29139
30053
  }
29140
30054
  await this._uploadGroupAgentMd(store, groupAid, payload, group, uploaderAid);
30055
+ if (keystore && typeof keystore.clearPendingGroupBind === "function") {
30056
+ await keystore.clearPendingGroupBind(pendingKey);
30057
+ }
29141
30058
  const postprocessParams = { ...payload };
29142
30059
  delete postprocessParams._defer_group_ready_postprocess;
29143
30060
  return await this._groupState.postprocessResult("group.create", postprocessParams, result);
@@ -29195,10 +30112,14 @@ var _AUNClient = class _AUNClient {
29195
30112
  }
29196
30113
  this._aidStore = store;
29197
30114
  const groupId = String(params2.group_id ?? params2.group_aid ?? "").trim();
30115
+ const pendingKey = `bind:${groupId}`;
29198
30116
  const keystore = store._keystore;
29199
30117
  let keyPair = null;
29200
30118
  if (groupId && keystore && typeof keystore.loadPendingGroupBind === "function") {
29201
- keyPair = await keystore.loadPendingGroupBind(groupId);
30119
+ keyPair = await keystore.loadPendingGroupBind(pendingKey);
30120
+ if (!keyPair || !keyPair.public_key_der_b64 || !keyPair.private_key_pem) {
30121
+ keyPair = await keystore.loadPendingGroupBind(groupId);
30122
+ }
29202
30123
  }
29203
30124
  if (!keyPair || !keyPair.public_key_der_b64 || !keyPair.private_key_pem) {
29204
30125
  const generated = await new CryptoProvider().generateIdentity();
@@ -29208,7 +30129,7 @@ var _AUNClient = class _AUNClient {
29208
30129
  curve: generated.curve
29209
30130
  };
29210
30131
  if (groupId && keystore && typeof keystore.savePendingGroupBind === "function") {
29211
- await keystore.savePendingGroupBind(groupId, keyPair);
30132
+ await keystore.savePendingGroupBind(pendingKey, keyPair);
29212
30133
  }
29213
30134
  }
29214
30135
  if (!keyPair) {
@@ -29244,6 +30165,7 @@ var _AUNClient = class _AUNClient {
29244
30165
  }
29245
30166
  await this._uploadGroupAgentMd(store, groupAid, payload, group, uploaderAid);
29246
30167
  if (groupId && keystore && typeof keystore.clearPendingGroupBind === "function") {
30168
+ await keystore.clearPendingGroupBind(pendingKey);
29247
30169
  await keystore.clearPendingGroupBind(groupId);
29248
30170
  }
29249
30171
  return result;
@@ -29280,7 +30202,24 @@ var _AUNClient = class _AUNClient {
29280
30202
  if (!oldPublicKey) {
29281
30203
  throw new ValidationError(`renewGroupAid: cannot determine old public key for ${groupAid}`);
29282
30204
  }
29283
- const newKeyPair = await new CryptoProvider().generateIdentity();
30205
+ const keystore = store._keystore;
30206
+ const pendingKey = `renew:${groupId}`;
30207
+ let newKeyPair = null;
30208
+ if (keystore && typeof keystore.loadPendingGroupBind === "function") {
30209
+ newKeyPair = await keystore.loadPendingGroupBind(pendingKey);
30210
+ }
30211
+ if (!newKeyPair || !newKeyPair.public_key_der_b64 || !newKeyPair.private_key_pem) {
30212
+ const generated = await new CryptoProvider().generateIdentity();
30213
+ newKeyPair = {
30214
+ private_key_pem: generated.private_key_pem,
30215
+ public_key_der_b64: generated.public_key_der_b64,
30216
+ curve: generated.curve
30217
+ };
30218
+ if (keystore && typeof keystore.savePendingGroupBind === "function") {
30219
+ await keystore.savePendingGroupBind(pendingKey, newKeyPair);
30220
+ }
30221
+ }
30222
+ if (!newKeyPair) throw new ValidationError("renewGroupAid: failed to generate or load key pair");
29284
30223
  const newPublicKey = newKeyPair.public_key_der_b64;
29285
30224
  const newPrivateKey = newKeyPair.private_key_pem;
29286
30225
  const curve = newKeyPair.curve || "P-256";
@@ -29336,6 +30275,9 @@ var _AUNClient = class _AUNClient {
29336
30275
  throw new ValidationError("renewGroupAid requires current owner AID for group agent.md upload");
29337
30276
  }
29338
30277
  await this._uploadGroupAgentMd(store, returnedGroupAid, payload, group, uploaderAid);
30278
+ if (keystore && typeof keystore.clearPendingGroupBind === "function") {
30279
+ await keystore.clearPendingGroupBind(pendingKey);
30280
+ }
29339
30281
  return result;
29340
30282
  }
29341
30283
  async startGroupTransfer(params2 = {}, options = {}) {
@@ -29393,12 +30335,12 @@ var _AUNClient = class _AUNClient {
29393
30335
  if (!store) {
29394
30336
  throw new ValidationError("completeGroupTransfer requires aidStore");
29395
30337
  }
29396
- const keyPair = await new CryptoProvider().generateIdentity();
29397
30338
  const payload = { ...params2 };
29398
30339
  const groupId = String(params2.group_id ?? "").trim();
29399
30340
  if (!groupId) {
29400
30341
  throw new ValidationError("completeGroupTransfer requires group_id");
29401
30342
  }
30343
+ const pendingKey = `complete:${groupId}`;
29402
30344
  let groupAid = String(params2.group_aid ?? "").trim();
29403
30345
  if (!groupAid) {
29404
30346
  const info = await this.call("group.get_info", { group_id: groupId, required: ["member"] });
@@ -29407,6 +30349,23 @@ var _AUNClient = class _AUNClient {
29407
30349
  if (!groupAid) {
29408
30350
  throw new ValidationError("completeGroupTransfer: unable to determine group_aid");
29409
30351
  }
30352
+ const keystore = store._keystore;
30353
+ let keyPair = null;
30354
+ if (keystore && typeof keystore.loadPendingGroupBind === "function") {
30355
+ keyPair = await keystore.loadPendingGroupBind(pendingKey);
30356
+ }
30357
+ if (!keyPair || !keyPair.public_key_der_b64 || !keyPair.private_key_pem) {
30358
+ const generated = await new CryptoProvider().generateIdentity();
30359
+ keyPair = {
30360
+ private_key_pem: generated.private_key_pem,
30361
+ public_key_der_b64: generated.public_key_der_b64,
30362
+ curve: generated.curve
30363
+ };
30364
+ if (keystore && typeof keystore.savePendingGroupBind === "function") {
30365
+ await keystore.savePendingGroupBind(pendingKey, keyPair);
30366
+ }
30367
+ }
30368
+ if (!keyPair) throw new ValidationError("completeGroupTransfer: failed to generate or load key pair");
29410
30369
  const current = this.currentAid;
29411
30370
  const newOwner = String(current?.aid ?? "").trim();
29412
30371
  if (!current || !newOwner || !current.isPrivateKeyValid()) {
@@ -29460,6 +30419,9 @@ var _AUNClient = class _AUNClient {
29460
30419
  throw new ValidationError("completeGroupTransfer requires current owner AID for group agent.md upload");
29461
30420
  }
29462
30421
  await this._uploadGroupAgentMd(store, returnedGroupAid, payload, group, uploaderAid);
30422
+ if (keystore && typeof keystore.clearPendingGroupBind === "function") {
30423
+ await keystore.clearPendingGroupBind(pendingKey);
30424
+ }
29463
30425
  return result;
29464
30426
  }
29465
30427
  static _notifyParamsSizeOk(params2) {
@@ -29623,8 +30585,8 @@ var _AUNClient = class _AUNClient {
29623
30585
  _markPublishedSeq(ns, seq2) {
29624
30586
  this._delivery.markPublishedSeq(ns, seq2);
29625
30587
  }
29626
- async _publishAppEvent(event, payload) {
29627
- await this._delivery.publishAppEvent(event, payload);
30588
+ async _publishAppEvent(event, payload, source = "", ns = "", batch) {
30589
+ await this._delivery.publishAppEvent(event, payload, source, ns, batch);
29628
30590
  }
29629
30591
  _echoTimestamp() {
29630
30592
  const now = /* @__PURE__ */ new Date();
@@ -29662,11 +30624,11 @@ var _AUNClient = class _AUNClient {
29662
30624
  async _drainOrderedMessages(ns, beforeSeq, pullResponse = false, persist = true) {
29663
30625
  await this._delivery.drainOrderedMessages(ns, beforeSeq, pullResponse, persist);
29664
30626
  }
29665
- async _publishOrderedMessage(event, ns, seq2, payload) {
29666
- 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);
29667
30629
  }
29668
- async _publishPulledMessage(event, ns, seq2, payload, persist = true) {
29669
- 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);
29670
30632
  }
29671
30633
  _extractGroupIdFromResult(result) {
29672
30634
  const group = isJsonObject(result.group) ? result.group : null;
@@ -29697,7 +30659,7 @@ var _AUNClient = class _AUNClient {
29697
30659
  const groupId = d.group_id ?? d.group_aid ?? "";
29698
30660
  await this._delivery.handleGroupChangedEventSeq(d, groupId);
29699
30661
  } else {
29700
- await this._dispatcher.publish("group.changed", data);
30662
+ this._dispatcher.enqueue("group.changed", data);
29701
30663
  }
29702
30664
  this._clientLog.debug(`_onRawGroupChanged exit: elapsed=${Date.now() - tStart}ms group_id=${groupIdInit}`);
29703
30665
  } catch (err) {
@@ -29770,7 +30732,7 @@ var _AUNClient = class _AUNClient {
29770
30732
  const ok = await ecdsaVerifyDer(pubKey, sigBytes, signData);
29771
30733
  if (!ok) {
29772
30734
  this._clientLog.warn(`group event sig verify failed aid=%s method=%s${sigAid} ${method}`);
29773
- this._dispatcher.publish("signature.verification_failed", {
30735
+ this._dispatcher.enqueue("signature.verification_failed", {
29774
30736
  aid: sigAid,
29775
30737
  method,
29776
30738
  error: "ECDSA verification failed"
@@ -29779,7 +30741,7 @@ var _AUNClient = class _AUNClient {
29779
30741
  return ok;
29780
30742
  } catch (exc) {
29781
30743
  this._clientLog.warn(`group event sig verify exception:${String(exc)}`);
29782
- this._dispatcher.publish("signature.verification_failed", {
30744
+ this._dispatcher.enqueue("signature.verification_failed", {
29783
30745
  aid: sigAid,
29784
30746
  method,
29785
30747
  error: String(exc)
@@ -29997,7 +30959,7 @@ var _AUNClient = class _AUNClient {
29997
30959
  throw new StateError("connection attempt superseded");
29998
30960
  }
29999
30961
  } else {
30000
- await this._dispatcher.publish("state_change", statePayload);
30962
+ this._dispatcher.enqueue("state_change", statePayload);
30001
30963
  }
30002
30964
  this._assertReconnectOwner(reconnectOwner);
30003
30965
  this._lifecycle.assertConnectionAttemptOwner(connectionOwner);
@@ -30040,16 +31002,21 @@ var _AUNClient = class _AUNClient {
30040
31002
  async _resolveGatewayCandidatesForAid(aid) {
30041
31003
  const target = String(aid ?? this._aid ?? "").trim();
30042
31004
  if (!target) throw new StateError("gateway discovery requires a loaded AID");
31005
+ const discovery = this._discovery;
31006
+ const tokenStore = this._tokenStore;
31007
+ const discoveryIsCurrent = () => this._discovery === discovery && this._tokenStore === tokenStore && !this._closing;
30043
31008
  if (this._gatewayCandidates.length > 0) return [...this._gatewayCandidates];
30044
31009
  if (this._gatewayUrl) {
30045
31010
  this._gatewayCandidates = [this._gatewayUrl];
30046
31011
  return [...this._gatewayCandidates];
30047
31012
  }
30048
31013
  try {
30049
- const getMetadata = this._tokenStore.getMetadata;
30050
- const rawList = typeof getMetadata === "function" ? String(await getMetadata.call(this._tokenStore, target, "gateway_urls") ?? "").trim() : "";
31014
+ const getMetadata = tokenStore.getMetadata;
31015
+ const rawList = typeof getMetadata === "function" ? String(await getMetadata.call(tokenStore, target, "gateway_urls") ?? "").trim() : "";
31016
+ if (!discoveryIsCurrent()) throw new StateError("gateway discovery superseded by identity change");
30051
31017
  const cachedList = rawList ? JSON.parse(rawList) : [];
30052
- const raw = typeof getMetadata === "function" ? String(await getMetadata.call(this._tokenStore, target, "gateway_url") ?? "").trim() : "";
31018
+ const raw = typeof getMetadata === "function" ? String(await getMetadata.call(tokenStore, target, "gateway_url") ?? "").trim() : "";
31019
+ if (!discoveryIsCurrent()) throw new StateError("gateway discovery superseded by identity change");
30053
31020
  const candidates2 = Array.isArray(cachedList) ? cachedList.map((item) => String(item ?? "").trim()).filter(Boolean) : [];
30054
31021
  const gateway = candidates2[0] ?? (raw.startsWith('"') && raw.endsWith('"') ? String(JSON.parse(raw)).trim() : raw);
30055
31022
  if (gateway) {
@@ -30058,6 +31025,7 @@ var _AUNClient = class _AUNClient {
30058
31025
  return [...this._gatewayCandidates];
30059
31026
  }
30060
31027
  } catch {
31028
+ if (!discoveryIsCurrent()) throw new StateError("gateway discovery superseded by identity change");
30061
31029
  }
30062
31030
  const dotIdx = target.indexOf(".");
30063
31031
  const issuerDomain = dotIdx >= 0 ? target.slice(dotIdx + 1) : target;
@@ -30069,22 +31037,26 @@ var _AUNClient = class _AUNClient {
30069
31037
  let lastError = null;
30070
31038
  for (const url of candidates) {
30071
31039
  try {
30072
- const discovered = await this._discovery.discoverAll(url);
31040
+ const discovered = await discovery.discoverAll(url);
31041
+ if (!discoveryIsCurrent()) throw new StateError("gateway discovery superseded by identity change");
30073
31042
  const gatewayUrls = [...new Set(discovered.map((item) => String(item ?? "").trim()).filter(Boolean))];
30074
31043
  if (gatewayUrls.length === 0) throw new ValidationError("gateway discovery returned no candidates");
30075
31044
  const gateway = gatewayUrls[0];
30076
31045
  this._gatewayCandidates = gatewayUrls;
30077
31046
  this._gatewayUrl = gateway;
30078
31047
  try {
30079
- const setMetadata = this._tokenStore.setMetadata;
31048
+ const setMetadata = tokenStore.setMetadata;
30080
31049
  if (typeof setMetadata === "function") {
30081
- await setMetadata.call(this._tokenStore, target, "gateway_url", gateway);
30082
- await setMetadata.call(this._tokenStore, target, "gateway_urls", JSON.stringify(gatewayUrls));
31050
+ await setMetadata.call(tokenStore, target, "gateway_url", gateway);
31051
+ if (!discoveryIsCurrent()) throw new StateError("gateway discovery superseded by identity change");
31052
+ await setMetadata.call(tokenStore, target, "gateway_urls", JSON.stringify(gatewayUrls));
31053
+ if (!discoveryIsCurrent()) throw new StateError("gateway discovery superseded by identity change");
30083
31054
  }
30084
31055
  } catch {
30085
31056
  }
30086
31057
  return [...gatewayUrls];
30087
31058
  } catch (err) {
31059
+ if (!discoveryIsCurrent()) throw new StateError("gateway discovery superseded by identity change");
30088
31060
  lastError = err;
30089
31061
  }
30090
31062
  }
@@ -30304,7 +31276,7 @@ var _AUNClient = class _AUNClient {
30304
31276
  if (this._sessionParams && identity.access_token) {
30305
31277
  this._sessionParams.access_token = identity.access_token;
30306
31278
  }
30307
- await this._dispatcher.publish("token.refreshed", {
31279
+ this._dispatcher.enqueue("token.refreshed", {
30308
31280
  aid: identity.aid,
30309
31281
  expires_at: identity.access_token_expires_at
30310
31282
  });
@@ -30313,7 +31285,7 @@ var _AUNClient = class _AUNClient {
30313
31285
  if (exc instanceof AuthError) {
30314
31286
  if (authErrorRequiresRelogin(exc)) {
30315
31287
  this._clientLog.warn(`token refresh requires relogin, stopping refresh loop and triggering reconnect: ${exc.message}`);
30316
- await this._dispatcher.publish("token.refresh_exhausted", {
31288
+ this._dispatcher.enqueue("token.refresh_exhausted", {
30317
31289
  aid: this._identity?.aid ?? null,
30318
31290
  consecutive_failures: 1,
30319
31291
  last_error: String(exc),
@@ -30326,7 +31298,7 @@ var _AUNClient = class _AUNClient {
30326
31298
  this._tokenRefreshFailures++;
30327
31299
  if (this._tokenRefreshFailures >= 3) {
30328
31300
  this._clientLog.warn(`token refresh failed ${this._tokenRefreshFailures} consecutive times, stopping refresh loop and triggering reconnect`);
30329
- await this._dispatcher.publish("token.refresh_exhausted", {
31301
+ this._dispatcher.enqueue("token.refresh_exhausted", {
30330
31302
  aid: this._identity?.aid ?? null,
30331
31303
  consecutive_failures: this._tokenRefreshFailures,
30332
31304
  last_error: String(exc)
@@ -30337,7 +31309,7 @@ var _AUNClient = class _AUNClient {
30337
31309
  }
30338
31310
  this._clientLog.warn(`token refresh failed (${this._tokenRefreshFailures}/3), next retry: ${String(exc)}`);
30339
31311
  } else {
30340
- this._dispatcher.publish("connection.error", { error: formatCaughtError2(exc) });
31312
+ this._dispatcher.enqueue("connection.error", { error: formatCaughtError2(exc) });
30341
31313
  }
30342
31314
  }
30343
31315
  scheduleRefresh();
@@ -30366,7 +31338,7 @@ var _AUNClient = class _AUNClient {
30366
31338
  this._serverKicked = !retryable;
30367
31339
  this._lastDisconnectInfo = { code, reason, detail };
30368
31340
  try {
30369
- await this._dispatcher.publish("gateway.disconnect", { code, reason, detail });
31341
+ this._dispatcher.enqueue("gateway.disconnect", { code, reason, detail });
30370
31342
  } catch (exc) {
30371
31343
  this._clientLog.debug(`publish gateway.disconnect failed: ${exc?.message ?? exc}`);
30372
31344
  }
@@ -30385,7 +31357,7 @@ var _AUNClient = class _AUNClient {
30385
31357
  } catch (exc) {
30386
31358
  this._clientLog.debug(`transport cleanup skipped: ${formatCaughtError2(exc)}`);
30387
31359
  }
30388
- await this._dispatcher.publish("state_change", {
31360
+ this._dispatcher.enqueue("state_change", {
30389
31361
  state: this._publicState(this._state),
30390
31362
  error
30391
31363
  });
@@ -30421,7 +31393,7 @@ var _AUNClient = class _AUNClient {
30421
31393
  if (disconnectInfo.code !== void 0 && disconnectInfo.code !== null) {
30422
31394
  eventPayload.code = disconnectInfo.code;
30423
31395
  }
30424
- await this._dispatcher.publish("state_change", eventPayload);
31396
+ this._dispatcher.enqueue("state_change", eventPayload);
30425
31397
  if (this._reconnectAbort === reconnectAbort) {
30426
31398
  this._reconnectAbort = null;
30427
31399
  this._reconnectActive = false;
@@ -30452,12 +31424,7 @@ var _AUNClient = class _AUNClient {
30452
31424
  }
30453
31425
  async _publishReconnectEvent(owner, event, payload) {
30454
31426
  if (!this._ownsReconnect(owner)) return false;
30455
- this._reconnectEventDispatchDepth += 1;
30456
- try {
30457
- await this._dispatcher.publish(event, payload);
30458
- } finally {
30459
- this._reconnectEventDispatchDepth -= 1;
30460
- }
31427
+ this._dispatcher.enqueue(event, payload);
30461
31428
  return this._ownsReconnect(owner);
30462
31429
  }
30463
31430
  async _cancelReconnectAndWait() {
@@ -30470,7 +31437,7 @@ var _AUNClient = class _AUNClient {
30470
31437
  } catch (exc) {
30471
31438
  this._clientLog.debug(`reconnect cancellation transport cleanup skipped: ${formatCaughtError2(exc)}`);
30472
31439
  }
30473
- if (task && this._reconnectEventDispatchDepth === 0) {
31440
+ if (task) {
30474
31441
  try {
30475
31442
  await task;
30476
31443
  } catch (exc) {
@@ -30993,7 +31960,7 @@ var _AUNClient = class _AUNClient {
30993
31960
  _spk_id: spkId
30994
31961
  };
30995
31962
  attachV2EnvelopeMetadata2(event, e2eeMeta);
30996
- await this._dispatcher.publish(undecryptableEvent, event);
31963
+ this._dispatcher.enqueue(undecryptableEvent, event);
30997
31964
  } catch {
30998
31965
  }
30999
31966
  }
@@ -31034,7 +32001,7 @@ var _AUNClient = class _AUNClient {
31034
32001
  _suite: String(envelope.suite ?? "")
31035
32002
  };
31036
32003
  attachV2EnvelopeMetadata2(event, e2eeMeta);
31037
- await this._dispatcher.publish(undecryptableEvent, event);
32004
+ this._dispatcher.enqueue(undecryptableEvent, event);
31038
32005
  } catch {
31039
32006
  }
31040
32007
  }
@@ -31070,7 +32037,7 @@ var _AUNClient = class _AUNClient {
31070
32037
  _suite: String(envelope.suite ?? "")
31071
32038
  };
31072
32039
  attachV2EnvelopeMetadata2(event, e2eeMeta);
31073
- await this._dispatcher.publish(undecryptableEvent, event);
32040
+ this._dispatcher.enqueue(undecryptableEvent, event);
31074
32041
  } catch {
31075
32042
  }
31076
32043
  }
@@ -31645,7 +32612,7 @@ var RegisterFlow = class _RegisterFlow {
31645
32612
  return new Promise((resolve, reject) => {
31646
32613
  let ws;
31647
32614
  try {
31648
- ws = new WebSocket(gatewayUrl);
32615
+ ws = createTransportWebSocket(gatewayUrl);
31649
32616
  } catch {
31650
32617
  reject(new AuthError(`WebSocket \u8FDE\u63A5\u5931\u8D25: ${gatewayUrl}`));
31651
32618
  return;
@@ -31679,7 +32646,19 @@ var RegisterFlow = class _RegisterFlow {
31679
32646
  if (!receivedChallenge) {
31680
32647
  if (!isJsonObject(msg) || msg.method !== "challenge") return;
31681
32648
  receivedChallenge = true;
31682
- 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
+ }
31683
32662
  return;
31684
32663
  }
31685
32664
  if (!isJsonObject(msg) || msg.id !== requestId) return;
@@ -32600,8 +33579,7 @@ var AIDStore = class {
32600
33579
  });
32601
33580
  }
32602
33581
  await this._persistGatewayUrl(target, gatewayUrl);
32603
- const uploaded = await this.uploadAgentMd(target, buildDefaultAgentMd(target));
32604
- if (!uploaded.ok) return uploaded;
33582
+ await this.uploadAgentMd(target, buildDefaultAgentMd(target));
32605
33583
  return resultOk({ registered: true });
32606
33584
  } catch (exc) {
32607
33585
  if (exc instanceof IdentityConflictError) {