@agentunion/fastaun 0.4.3 → 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 (50) hide show
  1. package/CHANGELOG.md +198 -185
  2. 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 -194
  3. 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
  4. 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
  5. package/_packed_docs/CHANGELOG.md +198 -185
  6. package/_packed_docs/INDEX.md +17 -17
  7. package/_packed_docs/KITE_DOCS_GUIDE.md +11 -11
  8. package/_packed_docs/agent.md/SCHEMA.md +49 -49
  9. package/_packed_docs/agent.md/examples/signed-openclaw-lobster.md +22 -22
  10. 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
  11. package/_packed_docs/cli/AUN-CLI/350/256/276/350/256/241/346/226/207/346/241/243.md +686 -686
  12. package/_packed_docs/design/2026-05-22-aun-rpc-trace-enhancement.md +542 -542
  13. 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
  14. 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
  15. 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
  16. 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
  17. 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
  18. package/_packed_docs/protocol/README.md +1 -1
  19. package/_packed_docs/protocol/aun-docs-guide.md +1 -1
  20. package/_packed_docs/protocol//351/231/204/345/275/225A-/346/234/257/350/257/255/350/241/250.md +15 -15
  21. 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
  22. 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
  23. 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
  24. 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
  25. package/_packed_docs/python-sdk-v2-only-changelog.md +189 -189
  26. package/_packed_docs/sdk/01-/345/277/253/351/200/237/345/274/200/345/247/213.md +1 -1
  27. package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +1 -1
  28. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +1 -0
  29. package/_packed_docs/sdk/09-payload-reference.md +13 -13
  30. 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
  31. package/dist/aid.d.ts +2 -1
  32. package/dist/aid.js +7 -6
  33. package/dist/aid.js.map +1 -1
  34. package/dist/auth.js +4 -0
  35. package/dist/auth.js.map +1 -1
  36. package/dist/client.d.ts +6 -0
  37. package/dist/client.js +204 -52
  38. package/dist/client.js.map +1 -1
  39. package/dist/keystore/file.d.ts +0 -2
  40. package/dist/keystore/file.js +6 -44
  41. package/dist/keystore/file.js.map +1 -1
  42. package/dist/tools/cross-sdk-agent.js +0 -9
  43. package/dist/tools/cross-sdk-agent.js.map +1 -1
  44. package/dist/transport.d.ts +1 -0
  45. package/dist/transport.js +7 -1
  46. package/dist/transport.js.map +1 -1
  47. package/dist/v2/session/keystore.js +2 -2
  48. package/dist/version.d.ts +1 -1
  49. package/dist/version.js +1 -1
  50. package/package.json +1 -1
package/dist/client.js CHANGED
@@ -37,6 +37,14 @@ import { AID } from './aid.js';
37
37
  function isPromiseLike(value) {
38
38
  return Boolean(value && typeof value.then === 'function');
39
39
  }
40
+ function isAIDObject(value) {
41
+ const candidate = value;
42
+ return Boolean(candidate
43
+ && typeof candidate === 'object'
44
+ && typeof candidate.aid === 'string'
45
+ && typeof candidate.aunPath === 'string'
46
+ && typeof candidate.isPrivateKeyValid === 'function');
47
+ }
40
48
  /**
41
49
  * 递归排序键的 JSON 序列化(Canonical JSON for AUN)
42
50
  * 等价于 Python json.dumps(sort_keys=True, separators=(",",":"), ensure_ascii=False)
@@ -128,6 +136,20 @@ const DEFAULT_SESSION_OPTIONS = {
128
136
  http: 30.0,
129
137
  },
130
138
  };
139
+ const PUBLIC_CONNECTION_OPTION_KEYS = new Set([
140
+ 'auto_reconnect',
141
+ 'connect_timeout',
142
+ 'retry_initial_delay',
143
+ 'retry_max_delay',
144
+ 'retry_max_attempts',
145
+ 'heartbeat_interval',
146
+ 'call_timeout',
147
+ 'connection_kind',
148
+ 'short_ttl_ms',
149
+ 'delivery_mode',
150
+ 'extra_info',
151
+ 'background_sync',
152
+ ]);
131
153
  const PROTECTED_HEADERS_METHODS = new Set([
132
154
  'message.send',
133
155
  'group.send',
@@ -498,6 +520,7 @@ export class AUNClient {
498
520
  // ── V2 E2EE 状态 ──────────────────────────────────────────────
499
521
  _v2Session;
500
522
  _v2KeyStore;
523
+ _v2SessionInitInFlight = null;
501
524
  /** V2 bootstrap 缓存:aid/group:id → 设备列表 + 时间戳 */
502
525
  _v2BootstrapCache = new Map();
503
526
  _connectCapabilities = null;
@@ -527,10 +550,10 @@ export class AUNClient {
527
550
  _logger;
528
551
  _clientLog;
529
552
  constructor(aid) {
530
- if (typeof aid === 'string') {
531
- throw new ValidationError('AUNClient aid must be an AID object, not a string');
553
+ if (aid !== null && aid !== undefined && !isAIDObject(aid)) {
554
+ throw new ValidationError('AUNClient only accepts an AID object or no argument');
532
555
  }
533
- const inputAid = (aid !== null && aid !== undefined && typeof aid.aunPath === 'string' && typeof aid.isPrivateKeyValid === 'function') ? aid : null;
556
+ const inputAid = aid ?? null;
534
557
  const rawConfig = {};
535
558
  if (inputAid) {
536
559
  rawConfig.aun_path = inputAid.aunPath;
@@ -566,7 +589,6 @@ export class AUNClient {
566
589
  });
567
590
  this._discovery = new GatewayDiscovery({ verifySsl: this._configModel.verifySsl, logger: this._clientLog, net: dnsNet });
568
591
  const keystore = new FileKeyStore(this._configModel.aunPath, {
569
- encryptionSeed: this._configModel.seedPassword ?? undefined,
570
592
  logger: this._logger.for('aun_core.keystore'),
571
593
  secretStoreLogger: this._logger.for('aun_core.secret-store'),
572
594
  });
@@ -614,7 +636,7 @@ export class AUNClient {
614
636
  this._currentAid = inputAid;
615
637
  this._identity = {
616
638
  aid: inputAid.aid,
617
- private_key_pem: inputAid._privateKeyPem ?? '',
639
+ private_key_pem: inputAid.privateKeyPem,
618
640
  public_key_der_b64: inputAid.publicKey,
619
641
  cert: inputAid.certPem,
620
642
  };
@@ -697,6 +719,71 @@ export class AUNClient {
697
719
  get lastErrorCode() {
698
720
  return this._lastErrorCode;
699
721
  }
722
+ _applyAidRuntimeContext(aid) {
723
+ const rawConfig = {
724
+ aun_path: aid.aunPath,
725
+ verify_ssl: aid.verifySsl,
726
+ debug: aid.debug,
727
+ };
728
+ if (aid.rootCaPath)
729
+ rawConfig.root_ca_path = aid.rootCaPath;
730
+ const nextConfig = configFromMap(rawConfig);
731
+ try {
732
+ const close = this._keystore.close;
733
+ if (typeof close === 'function')
734
+ close.call(this._keystore);
735
+ }
736
+ catch {
737
+ // best-effort cleanup before switching keystore roots
738
+ }
739
+ this._configModel = nextConfig;
740
+ this.config.aun_path = nextConfig.aunPath;
741
+ this.config.root_ca_path = nextConfig.rootCaPath;
742
+ this.config.seed_password = nextConfig.seedPassword;
743
+ this._agentMdPath = path.join(nextConfig.aunPath, 'AIDs');
744
+ this._agentMdCache.clear();
745
+ this._agentMdFetchInflight.clear();
746
+ this._agentMdDownloadInflight.clear();
747
+ this._peerCache.clear();
748
+ this._certCache.clear();
749
+ this._gatewayUrl = null;
750
+ this._deviceId = aid.deviceId || getDeviceId(nextConfig.aunPath);
751
+ this._slotId = aid.slotId || 'default';
752
+ const debugFlag = nextConfig.debug;
753
+ this._logger = new AUNLogger({ debug: debugFlag, aunPath: nextConfig.aunPath });
754
+ this._logger.bindDeviceId(this._deviceId);
755
+ this._clientLog = this._logger.for('aun_core.client');
756
+ const dnsNet = new DnsResilientNet({
757
+ verifySsl: nextConfig.verifySsl,
758
+ logger: this._clientLog,
759
+ });
760
+ this._discovery = new GatewayDiscovery({ verifySsl: nextConfig.verifySsl, logger: this._clientLog, net: dnsNet });
761
+ const keystore = new FileKeyStore(nextConfig.aunPath, {
762
+ logger: this._logger.for('aun_core.keystore'),
763
+ secretStoreLogger: this._logger.for('aun_core.secret-store'),
764
+ });
765
+ this._keystore = keystore;
766
+ this._auth = new AuthFlow({
767
+ keystore,
768
+ crypto: new CryptoProvider(),
769
+ aid: aid.aid,
770
+ deviceId: this._deviceId,
771
+ slotId: this._slotId,
772
+ rootCaPath: nextConfig.rootCaPath ?? undefined,
773
+ verifySsl: nextConfig.verifySsl,
774
+ logger: this._logger.for('aun_core.auth'),
775
+ net: dnsNet,
776
+ });
777
+ this._transport = new RPCTransport({
778
+ eventDispatcher: this._dispatcher,
779
+ timeout: 10_000,
780
+ onDisconnect: (err, closeCode) => this._handleTransportDisconnect(err, closeCode),
781
+ verifySsl: nextConfig.verifySsl,
782
+ logger: this._logger.for('aun_core.transport'),
783
+ dnsNet,
784
+ });
785
+ this._transport.setMetaObserver((meta) => this._observeRpcMeta(meta));
786
+ }
700
787
  loadIdentity(aid) {
701
788
  if (!aid?.isPrivateKeyValid()) {
702
789
  throw new StateError('loadIdentity requires an AID with a valid private key');
@@ -705,16 +792,15 @@ export class AUNClient {
705
792
  if (publicState !== ConnectionState.NO_IDENTITY && publicState !== ConnectionState.CLOSED) {
706
793
  throw new StateError(`loadIdentity not allowed in state ${publicState}`);
707
794
  }
795
+ this._applyAidRuntimeContext(aid);
708
796
  this._currentAid = aid;
709
797
  this._aid = aid.aid;
710
798
  this._identity = {
711
799
  aid: aid.aid,
712
- private_key_pem: aid._privateKeyPem ?? '',
800
+ private_key_pem: aid.privateKeyPem,
713
801
  public_key_der_b64: aid.publicKey,
714
802
  cert: aid.certPem,
715
803
  };
716
- this._auth._aid = aid.aid;
717
- this._slotId = aid.slotId || 'default';
718
804
  this._state = 'standby';
719
805
  this._closing = false;
720
806
  this._lastError = null;
@@ -1589,10 +1675,11 @@ export class AUNClient {
1589
1675
  async connect(opts) {
1590
1676
  const tStart = Date.now();
1591
1677
  // 先校验非法参数(ValidationError),再检查身份(StateError)
1592
- if (opts !== undefined && typeof opts === 'object') {
1678
+ if (opts !== undefined && opts !== null && typeof opts === 'object') {
1593
1679
  const raw = opts;
1594
- if ('access_token' in raw || 'aid' in raw || 'token' in raw) {
1595
- throw new ValidationError('connect options must not include access_token/aid; these are managed internally');
1680
+ const invalid = Object.keys(raw).filter((key) => !PUBLIC_CONNECTION_OPTION_KEYS.has(key)).sort();
1681
+ if (invalid.length > 0) {
1682
+ throw new ValidationError(`connect options contain unsupported field(s): ${invalid.join(', ')}`);
1596
1683
  }
1597
1684
  }
1598
1685
  const target = this._currentAid?.aid ?? this._aid ?? '';
@@ -1788,6 +1875,14 @@ export class AUNClient {
1788
1875
  }
1789
1876
  this._validateOutboundCall(method, p);
1790
1877
  this._injectMessageCursorContext(method, p);
1878
+ if (method.startsWith('group.')
1879
+ && !('_group_cursor_params' in p)
1880
+ && !Boolean(p._pull_gate_locked)) {
1881
+ const explicitCursorParams = this._groupCursorParams(p);
1882
+ if (Object.keys(explicitCursorParams).length > 0) {
1883
+ p._group_cursor_params = explicitCursorParams;
1884
+ }
1885
+ }
1791
1886
  // group.* 方法的 group_id 归一化为 canonical 格式(兼容老/污染数据)
1792
1887
  if (method.startsWith('group.') && p.group_id !== undefined && p.group_id !== null) {
1793
1888
  const rawGroupId = String(p.group_id);
@@ -1889,7 +1984,17 @@ export class AUNClient {
1889
1984
  throw new ValidationError('group.pull requires group_id');
1890
1985
  }
1891
1986
  await this._ensureV2SessionReady('group.pull');
1892
- const messages = await runWithRpcPriority(() => this._pullGroupV2(String(p.group_id), Number(p.after_seq ?? p.after_message_seq ?? 0) || 0, Number(p.limit ?? 50) || 50, { gateLocked: true }));
1987
+ const hasExplicitAfterSeq = 'after_seq' in p || 'after_message_seq' in p;
1988
+ const cursorParams = this._explicitGroupCursorParams(p);
1989
+ const ownsCursor = Object.keys(cursorParams).length === 0 || this._groupCursorTargetsCurrentInstance(cursorParams);
1990
+ const pullOpts = { gateLocked: true };
1991
+ if (hasExplicitAfterSeq)
1992
+ pullOpts.explicitAfterSeq = true;
1993
+ if (Object.keys(cursorParams).length > 0)
1994
+ pullOpts.cursorParams = cursorParams;
1995
+ if (!ownsCursor)
1996
+ pullOpts.ownsCursor = false;
1997
+ const messages = await runWithRpcPriority(() => this._pullGroupV2(String(p.group_id), Number(p.after_seq ?? p.after_message_seq ?? 0) || 0, Number(p.limit ?? 50) || 50, pullOpts));
1893
1998
  return { messages };
1894
1999
  }
1895
2000
  if (method === 'group.ack_messages' || method === 'group.v2.ack') {
@@ -1897,12 +2002,18 @@ export class AUNClient {
1897
2002
  throw new ValidationError('group.ack_messages requires group_id');
1898
2003
  }
1899
2004
  await this._ensureV2SessionReady('group.ack_messages');
2005
+ const cursorParams = this._explicitGroupCursorParams(p);
2006
+ const ownsCursor = Object.keys(cursorParams).length === 0 || this._groupCursorTargetsCurrentInstance(cursorParams);
2007
+ if (method === 'group.ack_messages' && !ownsCursor) {
2008
+ return await runWithRpcPriority(() => this._rawGroupAckMessages(p));
2009
+ }
1900
2010
  return await runWithRpcPriority(() => this._ackGroupV2(String(p.group_id), Number(p.seq ?? p.msg_seq ?? p.up_to_seq ?? 0) || undefined));
1901
2011
  }
1902
2012
  if (method === 'message.pull') {
1903
2013
  delete p._skip_auto_ack;
1904
2014
  delete p.skip_auto_ack;
1905
2015
  }
2016
+ delete p._group_cursor_params;
1906
2017
  // 关键操作自动附加客户端签名
1907
2018
  if (SIGNED_METHODS.has(method)) {
1908
2019
  if (this._shouldSkipClientSignature(method, p)) {
@@ -1973,6 +2084,7 @@ export class AUNClient {
1973
2084
  delete p._pull_gate_locked;
1974
2085
  delete p._skip_auto_ack;
1975
2086
  delete p.skip_auto_ack;
2087
+ delete p._group_cursor_params;
1976
2088
  if (method.startsWith('group.') && p.group_id !== undefined && p.group_id !== null) {
1977
2089
  p.group_id = normalizeGroupId(String(p.group_id)) || String(p.group_id);
1978
2090
  }
@@ -2021,13 +2133,12 @@ export class AUNClient {
2021
2133
  * 签名覆盖所有非 _ 前缀且非 client_signature 的业务字段。
2022
2134
  */
2023
2135
  _signClientOperation(method, params) {
2024
- const identity = this._identity;
2025
- if (!identity || !identity.private_key_pem)
2136
+ const currentAid = this._currentAid;
2137
+ if (!currentAid?.privateKeyPem)
2026
2138
  return;
2027
2139
  try {
2028
- const aid = String(identity.aid ?? '');
2140
+ const aid = currentAid.aid;
2029
2141
  const ts = String(Math.floor(Date.now() / 1000));
2030
- // 计算 params hash — 必须递归排序所有键(与 Python json.dumps(sort_keys=True, separators=(",",":")) 一致)
2031
2142
  const paramsForHash = {};
2032
2143
  for (const [k, v] of Object.entries(params)) {
2033
2144
  if (k !== 'client_signature' && !k.startsWith('_')) {
@@ -2037,11 +2148,11 @@ export class AUNClient {
2037
2148
  const paramsJson = stableStringify(paramsForHash);
2038
2149
  const paramsHash = crypto.createHash('sha256').update(paramsJson, 'utf-8').digest('hex');
2039
2150
  const signData = Buffer.from(`${method}|${aid}|${ts}|${paramsHash}`, 'utf-8');
2040
- const privateKey = crypto.createPrivateKey(String(identity.private_key_pem));
2151
+ const privateKey = crypto.createPrivateKey(currentAid.privateKeyPem);
2041
2152
  const signature = crypto.sign('SHA256', signData, privateKey);
2042
2153
  // 证书指纹
2043
2154
  let certFingerprint = '';
2044
- const certPem = String(identity.cert ?? '');
2155
+ const certPem = currentAid.certPem;
2045
2156
  if (certPem) {
2046
2157
  const certObj = new crypto.X509Certificate(certPem);
2047
2158
  certFingerprint = 'sha256:' + certObj.fingerprint256.replace(/:/g, '').toLowerCase();
@@ -2747,6 +2858,27 @@ export class AUNClient {
2747
2858
  }
2748
2859
  return Math.max(0, ...values.filter((value) => Number.isFinite(value)));
2749
2860
  }
2861
+ _groupCursorParams(params) {
2862
+ const cursorParams = {};
2863
+ for (const key of ['device_id', 'slot_id', 'device_name', 'device_type']) {
2864
+ const value = params[key];
2865
+ if (value !== undefined && value !== null)
2866
+ cursorParams[key] = value;
2867
+ }
2868
+ return cursorParams;
2869
+ }
2870
+ _explicitGroupCursorParams(params) {
2871
+ const value = params._group_cursor_params;
2872
+ if (!isJsonObject(value))
2873
+ return {};
2874
+ return { ...value };
2875
+ }
2876
+ _groupCursorTargetsCurrentInstance(params) {
2877
+ const deviceId = String(params.device_id ?? '').trim();
2878
+ const slotId = String(params.slot_id ?? '').trim();
2879
+ return (!deviceId || deviceId === (this._deviceId ?? ''))
2880
+ && (!slotId || slotId === (this._slotId ?? ''));
2881
+ }
2750
2882
  _schedulePullFollowup(method, params, result) {
2751
2883
  if (method === 'message.pull')
2752
2884
  method = 'message.v2.pull';
@@ -4026,16 +4158,26 @@ export class AUNClient {
4026
4158
  this._restoreSeqTrackerState();
4027
4159
  }
4028
4160
  this._startBackgroundTasks();
4029
- // V2 E2EE:初始化 session 并注册本设备 SPK。
4030
- try {
4031
- await this._initV2Session();
4161
+ const connectionKind = String(params.connection_kind ?? 'long');
4162
+ const isShortConnection = connectionKind === 'short';
4163
+ if (!isShortConnection) {
4164
+ // V2 E2EE:长连接上线时初始化 session 并注册本设备 SPK。
4165
+ try {
4166
+ await this._initV2Session();
4167
+ }
4168
+ catch (exc) {
4169
+ this._clientLog.warn(`V2 session init failed (non-fatal): ${formatCaughtError(exc)}`);
4170
+ }
4032
4171
  }
4033
- catch (exc) {
4034
- this._clientLog.warn(`V2 session init failed (non-fatal): ${formatCaughtError(exc)}`);
4172
+ else {
4173
+ this._clientLog.debug('V2 session init deferred for short connection');
4035
4174
  }
4036
4175
  // connect/reconnect 成功后自动触发一次 P2P message.v2.pull,补齐离线期间积压
4037
4176
  // 群消息按惰性触发,不在此处主动 pull
4038
- if (this._sessionOptions.background_sync !== false) {
4177
+ const hasExplicitBackgroundSync = Object.prototype.hasOwnProperty.call(params, 'background_sync');
4178
+ const backgroundSyncEnabled = this._sessionOptions.background_sync !== false
4179
+ && (!isShortConnection || hasExplicitBackgroundSync);
4180
+ if (backgroundSyncEnabled) {
4039
4181
  void this._fillP2pGap().catch((exc) => {
4040
4182
  this._clientLog.warn(`schedule post-connect P2P gap fill failed: ${formatCaughtError(exc)}`);
4041
4183
  });
@@ -4074,6 +4216,15 @@ export class AUNClient {
4074
4216
  }
4075
4217
  /** V2-only:所有加密入口都必须有 V2 session。 */
4076
4218
  async _ensureV2SessionReady(method, errorMessage) {
4219
+ if (!this._v2Session) {
4220
+ if (!this._v2SessionInitInFlight) {
4221
+ this._v2SessionInitInFlight = this._initV2Session()
4222
+ .finally(() => {
4223
+ this._v2SessionInitInFlight = null;
4224
+ });
4225
+ }
4226
+ await this._v2SessionInitInFlight;
4227
+ }
4077
4228
  if (!this._v2Session) {
4078
4229
  throw new StateError(errorMessage ?? `V2 session not initialized; encrypted ${method} requires E2EE V2`);
4079
4230
  }
@@ -4106,32 +4257,13 @@ export class AUNClient {
4106
4257
  identity = null;
4107
4258
  }
4108
4259
  }
4109
- if (!identity?.private_key_pem) {
4110
- // fallback:缓存的 identity 可能被 instanceState 污染,重新从 keystore 加载
4111
- try {
4112
- identity = this._keystore.loadIdentity(this._aid);
4113
- if (identity?.private_key_pem) {
4114
- this._identity = identity;
4115
- this._clientLog.warn('V2 session init: identity cache was stale, reloaded from keystore');
4116
- // 重新持久化 instance_state,清理脏数据
4117
- const persistIdentity = this._auth._persistIdentity;
4118
- if (typeof persistIdentity === 'function') {
4119
- try {
4120
- persistIdentity.call(this._auth, identity);
4121
- }
4122
- catch { /* best-effort */ }
4123
- }
4124
- }
4125
- }
4126
- catch {
4127
- identity = null;
4128
- }
4129
- }
4130
- if (!identity?.private_key_pem) {
4260
+ // 私钥由 AIDStore 管理,直接从 _currentAid 读取明文私钥
4261
+ const currentAid = this._currentAid;
4262
+ if (!currentAid?.privateKeyPem) {
4131
4263
  this._clientLog.warn('V2 session init skipped: no AID private key');
4132
4264
  return;
4133
4265
  }
4134
- const privateKey = crypto.createPrivateKey(String(identity.private_key_pem));
4266
+ const privateKey = crypto.createPrivateKey(currentAid.privateKeyPem);
4135
4267
  const jwk = privateKey.export({ format: 'jwk' });
4136
4268
  if (jwk.kty !== 'EC' || jwk.crv !== 'P-256' || !jwk.d) {
4137
4269
  throw new StateError('AID private key must be EC P-256');
@@ -4677,6 +4809,7 @@ export class AUNClient {
4677
4809
  decrypted.push(plaintext);
4678
4810
  this._logMessageDebug('decrypt-ok', 'message.v2.pull', 'message.received', plaintext);
4679
4811
  }
4812
+ const hasServerAckSeq = Object.prototype.hasOwnProperty.call(result, 'server_ack_seq');
4680
4813
  const serverAckSeq = Number(result.server_ack_seq ?? 0);
4681
4814
  if (ns && Number.isFinite(serverAckSeq) && serverAckSeq > 0) {
4682
4815
  const contig = this._seqTracker.getContiguousSeq(ns);
@@ -4692,7 +4825,11 @@ export class AUNClient {
4692
4825
  await this._drainOrderedMessages(ns, undefined, true);
4693
4826
  this._saveSeqTrackerState();
4694
4827
  }
4695
- if (messages.length > 0 && contigAdvanced && ackSeq > 0 && !opts?.skipAutoAck) {
4828
+ const ackNeeded = messages.length > 0
4829
+ && ackSeq > 0
4830
+ && !opts?.skipAutoAck
4831
+ && (contigAdvanced || (hasServerAckSeq && ackSeq > serverAckSeq));
4832
+ if (ackNeeded) {
4696
4833
  this._clientLog.debug(`message.v2.pull scheduling auto-ack: ns=${ns}, ack_seq=${ackSeq}, raw_count=${messages.length}`);
4697
4834
  this._safeAsync(this._ackV2(ackSeq).then(() => undefined));
4698
4835
  }
@@ -4955,7 +5092,9 @@ export class AUNClient {
4955
5092
  }
4956
5093
  const decrypted = [];
4957
5094
  let totalRawCount = 0;
4958
- let nextAfterSeq = afterSeq || this._seqTracker.getContiguousSeq(ns);
5095
+ const cursorParams = opts?.cursorParams ?? {};
5096
+ const ownsCursor = opts?.ownsCursor !== false;
5097
+ let nextAfterSeq = opts?.explicitAfterSeq ? afterSeq : (afterSeq || this._seqTracker.getContiguousSeq(ns));
4959
5098
  let pageCount = 0;
4960
5099
  const maxPages = 100;
4961
5100
  while (pageCount < maxPages) {
@@ -4965,6 +5104,7 @@ export class AUNClient {
4965
5104
  group_id: gid,
4966
5105
  after_seq: nextAfterSeq,
4967
5106
  limit,
5107
+ ...cursorParams,
4968
5108
  });
4969
5109
  const messages = (Array.isArray(result.messages) ? result.messages : []);
4970
5110
  totalRawCount += messages.length;
@@ -5043,7 +5183,9 @@ export class AUNClient {
5043
5183
  decrypted.push(plaintext);
5044
5184
  this._logMessageDebug('decrypt-ok', 'group.v2.pull', 'group.message_created', plaintext);
5045
5185
  }
5046
- const retentionFloor = this._pullRetentionFloor(result, 'retention_floor_message_seq', 'retention_floor_message_seq');
5186
+ const cursorCurrentSeq = Number(cursor?.current_seq ?? 0);
5187
+ const hasServerCursor = cursor !== null && Object.prototype.hasOwnProperty.call(cursor, 'current_seq');
5188
+ const retentionFloor = Math.max(this._pullRetentionFloor(result, 'retention_floor_message_seq', 'retention_floor_message_seq'), Number.isFinite(cursorCurrentSeq) ? cursorCurrentSeq : 0);
5047
5189
  if (retentionFloor > 0) {
5048
5190
  const contig = this._seqTracker.getContiguousSeq(ns);
5049
5191
  if (contig < retentionFloor) {
@@ -5057,11 +5199,17 @@ export class AUNClient {
5057
5199
  await this._drainOrderedMessages(ns, undefined, true);
5058
5200
  this._saveSeqTrackerState();
5059
5201
  }
5060
- if (messages.length > 0 && contigAdvanced && ackSeq > 0) {
5202
+ const ackNeeded = messages.length > 0
5203
+ && ackSeq > 0
5204
+ && ownsCursor
5205
+ && (contigAdvanced || (hasServerCursor && ackSeq > cursorCurrentSeq));
5206
+ if (ackNeeded) {
5061
5207
  this._clientLog.debug(`group.v2.pull scheduling auto-ack: group=${gid}, ns=${ns}, ack_seq=${ackSeq}, raw_count=${messages.length}`);
5062
5208
  this._safeAsync(this._ackGroupV2(gid, ackSeq).then(() => undefined));
5063
5209
  }
5064
5210
  const nextAfter = Math.max(pageMaxSeq, nextAfterSeq);
5211
+ if (!ownsCursor)
5212
+ break;
5065
5213
  if (messages.length === 0 || nextAfter <= nextAfterSeq || result.has_more === false)
5066
5214
  break;
5067
5215
  nextAfterSeq = nextAfter;
@@ -5072,6 +5220,10 @@ export class AUNClient {
5072
5220
  this._clientLog.debug(`group.v2.pull done: group=${gid}, requested_after_seq=${afterSeq}, pages=${pageCount}, decrypted=${decrypted.length}, ns=${ns}`);
5073
5221
  return decrypted;
5074
5222
  }
5223
+ async _rawGroupAckMessages(params) {
5224
+ const p = { ...params };
5225
+ return await this._callRawV2Rpc('group.ack_messages', p);
5226
+ }
5075
5227
  /** V2 Group ack。 */
5076
5228
  async _ackGroupV2(groupId, upToSeq) {
5077
5229
  const gid = normalizeGroupId(groupId) || String(groupId ?? '').trim();
@@ -6071,7 +6223,7 @@ export class AUNClient {
6071
6223
  return;
6072
6224
  }
6073
6225
  let signature = '';
6074
- const privateKeyPem = String(this._identity?.private_key_pem ?? '');
6226
+ const privateKeyPem = this._currentAid?.privateKeyPem ?? '';
6075
6227
  if (privateKeyPem) {
6076
6228
  try {
6077
6229
  const signPayload = stableStringify({