@agentunion/fastaun-browser 0.4.2 → 0.4.4

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 (61) hide show
  1. package/CHANGELOG.md +190 -164
  2. package/_packed_docs/0.4.0_/345/267/256/345/274/202/346/240/270/345/256/236/345/206/263/347/255/226/350/256/260/345/275/225.md +302 -0
  3. package/_packed_docs/AUN_SDK_0.4.0_/350/256/276/350/256/241/345/257/271/346/257/224/345/210/206/346/236/220.md +194 -0
  4. package/_packed_docs/AUN_SDK_/351/207/215/346/236/204/345/256/236/346/226/275/350/256/241/345/210/222.md +596 -596
  5. package/_packed_docs/AUN_SDK_/351/207/215/346/236/204/350/256/276/350/256/241/346/226/271/346/241/210_v3.md +1698 -1697
  6. package/_packed_docs/CHANGELOG.md +190 -164
  7. package/_packed_docs/INDEX.md +17 -17
  8. package/_packed_docs/KITE_DOCS_GUIDE.md +11 -11
  9. package/_packed_docs/agent.md/SCHEMA.md +49 -49
  10. package/_packed_docs/agent.md/examples/signed-openclaw-lobster.md +22 -22
  11. package/_packed_docs/agent.md//350/277/234/347/250/213agent.md/347/274/223/345/255/230/344/270/216etag/351/200/217/344/274/240/346/226/271/346/241/210.md +327 -327
  12. package/_packed_docs/cli/AUN-CLI/350/256/276/350/256/241/346/226/207/346/241/243.md +686 -686
  13. package/_packed_docs/design/2026-05-22-aun-rpc-trace-enhancement.md +542 -542
  14. package/_packed_docs/design/E2EE_V2/347/256/200/345/214/226/344/270/2721DH/345/212/240Per-AID_Wrap/346/226/271/346/241/210.md +124 -124
  15. package/_packed_docs/design//350/267/250/350/257/255/350/250/200/345/256/271/345/231/250E2E/346/265/213/350/257/225/346/226/271/346/241/210.md +665 -665
  16. package/_packed_docs/protocol/01-/350/272/253/344/273/275/344/270/216/345/207/255/350/257/201/345/215/217/350/256/256-auth.md +2 -2
  17. package/_packed_docs/protocol/14-/344/272/244/344/272/222/346/234/272/345/210/266-/345/223/215/345/272/224/346/250/241/345/274/217/344/270/216/350/207/252/344/270/273/346/250/241/345/274/217.md +170 -170
  18. package/_packed_docs/protocol/15-/347/246/273/347/272/277/346/216/250/351/200/201/351/200/232/347/237/245/345/215/217/350/256/256.md +419 -419
  19. package/_packed_docs/protocol/README.md +1 -1
  20. package/_packed_docs/protocol/aun-docs-guide.md +1 -1
  21. package/_packed_docs/protocol//351/231/204/345/275/225A-/346/234/257/350/257/255/350/241/250.md +15 -15
  22. package/_packed_docs/protocol//351/231/204/345/275/225B-/346/211/251/345/261/225/346/200/247/346/214/207/345/215/227.md +4 -4
  23. package/_packed_docs/protocol//351/231/204/345/275/225J-/345/256/242/346/210/267/347/253/257/346/216/245/345/205/245/347/244/272/344/276/213.md +98 -98
  24. package/_packed_docs/protocol//351/231/204/345/275/225M-JWT/350/256/244/350/257/201/345/256/236/347/216/260/346/214/207/345/215/227.md +46 -46
  25. package/_packed_docs/protocol//351/231/204/345/275/225N-/345/210/206/345/270/203/345/274/217Trace/345/215/217/350/256/256.md +257 -257
  26. package/_packed_docs/python-sdk-v2-only-changelog.md +189 -189
  27. package/_packed_docs/sdk/01-/345/277/253/351/200/237/345/274/200/345/247/213.md +7 -3
  28. package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +1 -1
  29. package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +3 -1
  30. package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +1 -1
  31. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +63 -15
  32. package/_packed_docs/sdk/09-payload-reference.md +13 -13
  33. package/_packed_docs/sdk/E2EE_V2/346/266/210/346/201/257/351/200/232/344/277/241/346/227/266/345/272/217/345/233/276.md +171 -171
  34. package/_packed_docs/sdk/README.md +5 -5
  35. package/dist/aid-store.d.ts.map +1 -1
  36. package/dist/aid-store.js +5 -6
  37. package/dist/aid-store.js.map +1 -1
  38. package/dist/aid.d.ts +2 -1
  39. package/dist/aid.d.ts.map +1 -1
  40. package/dist/aid.js +7 -6
  41. package/dist/aid.js.map +1 -1
  42. package/dist/auth.d.ts.map +1 -1
  43. package/dist/auth.js +4 -0
  44. package/dist/auth.js.map +1 -1
  45. package/dist/bundle.js +292 -188
  46. package/dist/client.d.ts +13 -17
  47. package/dist/client.d.ts.map +1 -1
  48. package/dist/client.js +275 -190
  49. package/dist/client.js.map +1 -1
  50. package/dist/config.d.ts +4 -7
  51. package/dist/config.d.ts.map +1 -1
  52. package/dist/config.js +18 -1
  53. package/dist/config.js.map +1 -1
  54. package/dist/index.d.ts +1 -1
  55. package/dist/index.d.ts.map +1 -1
  56. package/dist/index.js.map +1 -1
  57. package/dist/keystore/indexeddb.js +5 -5
  58. package/dist/keystore/indexeddb.js.map +1 -1
  59. package/dist/version.d.ts +1 -1
  60. package/dist/version.js +1 -1
  61. package/package.json +1 -1
package/dist/client.js CHANGED
@@ -4,7 +4,7 @@
4
4
  // - HTTP 使用 fetch() 而非 Node http
5
5
  // - 无文件系统(IndexedDB via keystore)
6
6
  // - 后台任务使用 setTimeout/setInterval
7
- import { createConfig, getDeviceId, normalizeInstanceId } from './config.js';
7
+ import { createConfig, getDeviceId, normalizeInstanceId, normalizeSlotId, slotIsolationKey } from './config.js';
8
8
  import { EventDispatcher } from './events.js';
9
9
  import { normalizeGroupId } from './group-id.js';
10
10
  import { GatewayDiscovery } from './discovery.js';
@@ -52,6 +52,14 @@ function getV2DeviceId(dev) {
52
52
  }
53
53
  return { present: false, value: '' };
54
54
  }
55
+ function isAIDObject(value) {
56
+ const candidate = value;
57
+ return Boolean(candidate
58
+ && typeof candidate === 'object'
59
+ && typeof candidate.aid === 'string'
60
+ && typeof candidate.aunPath === 'string'
61
+ && typeof candidate.isPrivateKeyValid === 'function');
62
+ }
55
63
  function sortObjectKeys(obj) {
56
64
  if (obj === null || obj === undefined || typeof obj !== 'object')
57
65
  return obj;
@@ -169,6 +177,20 @@ const DEFAULT_SESSION_OPTIONS = {
169
177
  http: 30.0,
170
178
  },
171
179
  };
180
+ const PUBLIC_CONNECTION_OPTION_KEYS = new Set([
181
+ 'auto_reconnect',
182
+ 'connect_timeout',
183
+ 'retry_initial_delay',
184
+ 'retry_max_delay',
185
+ 'retry_max_attempts',
186
+ 'heartbeat_interval',
187
+ 'call_timeout',
188
+ 'connection_kind',
189
+ 'short_ttl_ms',
190
+ 'delivery_mode',
191
+ 'extra_info',
192
+ 'background_sync',
193
+ ]);
172
194
  const PROTECTED_HEADERS_METHODS = new Set([
173
195
  'message.send',
174
196
  'group.send',
@@ -609,22 +631,6 @@ function normalizeDeliveryModeConfig(raw, opts = {}) {
609
631
  affinity_ttl_ms: affinityTtlMs,
610
632
  };
611
633
  }
612
- function assertClientOptions(value, label) {
613
- if (value == null)
614
- return;
615
- if (typeof value !== 'object' || Array.isArray(value) || value instanceof AID) {
616
- throw new ValidationError(`${label} must be an options object`);
617
- }
618
- }
619
- function clientOptionsConfig(options) {
620
- const raw = { ...(options ?? {}) };
621
- if (Object.prototype.hasOwnProperty.call(raw, 'aid')) {
622
- throw new ValidationError('AUNClient options must not include aid; pass an AID object as the first argument');
623
- }
624
- delete raw.debug;
625
- delete raw.protected_headers;
626
- return raw;
627
- }
628
634
  /**
629
635
  * AUN Core SDK 客户端 — 浏览器版本。
630
636
  *
@@ -672,6 +678,7 @@ export class AUNClient {
672
678
  // V2 E2EE 状态
673
679
  _v2Session;
674
680
  _v2KeyStore;
681
+ _v2SessionInitInFlight = null;
675
682
  _v2BootstrapCache = new Map();
676
683
  _v2SenderIKPending = new Map();
677
684
  _v2SenderIKFetching = new Set();
@@ -748,17 +755,16 @@ export class AUNClient {
748
755
  _logDiscovery;
749
756
  _logEvents;
750
757
  constructor(aid) {
751
- const inputAid = aid instanceof AID ? aid : null;
752
- if (typeof aid === 'string') {
753
- throw new ValidationError('AUNClient aid must be an AID object, not a string');
758
+ if (aid !== null && aid !== undefined && !isAIDObject(aid)) {
759
+ throw new ValidationError('AUNClient only accepts an AID object or no argument');
754
760
  }
755
- const options = {};
756
- const rawConfig = clientOptionsConfig(options);
761
+ const inputAid = aid ?? null;
762
+ const rawConfig = {};
757
763
  if (inputAid)
758
764
  rawConfig.aun_path = inputAid.aunPath;
759
- const _debug = false;
765
+ const _debug = inputAid ? inputAid.debug : false;
760
766
  this.configModel = createConfig(rawConfig);
761
- const initAid = inputAid ? inputAid.aid : null;
767
+ const initAid = (inputAid && inputAid.isPrivateKeyValid()) ? inputAid.aid : null;
762
768
  this.config = {
763
769
  aun_path: this.configModel.aunPath,
764
770
  root_ca_path: this.configModel.rootCaPem,
@@ -778,7 +784,7 @@ export class AUNClient {
778
784
  this._clientLog.info(`AUNClient initialized: debug=${_debug} aunPath=${this.configModel.aunPath} aid=${initAid ?? '-'}`);
779
785
  this._dispatcher = new EventDispatcher();
780
786
  this._discovery = new GatewayDiscovery();
781
- this._keystore = new IndexedDBKeyStore({ encryptionSeed: this.configModel.seedPassword ?? undefined });
787
+ this._keystore = new IndexedDBKeyStore({});
782
788
  this._slotId = inputAid?.slotId || 'default';
783
789
  this._connectDeliveryMode = normalizeDeliveryModeConfig({ mode: 'fanout' });
784
790
  this._defaultConnectDeliveryMode = { ...this._connectDeliveryMode };
@@ -803,19 +809,16 @@ export class AUNClient {
803
809
  });
804
810
  });
805
811
  if (inputAid) {
806
- if (!inputAid.isPrivateKeyValid())
807
- throw new StateError('AUNClient requires an AID with a valid private key');
808
- this._currentAid = inputAid;
809
- this._identity = {
810
- aid: inputAid.aid,
811
- private_key_pem: inputAid._privateKeyPem ?? '',
812
- public_key_der_b64: inputAid.publicKey,
813
- cert: inputAid.certPem,
814
- };
815
- this._state = 'disconnected';
816
- }
817
- if (options?.protected_headers !== undefined) {
818
- this.setProtectedHeaders(options.protected_headers);
812
+ if (inputAid.isPrivateKeyValid()) {
813
+ this._currentAid = inputAid;
814
+ this._identity = {
815
+ aid: inputAid.aid,
816
+ private_key_pem: inputAid.privateKeyPem,
817
+ public_key_der_b64: inputAid.publicKey,
818
+ cert: inputAid.certPem,
819
+ };
820
+ this._state = 'disconnected';
821
+ }
819
822
  }
820
823
  // 注入 logger 到各子模块(构造时未传 logger,构造后通过 setLogger 注入)
821
824
  this._auth.setLogger(this._logAuth);
@@ -1062,7 +1065,7 @@ export class AUNClient {
1062
1065
  */
1063
1066
  async publishAgentMd(content) {
1064
1067
  const target = this._agentMdOwnerAid();
1065
- if (!target) {
1068
+ if (!target || !this._currentAid) {
1066
1069
  throw new ValidationError('publishAgentMd requires local AID');
1067
1070
  }
1068
1071
  if (content !== undefined && content !== null) {
@@ -1584,6 +1587,63 @@ export class AUNClient {
1584
1587
  get lastError() { return this._lastError; }
1585
1588
  /** 最近一次错误码(对齐 Python last_error_code) */
1586
1589
  get lastErrorCode() { return this._lastErrorCode; }
1590
+ _applyAidRuntimeContext(aid) {
1591
+ const nextConfig = createConfig({
1592
+ aunPath: aid.aunPath,
1593
+ rootCaPem: aid.rootCaPath,
1594
+ verifySsl: aid.verifySsl,
1595
+ });
1596
+ Object.assign(this.configModel, nextConfig);
1597
+ this.config.aun_path = nextConfig.aunPath;
1598
+ this.config.root_ca_path = nextConfig.rootCaPem;
1599
+ this.config.seed_password = nextConfig.seedPassword;
1600
+ this._agentMdPath = this._agentMdDefaultRoot();
1601
+ this._agentMdCache.clear();
1602
+ this._agentMdFetchInflight.clear();
1603
+ this._peerCache.clear();
1604
+ this._certCache.clear();
1605
+ this._gatewayUrl = null;
1606
+ this._deviceId = aid.deviceId || getDeviceId();
1607
+ this._slotId = aid.slotId || 'default';
1608
+ this._logger = new AUNLogger({ debug: aid.debug, aunPath: nextConfig.aunPath });
1609
+ this._logger.bindDeviceId(this._deviceId);
1610
+ this._clientLog = this._logger.for('aun_core.client');
1611
+ this._logAuth = this._logger.for('aun_core.auth');
1612
+ this._logTransport = this._logger.for('aun_core.transport');
1613
+ this._logKeystore = this._logger.for('aun_core.keystore');
1614
+ this._logDiscovery = this._logger.for('aun_core.discovery');
1615
+ this._logEvents = this._logger.for('aun_core.events');
1616
+ this._discovery = new GatewayDiscovery();
1617
+ this._keystore = new IndexedDBKeyStore({});
1618
+ this._auth = new AuthFlow({
1619
+ keystore: this._keystore,
1620
+ crypto: new CryptoProvider(),
1621
+ aid: aid.aid,
1622
+ deviceId: this._deviceId,
1623
+ slotId: this._slotId,
1624
+ rootCaPem: nextConfig.rootCaPem,
1625
+ verifySsl: nextConfig.verifySsl,
1626
+ });
1627
+ this._transport = new RPCTransport({
1628
+ eventDispatcher: this._dispatcher,
1629
+ timeout: DEFAULT_SESSION_OPTIONS.timeouts.call,
1630
+ onDisconnect: (error, closeCode) => this._handleTransportDisconnect(error, closeCode),
1631
+ });
1632
+ this._transport.setMetaObserver((meta) => {
1633
+ void this._observeRpcMeta(meta).catch((exc) => {
1634
+ this._clientLog.debug(`agent.md meta observer skipped: ${String(exc)}`);
1635
+ });
1636
+ });
1637
+ this._auth.setLogger(this._logAuth);
1638
+ this._transport.setLogger(this._logTransport);
1639
+ this._dispatcher.setLogger(this._logEvents);
1640
+ if (typeof this._discovery.setLogger === 'function') {
1641
+ this._discovery.setLogger(this._logDiscovery);
1642
+ }
1643
+ if (typeof this._keystore.setLogger === 'function') {
1644
+ this._keystore.setLogger(this._logKeystore);
1645
+ }
1646
+ }
1587
1647
  loadIdentity(aid) {
1588
1648
  if (!aid?.isPrivateKeyValid())
1589
1649
  throw new StateError('loadIdentity requires an AID with a valid private key');
@@ -1591,15 +1651,15 @@ export class AUNClient {
1591
1651
  if (publicState !== ConnectionState.NO_IDENTITY && publicState !== ConnectionState.CLOSED) {
1592
1652
  throw new StateError(`loadIdentity not allowed in state ${publicState}`);
1593
1653
  }
1654
+ this._applyAidRuntimeContext(aid);
1594
1655
  this._currentAid = aid;
1595
1656
  this._aid = aid.aid;
1596
1657
  this._identity = {
1597
1658
  aid: aid.aid,
1598
- private_key_pem: aid._privateKeyPem ?? '',
1659
+ private_key_pem: aid.privateKeyPem,
1599
1660
  public_key_der_b64: aid.publicKey,
1600
1661
  cert: aid.certPem,
1601
1662
  };
1602
- this._auth._aid = aid.aid;
1603
1663
  this._state = 'disconnected';
1604
1664
  this._closing = false;
1605
1665
  }
@@ -1651,9 +1711,6 @@ export class AUNClient {
1651
1711
  get gatewayUrl() {
1652
1712
  return this._gatewayUrl;
1653
1713
  }
1654
- set gatewayUrl(url) {
1655
- this._gatewayUrl = url;
1656
- }
1657
1714
  get discovery() {
1658
1715
  return this._discovery;
1659
1716
  }
@@ -1695,6 +1752,17 @@ export class AUNClient {
1695
1752
  /** 连接到 Gateway;身份来自构造函数或 loadIdentity(aid),认证由 SDK 内部自动完成。 */
1696
1753
  async connect(opts) {
1697
1754
  const tStart = Date.now();
1755
+ if (opts !== undefined && opts !== null && typeof opts === 'object') {
1756
+ const raw = opts;
1757
+ const invalid = Object.keys(raw).filter((key) => !PUBLIC_CONNECTION_OPTION_KEYS.has(key)).sort();
1758
+ if (invalid.length > 0) {
1759
+ throw new ValidationError(`connect options contain unsupported field(s): ${invalid.join(', ')}`);
1760
+ }
1761
+ }
1762
+ const target = this._currentAid?.aid ?? this._aid ?? '';
1763
+ if (!target || !this._currentAid?.isPrivateKeyValid()) {
1764
+ throw new StateError('connect requires a loaded AID with a valid private key');
1765
+ }
1698
1766
  const options = {};
1699
1767
  if (opts?.auto_reconnect !== undefined)
1700
1768
  options.auto_reconnect = opts.auto_reconnect;
@@ -1713,11 +1781,17 @@ export class AUNClient {
1713
1781
  max_attempts: opts.retry_max_attempts ?? 0,
1714
1782
  };
1715
1783
  }
1784
+ if (opts?.connection_kind !== undefined)
1785
+ options.connection_kind = opts.connection_kind;
1786
+ if (opts?.short_ttl_ms !== undefined)
1787
+ options.short_ttl_ms = opts.short_ttl_ms;
1788
+ if (opts?.delivery_mode !== undefined)
1789
+ options.delivery_mode = opts.delivery_mode;
1790
+ if (opts?.extra_info !== undefined)
1791
+ options.extra_info = opts.extra_info;
1792
+ if (opts?.background_sync !== undefined)
1793
+ options.background_sync = opts.background_sync;
1716
1794
  this._clientLog.debug(`connect enter: state=${this._state} aid=${this._aid ?? '-'}`);
1717
- const target = this._currentAid?.aid ?? this._aid ?? '';
1718
- if (!target || !this._currentAid?.isPrivateKeyValid()) {
1719
- throw new StateError('connect requires a loaded AID with a valid private key');
1720
- }
1721
1795
  const publicState = this.state;
1722
1796
  const allowed = new Set([
1723
1797
  ConnectionState.STANDBY,
@@ -1858,6 +1932,14 @@ export class AUNClient {
1858
1932
  }
1859
1933
  this._validateOutboundCall(method, p);
1860
1934
  this._injectMessageCursorContext(method, p);
1935
+ if (method.startsWith('group.')
1936
+ && !('_group_cursor_params' in p)
1937
+ && !Boolean(p._pull_gate_locked)) {
1938
+ const explicitCursorParams = this._groupCursorParams(p);
1939
+ if (Object.keys(explicitCursorParams).length > 0) {
1940
+ p._group_cursor_params = explicitCursorParams;
1941
+ }
1942
+ }
1861
1943
  // group.* 方法的 group_id 归一化为 canonical 格式(兼容老/污染数据)
1862
1944
  if (method.startsWith('group.') && p.group_id !== undefined && p.group_id !== null) {
1863
1945
  const rawGroupId = String(p.group_id);
@@ -1879,9 +1961,7 @@ export class AUNClient {
1879
1961
  const encrypt = p.encrypt !== undefined ? p.encrypt : true;
1880
1962
  delete p.encrypt;
1881
1963
  if (encrypt) {
1882
- if (!this._v2Session) {
1883
- throw new StateError('V2 session not initialized; encrypted message.send requires V2 (V1 E2EE removed)');
1884
- }
1964
+ await this._ensureV2SessionReady('message.send', 'V2 session not initialized; encrypted message.send requires V2 (V1 E2EE removed)');
1885
1965
  this._clientLog.debug('call route: message.send → V2 encrypted send');
1886
1966
  return await this._sendV2(String(p.to ?? ''), p.payload ?? {}, {
1887
1967
  messageId: String(p.message_id ?? '') || undefined,
@@ -1898,9 +1978,7 @@ export class AUNClient {
1898
1978
  const encrypt = p.encrypt !== undefined ? p.encrypt : true;
1899
1979
  delete p.encrypt;
1900
1980
  if (encrypt) {
1901
- if (!this._v2Session) {
1902
- throw new StateError('V2 session not initialized; encrypted group.send requires V2 (V1 E2EE removed)');
1903
- }
1981
+ await this._ensureV2SessionReady('group.send', 'V2 session not initialized; encrypted group.send requires V2 (V1 E2EE removed)');
1904
1982
  this._clientLog.debug('call route: group.send → V2 encrypted send');
1905
1983
  return await this._sendGroupV2(String(p.group_id ?? ''), p.payload ?? {}, {
1906
1984
  messageId: String(p.message_id ?? '') || undefined,
@@ -1915,9 +1993,7 @@ export class AUNClient {
1915
1993
  const encrypt = p.encrypt !== undefined ? p.encrypt : true;
1916
1994
  delete p.encrypt;
1917
1995
  if (encrypt) {
1918
- if (!this._v2Session) {
1919
- throw new StateError('V2 session not initialized; encrypted group.thought.put requires V2 (V1 E2EE removed)');
1920
- }
1996
+ await this._ensureV2SessionReady('group.thought.put', 'V2 session not initialized; encrypted group.thought.put requires V2 (V1 E2EE removed)');
1921
1997
  this._clientLog.debug('call route: group.thought.put → V2 encrypted put');
1922
1998
  return this._putGroupThoughtEncryptedV2(p);
1923
1999
  }
@@ -1926,9 +2002,7 @@ export class AUNClient {
1926
2002
  const encrypt = p.encrypt !== undefined ? p.encrypt : true;
1927
2003
  delete p.encrypt;
1928
2004
  if (encrypt) {
1929
- if (!this._v2Session) {
1930
- throw new StateError('V2 session not initialized; encrypted message.thought.put requires V2 (V1 E2EE removed)');
1931
- }
2005
+ await this._ensureV2SessionReady('message.thought.put', 'V2 session not initialized; encrypted message.thought.put requires V2 (V1 E2EE removed)');
1932
2006
  this._clientLog.debug('call route: message.thought.put → V2 encrypted put');
1933
2007
  return this._putMessageThoughtEncryptedV2(p);
1934
2008
  }
@@ -1947,26 +2021,45 @@ export class AUNClient {
1947
2021
  * 拆分出来以便 pull gate 包裹整个操作。
1948
2022
  */
1949
2023
  async _callImplInner(method, p) {
1950
- // message.pull:V2 就绪时走 V2 pull
1951
- if (method === 'message.pull' && this._v2Session) {
2024
+ // message.pull:V2-only,按需初始化后走 V2 pull
2025
+ if (method === 'message.pull') {
2026
+ await this._ensureV2SessionReady('message.pull');
1952
2027
  this._clientLog.debug('call route: message.pull → V2 pull');
1953
2028
  const messages = await this._pullV2(Number(p.after_seq ?? 0) || 0, Number(p.limit ?? 50) || 50, { force: p.force === true });
1954
2029
  return { messages };
1955
2030
  }
1956
- // message.ack:V2 就绪时走 V2 ack
1957
- if (method === 'message.ack' && this._v2Session) {
2031
+ // message.ack:V2-only,按需初始化后走 V2 ack
2032
+ if (method === 'message.ack') {
2033
+ await this._ensureV2SessionReady('message.ack');
1958
2034
  this._clientLog.debug('call route: message.ack → V2 ack');
1959
2035
  return await this._ackV2(Number(p.seq ?? p.up_to_seq ?? 0) || undefined);
1960
2036
  }
1961
- // group.pull:V2 就绪时走 V2 pull
1962
- if (method === 'group.pull' && this._v2Session && p.group_id) {
2037
+ // group.pull:V2-only,按需初始化后走 V2 pull
2038
+ if (method === 'group.pull' && p.group_id) {
2039
+ await this._ensureV2SessionReady('group.pull');
1963
2040
  this._clientLog.debug('call route: group.pull → V2 pull');
1964
- const messages = await this._pullGroupV2(String(p.group_id), Number(p.after_seq ?? p.after_message_seq ?? 0) || 0, Number(p.limit ?? 50) || 50);
2041
+ const hasExplicitAfterSeq = 'after_seq' in p || 'after_message_seq' in p;
2042
+ const cursorParams = this._explicitGroupCursorParams(p);
2043
+ const ownsCursor = Object.keys(cursorParams).length === 0 || this._groupCursorTargetsCurrentInstance(cursorParams);
2044
+ const pullOpts = {};
2045
+ if (hasExplicitAfterSeq)
2046
+ pullOpts.explicitAfterSeq = true;
2047
+ if (Object.keys(cursorParams).length > 0)
2048
+ pullOpts.cursorParams = cursorParams;
2049
+ if (!ownsCursor)
2050
+ pullOpts.ownsCursor = false;
2051
+ const messages = await this._pullGroupV2(String(p.group_id), Number(p.after_seq ?? p.after_message_seq ?? 0) || 0, Number(p.limit ?? 50) || 50, Object.keys(pullOpts).length > 0 ? pullOpts : undefined);
1965
2052
  return { messages };
1966
2053
  }
1967
- // group.ack_messages:V2 就绪时走 V2 ack
1968
- if (method === 'group.ack_messages' && this._v2Session && p.group_id) {
2054
+ // group.ack_messages:V2-only,按需初始化后走 V2 ack
2055
+ if (method === 'group.ack_messages' && p.group_id) {
2056
+ await this._ensureV2SessionReady('group.ack_messages');
1969
2057
  this._clientLog.debug('call route: group.ack_messages → V2 ack');
2058
+ const cursorParams = this._explicitGroupCursorParams(p);
2059
+ const ownsCursor = Object.keys(cursorParams).length === 0 || this._groupCursorTargetsCurrentInstance(cursorParams);
2060
+ if (!ownsCursor) {
2061
+ return await this._rawGroupAckMessages(p);
2062
+ }
1970
2063
  return await this._ackGroupV2(String(p.group_id), Number(p.seq ?? p.msg_seq ?? p.up_to_seq ?? 0) || undefined);
1971
2064
  }
1972
2065
  // 关键操作自动附加客户端签名
@@ -2073,6 +2166,7 @@ export class AUNClient {
2073
2166
  delete p._pull_gate_locked;
2074
2167
  delete p._skip_auto_ack;
2075
2168
  delete p.skip_auto_ack;
2169
+ delete p._group_cursor_params;
2076
2170
  if (method.startsWith('group.') && p.group_id !== undefined && p.group_id !== null) {
2077
2171
  p.group_id = normalizeGroupId(String(p.group_id)) || String(p.group_id);
2078
2172
  }
@@ -2436,6 +2530,9 @@ export class AUNClient {
2436
2530
  // 状态保护:非 connected 或正在关闭时跳过(与 Python 对齐)
2437
2531
  if (this._state !== 'connected' || this._closing)
2438
2532
  return;
2533
+ groupId = normalizeGroupId(groupId) || String(groupId ?? '').trim();
2534
+ if (!groupId)
2535
+ return;
2439
2536
  const ns = `group:${groupId}`;
2440
2537
  const afterSeq = this._seqTracker.getContiguousSeq(ns);
2441
2538
  // per-namespace 去重:同一 group namespace 只允许 1 个 in-flight pull
@@ -2444,45 +2541,11 @@ export class AUNClient {
2444
2541
  return;
2445
2542
  this._gapFillDone.add(dedupKey);
2446
2543
  this._gapFillActive = true;
2544
+ let filled = 0;
2447
2545
  try {
2448
- const result = await this.call('group.pull', {
2449
- group_id: groupId,
2450
- after_message_seq: afterSeq,
2451
- device_id: this._deviceId,
2452
- limit: 50,
2453
- });
2454
- if (isJsonObject(result)) {
2455
- const messages = result.messages;
2456
- if (Array.isArray(messages)) {
2457
- // ⚠️ 不再重复调用 onPullResult:call('group.pull') 拦截器已在内部调用过一次
2458
- const pushed = this._pushedSeqs.get(ns);
2459
- for (const msg of messages) {
2460
- if (isJsonObject(msg)) {
2461
- const s = msg.seq;
2462
- if (pushed && s !== undefined && s !== null && pushed.has(s))
2463
- continue;
2464
- if (s !== undefined && s !== null) {
2465
- await this._publishPulledMessage('group.message_created', ns, s, msg);
2466
- }
2467
- else {
2468
- await this._publishAppEvent('group.message_created', msg);
2469
- }
2470
- }
2471
- }
2472
- this._prunePushedSeqs(ns);
2473
- // publish 完成后 auto-ack
2474
- const contig = this._seqTracker.getContiguousSeq(ns);
2475
- if (contig > 0) {
2476
- const gid = groupId;
2477
- this._transport.call('group.ack_messages', {
2478
- group_id: gid,
2479
- msg_seq: contig,
2480
- device_id: this._deviceId,
2481
- slot_id: this._slotId,
2482
- }).catch((e) => { this._clientLog.warn(`group gap-fill auto-ack failed: group=${gid}`, e); });
2483
- }
2484
- }
2485
- }
2546
+ const messages = await this._pullGroupV2(groupId, afterSeq, 50);
2547
+ filled = messages.length;
2548
+ this._prunePushedSeqs(ns);
2486
2549
  }
2487
2550
  catch (exc) {
2488
2551
  this._clientLog.warn(`group message gap-fill failed:${String(exc)}`);
@@ -2491,6 +2554,9 @@ export class AUNClient {
2491
2554
  // S1: 成功 / 失败路径都必须清理飞行标记
2492
2555
  this._gapFillDone.delete(dedupKey);
2493
2556
  this._gapFillActive = false;
2557
+ if (filled > 0 && this._seqTracker.getContiguousSeq(ns) > afterSeq) {
2558
+ this._safeAsync(this._fillGroupGap(groupId));
2559
+ }
2494
2560
  }
2495
2561
  }
2496
2562
  /** 后台补齐群事件空洞 */
@@ -2607,42 +2673,11 @@ export class AUNClient {
2607
2673
  return;
2608
2674
  this._gapFillDone.add(dedupKey);
2609
2675
  this._gapFillActive = true;
2676
+ let filled = 0;
2610
2677
  try {
2611
- const result = await this.call('message.pull', {
2612
- after_seq: afterSeq,
2613
- limit: 50,
2614
- });
2615
- if (isJsonObject(result)) {
2616
- const messages = result.messages;
2617
- if (Array.isArray(messages)) {
2618
- // ⚠️ 不再重复调用 onPullResult:call('message.pull') 拦截器已在内部调用过一次
2619
- // 与 _fillGroupGap 路径对齐,避免双重 tracker 推进。
2620
- const pushed = this._pushedSeqs.get(ns);
2621
- for (const msg of messages) {
2622
- if (isJsonObject(msg)) {
2623
- const s = msg.seq;
2624
- if (pushed && s !== undefined && s !== null && pushed.has(s))
2625
- continue;
2626
- if (s !== undefined && s !== null) {
2627
- await this._publishPulledMessage('message.received', ns, s, msg);
2628
- }
2629
- else {
2630
- await this._publishAppEvent('message.received', msg);
2631
- }
2632
- }
2633
- }
2634
- this._prunePushedSeqs(ns);
2635
- // publish 完成后 auto-ack
2636
- const contig = this._seqTracker.getContiguousSeq(ns);
2637
- if (contig > 0) {
2638
- this._transport.call('message.ack', {
2639
- seq: contig,
2640
- device_id: this._deviceId,
2641
- slot_id: this._slotId,
2642
- }).catch((e) => { this._clientLog.warn(`P2P gap-fill auto-ack failed:${String(e)}`); });
2643
- }
2644
- }
2645
- }
2678
+ const messages = await this._pullV2(afterSeq, 50);
2679
+ filled = messages.length;
2680
+ this._prunePushedSeqs(ns);
2646
2681
  }
2647
2682
  catch (exc) {
2648
2683
  this._clientLog.warn(`P2P message gap-fill failed:${String(exc)}`);
@@ -2651,6 +2686,9 @@ export class AUNClient {
2651
2686
  // S1: 成功 / 失败路径都必须清理飞行标记
2652
2687
  this._gapFillDone.delete(dedupKey);
2653
2688
  this._gapFillActive = false;
2689
+ if (filled > 0 && this._seqTracker.getContiguousSeq(ns) > afterSeq) {
2690
+ this._safeAsync(this._fillP2pGap());
2691
+ }
2654
2692
  }
2655
2693
  }
2656
2694
  /** 只按硬上限裁剪 published guard,不能按 contiguousSeq 清理。 */
@@ -2790,7 +2828,7 @@ export class AUNClient {
2790
2828
  }
2791
2829
  if ('slot_id' in message) {
2792
2830
  const targetSlotId = String(message.slot_id ?? '').trim();
2793
- if (targetSlotId !== this._slotId) {
2831
+ if (slotIsolationKey(targetSlotId) !== slotIsolationKey(this._slotId)) {
2794
2832
  return false;
2795
2833
  }
2796
2834
  }
@@ -3585,11 +3623,11 @@ export class AUNClient {
3585
3623
  * 使用 SubtleCrypto 异步签名。
3586
3624
  */
3587
3625
  async _signClientOperation(method, params) {
3588
- const identity = this._identity;
3589
- if (!identity || !identity.private_key_pem)
3626
+ const currentAid = this._currentAid;
3627
+ if (!currentAid?.privateKeyPem)
3590
3628
  return;
3591
3629
  try {
3592
- const aid = (identity.aid ?? '');
3630
+ const aid = currentAid.aid;
3593
3631
  const ts = String(Math.floor(Date.now() / 1000));
3594
3632
  // 计算 params hash:覆盖所有非 _ 前缀且非 client_signature 的业务字段
3595
3633
  const paramsForHash = {};
@@ -3606,14 +3644,14 @@ export class AUNClient {
3606
3644
  .join('');
3607
3645
  const signData = new TextEncoder().encode(`${method}|${aid}|${ts}|${paramsHash}`);
3608
3646
  // 导入私钥并签名
3609
- const pkcs8 = pemToArrayBuffer(identity.private_key_pem);
3647
+ const pkcs8 = pemToArrayBuffer(currentAid.privateKeyPem);
3610
3648
  const cryptoKey = await crypto.subtle.importKey('pkcs8', pkcs8, { name: 'ECDSA', namedCurve: 'P-256' }, false, ['sign']);
3611
3649
  const sigP1363 = await crypto.subtle.sign({ name: 'ECDSA', hash: 'SHA-256' }, cryptoKey, signData);
3612
3650
  // P1363 → DER 格式(与 Python 兼容)
3613
3651
  const sigDer = p1363ToDer(new Uint8Array(sigP1363));
3614
3652
  // 证书指纹
3615
3653
  let certFingerprint = '';
3616
- const certPem = (identity.cert ?? '');
3654
+ const certPem = currentAid.certPem;
3617
3655
  if (certPem) {
3618
3656
  const certDer = pemToArrayBuffer(certPem);
3619
3657
  const fpBuf = await crypto.subtle.digest('SHA-256', certDer);
@@ -3709,16 +3747,27 @@ export class AUNClient {
3709
3747
  await this._restoreSeqTrackerState();
3710
3748
  }
3711
3749
  this._startBackgroundTasks();
3712
- // V2 E2EE: 初始化 session 并注册设备 SPK(与 Python `_init_v2_session` 对齐)
3713
- try {
3714
- await this._initV2Session();
3750
+ const connectionKind = String(params.connection_kind ?? 'long');
3751
+ const isShortConnection = connectionKind === 'short';
3752
+ if (!isShortConnection) {
3753
+ // V2 E2EE: 长连接上线时初始化 session 并注册设备 SPK(与 Python `_init_v2_session` 对齐)
3754
+ try {
3755
+ await this._initV2Session();
3756
+ }
3757
+ catch (exc) {
3758
+ this._clientLog.warn(`V2 session init failed (non-fatal): ${String(exc)}`);
3759
+ }
3715
3760
  }
3716
- catch (exc) {
3717
- this._clientLog.warn(`V2 session init failed (non-fatal): ${String(exc)}`);
3761
+ else {
3762
+ this._clientLog.debug('V2 session init deferred for short connection');
3718
3763
  }
3719
3764
  // connect/reconnect 成功后自动触发一次 P2P message.pull,补齐离线期间积压
3720
- // 群消息按惰性触发,不在此处主动 pull
3721
- this._safeAsync(this._fillP2pGap());
3765
+ const hasExplicitBackgroundSync = Object.prototype.hasOwnProperty.call(params, 'background_sync');
3766
+ const backgroundSyncEnabled = this._sessionOptions?.background_sync !== false
3767
+ && (!isShortConnection || hasExplicitBackgroundSync);
3768
+ if (backgroundSyncEnabled) {
3769
+ this._safeAsync(this._fillP2pGap());
3770
+ }
3722
3771
  this._clientLog.debug(`_connectOnce exit: elapsed=${Date.now() - tStart}ms aid=${this._aid ?? '-'}`);
3723
3772
  }
3724
3773
  catch (err) {
@@ -3839,7 +3888,7 @@ export class AUNClient {
3839
3888
  delete request.access_token;
3840
3889
  request.gateway = gateway;
3841
3890
  request.device_id = this._deviceId;
3842
- request.slot_id = normalizeInstanceId(request.slot_id ?? this._slotId, 'slot_id', { allowEmpty: true });
3891
+ request.slot_id = normalizeSlotId(request.slot_id ?? this._slotId, this._slotId || 'default');
3843
3892
  let deliveryModeRaw = request.delivery_mode;
3844
3893
  if (deliveryModeRaw == null) {
3845
3894
  deliveryModeRaw = { ...this._defaultConnectDeliveryMode };
@@ -3914,6 +3963,8 @@ export class AUNClient {
3914
3963
  if (isJsonObject(params.timeouts)) {
3915
3964
  Object.assign(options.timeouts, params.timeouts);
3916
3965
  }
3966
+ if ('background_sync' in params)
3967
+ options.background_sync = Boolean(params.background_sync);
3917
3968
  return options;
3918
3969
  }
3919
3970
  // ── 内部:后台任务 ────────────────────────────────
@@ -4660,6 +4711,20 @@ export class AUNClient {
4660
4711
  this._clientLog.warn(`${label} push repaired contiguous_seq: ns=${ns} payload=${hasPayload} push_seq=${pushSeq} contiguous=${contig}->${repaired}`);
4661
4712
  return repaired;
4662
4713
  }
4714
+ async _ensureV2SessionReady(method, errorMessage) {
4715
+ if (!this._v2Session) {
4716
+ if (!this._v2SessionInitInFlight) {
4717
+ this._v2SessionInitInFlight = this._initV2Session()
4718
+ .finally(() => {
4719
+ this._v2SessionInitInFlight = null;
4720
+ });
4721
+ }
4722
+ await this._v2SessionInitInFlight;
4723
+ }
4724
+ if (!this._v2Session) {
4725
+ throw new StateError(errorMessage ?? `V2 session not initialized; encrypted ${method} requires E2EE V2`);
4726
+ }
4727
+ }
4663
4728
  // ── V2 E2EE API(async,与 Python `client.py` `_init_v2_session` / `send_v2` / `pull_v2` / `ack_v2` 对齐) ──
4664
4729
  /**
4665
4730
  * 初始化 V2 session:从 AID PEM 私钥提取 raw scalar + DER 公钥,
@@ -4670,35 +4735,16 @@ export class AUNClient {
4670
4735
  async _initV2Session() {
4671
4736
  if (!this._aid)
4672
4737
  return;
4673
- let identity = this._identity;
4674
- if (!identity?.private_key_pem) {
4675
- // fallback:缓存的 identity 可能被 instance_state 污染,重新从 keystore 加载
4676
- try {
4677
- const reloaded = await this._keystore.loadIdentity(this._aid);
4678
- if (reloaded?.private_key_pem) {
4679
- this._identity = reloaded;
4680
- identity = reloaded;
4681
- this._clientLog.warn('V2 session init: identity cache was stale, reloaded from keystore');
4682
- // 自愈:重新持久化,清理 instance_state 中的脏数据
4683
- try {
4684
- const persistIdentity = this._auth._persistIdentity;
4685
- if (typeof persistIdentity === 'function') {
4686
- await persistIdentity.call(this._auth, reloaded);
4687
- }
4688
- }
4689
- catch { /* best-effort */ }
4690
- }
4691
- }
4692
- catch { /* ignore */ }
4693
- }
4694
- if (!identity?.private_key_pem) {
4738
+ // 私钥由 AIDStore 管理,直接从 _currentAid 读取明文私钥
4739
+ const currentAid = this._currentAid;
4740
+ if (!currentAid?.privateKeyPem) {
4695
4741
  this._clientLog.warn('V2 session init skipped: no AID private key');
4696
4742
  return;
4697
4743
  }
4698
4744
  if (this._v2Session)
4699
4745
  return;
4700
4746
  // 1. PEM → DER PKCS8(去掉 header/footer 后 base64 解码)
4701
- const pem = String(identity.private_key_pem).trim();
4747
+ const pem = currentAid.privateKeyPem.trim();
4702
4748
  const pemBody = pem
4703
4749
  .replace(/-----BEGIN [^-]+-----/g, '')
4704
4750
  .replace(/-----END [^-]+-----/g, '')
@@ -5096,6 +5142,7 @@ export class AUNClient {
5096
5142
  decrypted.push(plaintext);
5097
5143
  }
5098
5144
  }
5145
+ const hasServerAckSeq = Object.prototype.hasOwnProperty.call(result, 'server_ack_seq');
5099
5146
  const serverAckSeq = Number(result.server_ack_seq ?? 0);
5100
5147
  if (ns && Number.isFinite(serverAckSeq) && serverAckSeq > 0) {
5101
5148
  const contig = this._seqTracker.getContiguousSeq(ns);
@@ -5111,7 +5158,10 @@ export class AUNClient {
5111
5158
  await this._drainOrderedMessages(ns);
5112
5159
  this._saveSeqTrackerState();
5113
5160
  }
5114
- if (messages.length > 0 && contigAdvanced && ackSeq > 0) {
5161
+ const ackNeeded = messages.length > 0
5162
+ && ackSeq > 0
5163
+ && (contigAdvanced || (hasServerAckSeq && ackSeq > serverAckSeq));
5164
+ if (ackNeeded) {
5115
5165
  this._safeAsync(this._ackV2(ackSeq).then(() => undefined));
5116
5166
  }
5117
5167
  }
@@ -5143,7 +5193,7 @@ export class AUNClient {
5143
5193
  seq = maxSeen;
5144
5194
  }
5145
5195
  }
5146
- const raw = await this.call('message.v2.ack', { up_to_seq: seq });
5196
+ const raw = await this._callRawV2Rpc('message.v2.ack', { up_to_seq: seq });
5147
5197
  const result = isJsonObject(raw)
5148
5198
  ? { ...raw }
5149
5199
  : { result: raw };
@@ -5435,7 +5485,7 @@ export class AUNClient {
5435
5485
  * @param afterSeq 从此 seq 之后开始拉取(0/省略 = 从当前 contiguous 开始)
5436
5486
  * @param limit 最多拉取条数
5437
5487
  */
5438
- async _pullGroupV2(groupId, afterSeq = 0, limit = 50) {
5488
+ async _pullGroupV2(groupId, afterSeq = 0, limit = 50, opts) {
5439
5489
  if (!this._v2Session) {
5440
5490
  throw new StateError('V2 session not initialized (not connected?)');
5441
5491
  }
@@ -5444,15 +5494,18 @@ export class AUNClient {
5444
5494
  throw new ValidationError('group.pull requires group_id');
5445
5495
  const ns = `group:${gid}`;
5446
5496
  const decrypted = [];
5447
- let nextAfterSeq = afterSeq || this._seqTracker.getContiguousSeq(ns);
5497
+ const cursorParams = opts?.cursorParams ?? {};
5498
+ const ownsCursor = opts?.ownsCursor !== false;
5499
+ let nextAfterSeq = opts?.explicitAfterSeq ? afterSeq : (afterSeq || this._seqTracker.getContiguousSeq(ns));
5448
5500
  let pageCount = 0;
5449
5501
  const maxPages = 100;
5450
5502
  while (pageCount < maxPages) {
5451
5503
  pageCount += 1;
5452
- const result = await this.call('group.v2.pull', {
5504
+ const result = await this._callRawV2Rpc('group.v2.pull', {
5453
5505
  group_id: gid,
5454
5506
  after_seq: nextAfterSeq,
5455
5507
  limit,
5508
+ ...cursorParams,
5456
5509
  });
5457
5510
  const messages = (Array.isArray(result?.messages) ? result.messages : []);
5458
5511
  const seqs = messages
@@ -5519,6 +5572,7 @@ export class AUNClient {
5519
5572
  decrypted.push(plaintext);
5520
5573
  }
5521
5574
  const cursor = isJsonObject(result.cursor) ? result.cursor : null;
5575
+ const hasServerCursor = cursor !== null && Object.prototype.hasOwnProperty.call(cursor, 'current_seq');
5522
5576
  const serverAckSeq = Number(cursor?.current_seq ?? 0);
5523
5577
  if (Number.isFinite(serverAckSeq) && serverAckSeq > 0) {
5524
5578
  const contig = this._seqTracker.getContiguousSeq(ns);
@@ -5533,10 +5587,16 @@ export class AUNClient {
5533
5587
  await this._drainOrderedMessages(ns);
5534
5588
  this._saveSeqTrackerState();
5535
5589
  }
5536
- if (messages.length > 0 && contigAdvanced && ackSeq > 0) {
5590
+ const ackNeeded = messages.length > 0
5591
+ && ackSeq > 0
5592
+ && ownsCursor
5593
+ && (contigAdvanced || (hasServerCursor && ackSeq > serverAckSeq));
5594
+ if (ackNeeded) {
5537
5595
  this._safeAsync(this._ackGroupV2(gid, ackSeq).then(() => undefined));
5538
5596
  }
5539
5597
  const nextAfter = Math.max(pageMaxSeq, nextAfterSeq);
5598
+ if (!ownsCursor)
5599
+ break;
5540
5600
  if (messages.length === 0 || nextAfter <= nextAfterSeq || result.has_more === false)
5541
5601
  break;
5542
5602
  nextAfterSeq = nextAfter;
@@ -5546,6 +5606,31 @@ export class AUNClient {
5546
5606
  }
5547
5607
  return decrypted;
5548
5608
  }
5609
+ _groupCursorParams(params) {
5610
+ const cursorParams = {};
5611
+ for (const key of ['device_id', 'slot_id', 'device_name', 'device_type']) {
5612
+ const value = params[key];
5613
+ if (value !== undefined && value !== null)
5614
+ cursorParams[key] = value;
5615
+ }
5616
+ return cursorParams;
5617
+ }
5618
+ _explicitGroupCursorParams(params) {
5619
+ const value = params._group_cursor_params;
5620
+ if (!isJsonObject(value))
5621
+ return {};
5622
+ return { ...value };
5623
+ }
5624
+ _groupCursorTargetsCurrentInstance(params) {
5625
+ const deviceId = String(params.device_id ?? '').trim();
5626
+ const slotId = String(params.slot_id ?? '').trim();
5627
+ return (!deviceId || deviceId === (this._deviceId ?? ''))
5628
+ && (!slotId || slotId === (this._slotId ?? ''));
5629
+ }
5630
+ async _rawGroupAckMessages(params) {
5631
+ const p = { ...params };
5632
+ return await this._callRawV2Rpc('group.ack_messages', p);
5633
+ }
5549
5634
  /**
5550
5635
  * 确认 V2 群消息已消费。
5551
5636
  *
@@ -5566,7 +5651,7 @@ export class AUNClient {
5566
5651
  this._clientLog.warn(`ackGroupV2 clamp: group=${gid} up_to_seq=${seq} > max_seen=${maxSeen}, clamp`);
5567
5652
  seq = maxSeen;
5568
5653
  }
5569
- return this.call('group.v2.ack', { group_id: gid, up_to_seq: seq });
5654
+ return this._callRawV2Rpc('group.v2.ack', { group_id: gid, up_to_seq: seq });
5570
5655
  }
5571
5656
  // ── V2 thought(per-device wrap,服务端透传,不持久化)──────────
5572
5657
  /**
@@ -6387,8 +6472,8 @@ export class AUNClient {
6387
6472
  return;
6388
6473
  }
6389
6474
  let signature = '';
6390
- const identity = this._identity;
6391
- if (identity?.private_key_pem) {
6475
+ const currentAid = this._currentAid;
6476
+ if (currentAid?.privateKeyPem) {
6392
6477
  try {
6393
6478
  const signPayloadObj = {
6394
6479
  group_id: groupId,
@@ -6398,7 +6483,7 @@ export class AUNClient {
6398
6483
  };
6399
6484
  const signPayload = stableStringify(signPayloadObj);
6400
6485
  const signPayloadBytes = new TextEncoder().encode(signPayload);
6401
- const privKey = await importPrivateKeyEcdsa(identity.private_key_pem);
6486
+ const privKey = await importPrivateKeyEcdsa(currentAid.privateKeyPem);
6402
6487
  const sigBytes = await ecdsaSignDer(privKey, signPayloadBytes);
6403
6488
  signature = uint8ToBase64(sigBytes);
6404
6489
  }
@@ -6594,7 +6679,7 @@ export class AUNClient {
6594
6679
  if (newContig > 0 && newContig !== contigBefore) {
6595
6680
  const maxSeen = this._seqTracker.getMaxSeenSeq(ns);
6596
6681
  const ackSeq = maxSeen > 0 ? Math.min(newContig, maxSeen) : newContig;
6597
- this.call('message.v2.ack', { up_to_seq: ackSeq })
6682
+ this._callRawV2Rpc('message.v2.ack', { up_to_seq: ackSeq })
6598
6683
  .catch(e => this._clientLog.debug(`V2 P2P push-ack failed: ${e}`));
6599
6684
  }
6600
6685
  this._clientLog.debug(`_onV2PushNotification: push 带 payload 解密成功, contiguous_seq=${contigBefore}->${newContig} push_seq=${pushSeq}`);