@agentunion/fastaun 0.4.4 → 0.4.6

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 (64) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/_packed_docs/CHANGELOG.md +41 -0
  3. package/_packed_docs/INDEX.md +2 -2
  4. package/_packed_docs/KITE_DOCS_GUIDE.md +1 -1
  5. 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 +73 -84
  6. package/_packed_docs/sdk/01-/345/277/253/351/200/237/345/274/200/345/247/213.md +16 -15
  7. package/_packed_docs/sdk/02-WebSocket/345/215/217/350/256/256.md +2 -2
  8. package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +22 -5
  9. package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +42 -26
  10. package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +2 -2
  11. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +61 -35
  12. package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +3 -3
  13. package/_packed_docs/sdk/09-message-rpc-manual.md +6 -6
  14. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +6 -4
  15. package/_packed_docs/sdk/INDEX.md +2 -2
  16. package/_packed_docs/sdk/README.md +3 -3
  17. package/dist/agent-md.d.ts +101 -0
  18. package/dist/agent-md.js +778 -0
  19. package/dist/agent-md.js.map +1 -0
  20. package/dist/aid-store.d.ts +7 -39
  21. package/dist/aid-store.js +74 -141
  22. package/dist/aid-store.js.map +1 -1
  23. package/dist/auth.d.ts +17 -32
  24. package/dist/auth.js +42 -295
  25. package/dist/auth.js.map +1 -1
  26. package/dist/client.d.ts +6 -65
  27. package/dist/client.js +213 -913
  28. package/dist/client.js.map +1 -1
  29. package/dist/crypto.d.ts +1 -1
  30. package/dist/crypto.js +1 -1
  31. package/dist/index.d.ts +4 -2
  32. package/dist/index.js +3 -1
  33. package/dist/index.js.map +1 -1
  34. package/dist/keystore/aid-db.d.ts +0 -4
  35. package/dist/keystore/aid-db.js +4 -95
  36. package/dist/keystore/aid-db.js.map +1 -1
  37. package/dist/keystore/file.d.ts +8 -3
  38. package/dist/keystore/file.js +103 -24
  39. package/dist/keystore/file.js.map +1 -1
  40. package/dist/keystore/index.d.ts +43 -36
  41. package/dist/keystore/index.js +3 -2
  42. package/dist/keystore/index.js.map +1 -1
  43. package/dist/keystore/local-identity-store.d.ts +70 -0
  44. package/dist/keystore/local-identity-store.js +525 -0
  45. package/dist/keystore/local-identity-store.js.map +1 -0
  46. package/dist/keystore/local-token-store.d.ts +68 -0
  47. package/dist/keystore/local-token-store.js +368 -0
  48. package/dist/keystore/local-token-store.js.map +1 -0
  49. package/dist/register-flow.d.ts +57 -0
  50. package/dist/register-flow.js +433 -0
  51. package/dist/register-flow.js.map +1 -0
  52. package/dist/secret-store/file-store.js +6 -1
  53. package/dist/secret-store/file-store.js.map +1 -1
  54. package/dist/v2/session/keystore.d.ts +5 -0
  55. package/dist/v2/session/keystore.js +21 -3
  56. package/dist/v2/session/keystore.js.map +1 -1
  57. package/dist/version.d.ts +1 -1
  58. package/dist/version.js +1 -1
  59. package/package.json +1 -1
  60. 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 +0 -302
  61. 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 +0 -194
  62. package/_packed_docs/AUN_SDK_/351/207/215/346/236/204/345/256/236/346/226/275/350/256/241/345/210/222.md +0 -596
  63. 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 +0 -1698
  64. package/_packed_docs/python-sdk-v2-only-changelog.md +0 -189
package/dist/client.js CHANGED
@@ -11,22 +11,21 @@
11
11
  * - 群组 E2EE 全自动编排(建群/加人/踢人/退出)
12
12
  */
13
13
  import * as crypto from 'node:crypto';
14
- import * as fs from 'node:fs';
15
14
  import * as http from 'node:http';
16
15
  import * as https from 'node:https';
17
- import * as path from 'node:path';
18
16
  import { URL } from 'node:url';
19
- import { configFromMap, getDeviceId, normalizeInstanceId, normalizeSlotId, slotIsolationKey } from './config.js';
17
+ import { configFromMap, getDeviceId, normalizeSlotId, slotIsolationKey } from './config.js';
20
18
  import { CryptoProvider } from './crypto.js';
21
19
  import { GatewayDiscovery } from './discovery.js';
22
20
  import { DnsResilientNet } from './net.js';
23
21
  import { AUNError, AuthError, ConnectionError, E2EEError, NotFoundError, PermissionError, StateError, TimeoutError, ValidationError, } from './errors.js';
24
22
  import { EventDispatcher } from './events.js';
25
- import { FileKeyStore } from './keystore/file.js';
23
+ import { LocalTokenStore } from './keystore/local-token-store.js';
26
24
  import { AUNLogger } from './logger.js';
27
25
  import { normalizeGroupId } from './group-id.js';
28
26
  import { RPCTransport } from './transport.js';
29
27
  import { AuthFlow } from './auth.js';
28
+ import { AgentMdManager } from './agent-md.js';
30
29
  import { SeqTracker } from './seq-tracker.js';
31
30
  import { V2Session } from './v2/session/index.js';
32
31
  import { encryptP2PMessage, encryptGroupMessage, decryptMessage, } from './v2/e2ee/index.js';
@@ -226,7 +225,6 @@ const SIGNED_METHODS = new Set([
226
225
  ]);
227
226
  /** peer 证书缓存 TTL(1 小时) */
228
227
  const PEER_CERT_CACHE_TTL = 3600;
229
- const AGENT_MD_HTTP_TIMEOUT_MS = 30_000;
230
228
  function normalizeV2WrapPolicy(raw) {
231
229
  if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
232
230
  return { explicit: false, version: '', protocol: '', scope: 'device' };
@@ -404,33 +402,93 @@ function lengthPrefixedBytesKey(...parts) {
404
402
  }
405
403
  return Buffer.concat(chunks);
406
404
  }
407
- function agentMdHttpScheme(gatewayUrl) {
408
- const raw = String(gatewayUrl ?? '').trim().toLowerCase();
409
- return raw.startsWith('ws://') ? 'http' : 'https';
410
- }
411
- function agentMdAuthority(aid, discoveryPort) {
412
- const host = String(aid ?? '').trim();
413
- if (!host)
414
- return '';
415
- if (discoveryPort && !host.includes(':'))
416
- return `${host}:${discoveryPort}`;
417
- return host;
418
- }
419
- async function fetchWithTimeout(input, init, timeoutMs = AGENT_MD_HTTP_TIMEOUT_MS) {
420
- const controller = new AbortController();
421
- const timer = setTimeout(() => controller.abort(), timeoutMs);
422
- try {
423
- return await fetch(input, { ...init, signal: controller.signal });
424
- }
425
- catch (error) {
426
- if (controller.signal.aborted) {
427
- throw new AUNError(`agent.md request timed out after ${timeoutMs}ms`);
428
- }
429
- throw error;
430
- }
431
- finally {
432
- clearTimeout(timer);
433
- }
405
+ function createAgentMdManagerForRuntime(opts) {
406
+ return new AgentMdManager({
407
+ aunPath: opts.config().aunPath,
408
+ verifySsl: opts.config().verifySsl,
409
+ discoveryPort: opts.config().discoveryPort,
410
+ logger: opts.logger(),
411
+ ownerAidGetter: opts.ownerAid,
412
+ currentAidGetter: opts.currentAid,
413
+ gatewayResolver: async (aid) => {
414
+ const gatewayUrl = await opts.gateway.resolve(aid);
415
+ opts.gateway.set(gatewayUrl);
416
+ return gatewayUrl;
417
+ },
418
+ accessTokenResolver: async (aid, gatewayUrl) => {
419
+ const target = String(aid ?? '').trim();
420
+ let identity = opts.identity.get();
421
+ if (!identity || String(identity.aid ?? '') !== target) {
422
+ throw new StateError('no local identity found, register or load an AID first');
423
+ }
424
+ const auth = opts.auth();
425
+ const cachedToken = String(identity.access_token ?? '');
426
+ const expiresAt = auth.getAccessTokenExpiry(identity);
427
+ if (cachedToken && (expiresAt === null || expiresAt > Date.now() / 1000 + 30)) {
428
+ return cachedToken;
429
+ }
430
+ if (identity.refresh_token) {
431
+ try {
432
+ const refreshed = await auth.refreshCachedTokens(gatewayUrl, identity);
433
+ const refreshedToken = String(refreshed.access_token ?? '');
434
+ const refreshedExpiry = auth.getAccessTokenExpiry(refreshed);
435
+ if (refreshedToken && (refreshedExpiry === null || refreshedExpiry > Date.now() / 1000 + 30)) {
436
+ opts.identity.set(refreshed);
437
+ return refreshedToken;
438
+ }
439
+ }
440
+ catch {
441
+ // refresh 失败时回退到完整 authenticate。
442
+ }
443
+ }
444
+ const result = await auth.authenticate(gatewayUrl, { aid: target });
445
+ const token = String(result.access_token ?? '');
446
+ if (!token)
447
+ throw new StateError('authenticate did not return access_token');
448
+ identity = auth.loadIdentityOrNone(target) ?? {
449
+ ...identity,
450
+ access_token: token,
451
+ refresh_token: String(result.refresh_token ?? identity.refresh_token ?? ''),
452
+ access_token_expires_at: typeof result.expires_at === 'number' ? result.expires_at : identity.access_token_expires_at,
453
+ token_exp: typeof result.expires_at === 'number' ? result.expires_at : identity.token_exp,
454
+ expires_at: typeof result.expires_at === 'number' ? result.expires_at : identity.expires_at,
455
+ };
456
+ opts.identity.set(identity);
457
+ return token;
458
+ },
459
+ peerResolver: async (aid) => {
460
+ const target = String(aid ?? '').trim();
461
+ const current = opts.currentAid();
462
+ if (current?.aid === target)
463
+ return current;
464
+ let certPem = '';
465
+ try {
466
+ certPem = String(opts.tokenStore().loadCert(target) ?? '').trim();
467
+ }
468
+ catch {
469
+ certPem = '';
470
+ }
471
+ if (!certPem) {
472
+ if (!opts.gateway.get()) {
473
+ try {
474
+ opts.gateway.set(await opts.gateway.resolve(target));
475
+ }
476
+ catch { /* best effort before cert fetch */ }
477
+ }
478
+ certPem = String(await opts.fetchPeerCert(target) ?? '').trim();
479
+ }
480
+ if (!certPem)
481
+ throw new NotFoundError(`certificate not found for aid: ${target}`);
482
+ return AID._create({
483
+ aid: target,
484
+ aunPath: opts.config().aunPath,
485
+ certPem,
486
+ privateKeyPem: null,
487
+ certValid: true,
488
+ privateKeyValid: false,
489
+ });
490
+ },
491
+ });
434
492
  }
435
493
  export class AUNClient {
436
494
  /** 原始配置 */
@@ -466,7 +524,7 @@ export class AUNClient {
466
524
  /** 认证流程 */
467
525
  _auth;
468
526
  /** 密钥存储 */
469
- _keystore;
527
+ _tokenStore;
470
528
  /** 会话参数(重连用) */
471
529
  _sessionParams = null;
472
530
  /** 会话选项 */
@@ -479,17 +537,7 @@ export class AUNClient {
479
537
  _defaultConnectDeliveryMode;
480
538
  /** peer 证书缓存 */
481
539
  _certCache = new Map();
482
- // AIDs 目录:{agentMdPath}/{aid}/agentmd.json 保存元数据,{agentMdPath}/{aid}/agent.md 保存正文。
483
- _agentMdPath = '';
484
- _localAgentMdPath = '';
485
- _localAgentMdEtag = '';
486
- // gateway 在 RPC envelope._meta.agent_md_etag 注入的服务端 etag;纯观察,无下游依赖。
487
- _remoteAgentMdEtag = '';
488
- _agentMdCache = new Map();
489
- _agentMdFetchInflight = new Map();
490
- _agentMdDownloadInflight = new Map();
491
- _agentMdDownloadActive = 0;
492
- _agentMdDownloadWaiters = [];
540
+ _agentMdManager;
493
541
  /** 消息序列号跟踪器(群消息 + P2P 空洞检测) */
494
542
  _seqTracker = new SeqTracker();
495
543
  _seqTrackerContext = null;
@@ -541,7 +589,6 @@ export class AUNClient {
541
589
  _peerCache = new Map();
542
590
  static V2_SIG_CACHE_TTL_MS = 60 * 60 * 1000;
543
591
  static V2_SIG_CACHE_MAX = 16_384;
544
- static AGENT_MD_DOWNLOAD_CONCURRENCY = 8;
545
592
  _reconnectActive = false;
546
593
  _reconnectAbort = null;
547
594
  _serverKicked = false;
@@ -564,7 +611,6 @@ export class AUNClient {
564
611
  }
565
612
  this._configModel = configFromMap(rawConfig);
566
613
  const initAid = (inputAid && inputAid.isPrivateKeyValid()) ? inputAid.aid : null;
567
- this._agentMdPath = path.join(this._configModel.aunPath, 'AIDs');
568
614
  this.config = {
569
615
  aun_path: this._configModel.aunPath,
570
616
  root_ca_path: this._configModel.rootCaPath,
@@ -588,29 +634,15 @@ export class AUNClient {
588
634
  logger: this._clientLog,
589
635
  });
590
636
  this._discovery = new GatewayDiscovery({ verifySsl: this._configModel.verifySsl, logger: this._clientLog, net: dnsNet });
591
- const keystore = new FileKeyStore(this._configModel.aunPath, {
637
+ const tokenStore = new LocalTokenStore(this._configModel.aunPath, {
592
638
  logger: this._logger.for('aun_core.keystore'),
593
- secretStoreLogger: this._logger.for('aun_core.secret-store'),
594
639
  });
595
- this._keystore = keystore;
596
- // 启动时被动清理 registerAid 留下的孤儿临时目录(>10 分钟)
597
- try {
598
- const cleanup = keystore.cleanupPendingDirs;
599
- if (typeof cleanup === 'function') {
600
- const removed = cleanup.call(keystore, 600_000);
601
- if (removed > 0) {
602
- this._clientLog.info(`_pending cleanup removed=${removed}`);
603
- }
604
- }
605
- }
606
- catch (err) {
607
- this._clientLog.warn(`_pending cleanup failed: ${err instanceof Error ? err.message : String(err)}`);
608
- }
640
+ this._tokenStore = tokenStore;
609
641
  this._slotId = inputAid?.slotId || 'default';
610
642
  this._connectDeliveryMode = normalizeDeliveryModeConfig({ mode: 'fanout' });
611
643
  this._defaultConnectDeliveryMode = { ...this._connectDeliveryMode };
612
644
  this._auth = new AuthFlow({
613
- keystore,
645
+ tokenStore,
614
646
  crypto: new CryptoProvider(),
615
647
  aid: initAid,
616
648
  deviceId: this._deviceId,
@@ -621,6 +653,24 @@ export class AUNClient {
621
653
  net: dnsNet,
622
654
  });
623
655
  this._aid = initAid;
656
+ this._agentMdManager = createAgentMdManagerForRuntime({
657
+ config: () => this._configModel,
658
+ logger: () => this._logger.for('aun_core.agent_md'),
659
+ ownerAid: () => this._aid,
660
+ currentAid: () => this._currentAid,
661
+ gateway: {
662
+ resolve: (target) => this._resolveGatewayForAid(target),
663
+ get: () => this._gatewayUrl,
664
+ set: (gatewayUrl) => { this._gatewayUrl = gatewayUrl; },
665
+ },
666
+ identity: {
667
+ get: () => this._identity,
668
+ set: (identity) => { this._identity = identity; },
669
+ },
670
+ auth: () => this._auth,
671
+ tokenStore: () => this._tokenStore,
672
+ fetchPeerCert: (target) => this._fetchPeerCert(target),
673
+ });
624
674
  this._transport = new RPCTransport({
625
675
  eventDispatcher: this._dispatcher,
626
676
  timeout: 10_000,
@@ -640,6 +690,7 @@ export class AUNClient {
640
690
  public_key_der_b64: inputAid.publicKey,
641
691
  cert: inputAid.certPem,
642
692
  };
693
+ this._auth.setIdentity(this._identity);
643
694
  this._state = 'standby';
644
695
  }
645
696
  }
@@ -729,21 +780,17 @@ export class AUNClient {
729
780
  rawConfig.root_ca_path = aid.rootCaPath;
730
781
  const nextConfig = configFromMap(rawConfig);
731
782
  try {
732
- const close = this._keystore.close;
783
+ const close = this._tokenStore.close;
733
784
  if (typeof close === 'function')
734
- close.call(this._keystore);
785
+ close.call(this._tokenStore);
735
786
  }
736
787
  catch {
737
- // best-effort cleanup before switching keystore roots
788
+ // best-effort cleanup before switching tokenStore roots
738
789
  }
739
790
  this._configModel = nextConfig;
740
791
  this.config.aun_path = nextConfig.aunPath;
741
792
  this.config.root_ca_path = nextConfig.rootCaPath;
742
793
  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
794
  this._peerCache.clear();
748
795
  this._certCache.clear();
749
796
  this._gatewayUrl = null;
@@ -758,13 +805,12 @@ export class AUNClient {
758
805
  logger: this._clientLog,
759
806
  });
760
807
  this._discovery = new GatewayDiscovery({ verifySsl: nextConfig.verifySsl, logger: this._clientLog, net: dnsNet });
761
- const keystore = new FileKeyStore(nextConfig.aunPath, {
808
+ const tokenStore = new LocalTokenStore(nextConfig.aunPath, {
762
809
  logger: this._logger.for('aun_core.keystore'),
763
- secretStoreLogger: this._logger.for('aun_core.secret-store'),
764
810
  });
765
- this._keystore = keystore;
811
+ this._tokenStore = tokenStore;
766
812
  this._auth = new AuthFlow({
767
- keystore,
813
+ tokenStore,
768
814
  crypto: new CryptoProvider(),
769
815
  aid: aid.aid,
770
816
  deviceId: this._deviceId,
@@ -774,6 +820,24 @@ export class AUNClient {
774
820
  logger: this._logger.for('aun_core.auth'),
775
821
  net: dnsNet,
776
822
  });
823
+ this._agentMdManager = createAgentMdManagerForRuntime({
824
+ config: () => this._configModel,
825
+ logger: () => this._logger.for('aun_core.agent_md'),
826
+ ownerAid: () => this._aid,
827
+ currentAid: () => this._currentAid,
828
+ gateway: {
829
+ resolve: (target) => this._resolveGatewayForAid(target),
830
+ get: () => this._gatewayUrl,
831
+ set: (gatewayUrl) => { this._gatewayUrl = gatewayUrl; },
832
+ },
833
+ identity: {
834
+ get: () => this._identity,
835
+ set: (identity) => { this._identity = identity; },
836
+ },
837
+ auth: () => this._auth,
838
+ tokenStore: () => this._tokenStore,
839
+ fetchPeerCert: (target) => this._fetchPeerCert(target),
840
+ });
777
841
  this._transport = new RPCTransport({
778
842
  eventDispatcher: this._dispatcher,
779
843
  timeout: 10_000,
@@ -801,6 +865,8 @@ export class AUNClient {
801
865
  public_key_der_b64: aid.publicKey,
802
866
  cert: aid.certPem,
803
867
  };
868
+ // 注入内存私钥到 AuthFlow,禁止 AuthFlow 内部再走 keystore 解密
869
+ this._auth.setIdentity(this._identity);
804
870
  this._state = 'standby';
805
871
  this._closing = false;
806
872
  this._lastError = null;
@@ -858,772 +924,12 @@ export class AUNClient {
858
924
  throw new StateError('peers requires a loaded identity');
859
925
  return [...this._peerCache.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([, v]) => v);
860
926
  }
861
- async _resolveAgentMdUrl(aid) {
862
- const target = String(aid ?? '').trim();
863
- if (!target)
864
- throw new ValidationError('agent.md requires non-empty aid');
865
- let gatewayUrl = String(this._gatewayUrl ?? '').trim();
866
- if (!gatewayUrl) {
867
- try {
868
- gatewayUrl = await this._resolveGatewayForAid(target);
869
- }
870
- catch {
871
- gatewayUrl = '';
872
- }
873
- }
874
- return `${agentMdHttpScheme(gatewayUrl)}://${agentMdAuthority(target, this._configModel.discoveryPort)}/agent.md`;
875
- }
876
- async _ensureAgentMdUploadToken(aid, gatewayUrl) {
877
- let identity = this._auth.loadIdentityOrNone(aid);
878
- if (!identity && this._identity && String(this._identity.aid ?? '') === aid) {
879
- identity = this._identity;
880
- }
881
- if (!identity) {
882
- throw new StateError('no local identity found, register or load an AID first');
883
- }
884
- const cachedToken = String(identity.access_token ?? '');
885
- const expiresAt = this._auth.getAccessTokenExpiry(identity);
886
- if (cachedToken && (expiresAt === null || expiresAt > Date.now() / 1000 + 30)) {
887
- return cachedToken;
888
- }
889
- if (identity.refresh_token) {
890
- try {
891
- const refreshed = await this._auth.refreshCachedTokens(gatewayUrl, identity);
892
- const refreshedToken = String(refreshed.access_token ?? '');
893
- const refreshedExpiry = this._auth.getAccessTokenExpiry(refreshed);
894
- if (refreshedToken && (refreshedExpiry === null || refreshedExpiry > Date.now() / 1000 + 30)) {
895
- this._identity = refreshed;
896
- return refreshedToken;
897
- }
898
- }
899
- catch {
900
- // refresh 失败时回退到完整 authenticate。
901
- }
902
- }
903
- const result = await this._auth.authenticate(gatewayUrl, { aid });
904
- const token = String(result.access_token ?? '');
905
- if (!token)
906
- throw new StateError('authenticate did not return access_token');
907
- this._identity = this._auth.loadIdentityOrNone(aid) ?? {
908
- ...identity,
909
- access_token: token,
910
- refresh_token: String(result.refresh_token ?? identity.refresh_token ?? ''),
911
- access_token_expires_at: typeof result.expires_at === 'number' ? result.expires_at : identity.access_token_expires_at,
912
- token_exp: typeof result.expires_at === 'number' ? result.expires_at : identity.token_exp,
913
- expires_at: typeof result.expires_at === 'number' ? result.expires_at : identity.expires_at,
914
- };
915
- return token;
916
- }
917
- async _uploadAgentMd(content) {
918
- const target = String(this._aid ?? this._currentAid?.aid ?? '').trim();
919
- if (!target)
920
- throw new StateError('uploadAgentMd requires local AID');
921
- const gatewayUrl = await this._resolveGatewayForAid(target);
922
- this._gatewayUrl = gatewayUrl;
923
- const token = await this._ensureAgentMdUploadToken(target, gatewayUrl);
924
- const response = await fetchWithTimeout(await this._resolveAgentMdUrl(target), {
925
- method: 'PUT',
926
- headers: {
927
- Authorization: `Bearer ${token}`,
928
- 'Content-Type': 'text/markdown; charset=utf-8',
929
- },
930
- body: content,
931
- });
932
- if (response.status === 404) {
933
- throw new NotFoundError(`agent.md endpoint not found for aid: ${target}`);
934
- }
935
- if (!response.ok) {
936
- const message = (await response.text()).trim();
937
- throw new AUNError(`upload agent.md failed: HTTP ${response.status}${message ? ` - ${message}` : ''}`);
938
- }
939
- const payload = await response.json();
940
- if (!isJsonObject(payload))
941
- throw new AUNError('upload agent.md returned invalid JSON payload');
942
- return payload;
943
- }
944
- async _acquireAgentMdDownloadSlot() {
945
- if (this._agentMdDownloadActive < AUNClient.AGENT_MD_DOWNLOAD_CONCURRENCY) {
946
- this._agentMdDownloadActive += 1;
947
- return () => this._releaseAgentMdDownloadSlot();
948
- }
949
- await new Promise((resolve) => {
950
- this._agentMdDownloadWaiters.push(resolve);
951
- });
952
- return () => this._releaseAgentMdDownloadSlot();
953
- }
954
- _releaseAgentMdDownloadSlot() {
955
- const next = this._agentMdDownloadWaiters.shift();
956
- if (next) {
957
- next();
958
- return;
959
- }
960
- if (this._agentMdDownloadActive > 0)
961
- this._agentMdDownloadActive -= 1;
962
- }
963
- async _downloadAgentMd(aid) {
964
- const target = String(aid ?? '').trim();
965
- if (!target)
966
- throw new ValidationError('downloadAgentMd requires non-empty aid');
967
- const existing = this._agentMdDownloadInflight.get(target);
968
- if (existing)
969
- return await existing;
970
- const task = (async () => {
971
- const release = await this._acquireAgentMdDownloadSlot();
972
- try {
973
- return await this._downloadAgentMdOnce(target);
974
- }
975
- finally {
976
- release();
977
- }
978
- })();
979
- this._agentMdDownloadInflight.set(target, task);
980
- task.finally(() => {
981
- if (this._agentMdDownloadInflight.get(target) === task) {
982
- this._agentMdDownloadInflight.delete(target);
983
- }
984
- }).catch(() => undefined);
985
- return await task;
986
- }
987
- async _downloadAgentMdOnce(target) {
988
- const cached = this._agentMdCache.get(target);
989
- const url = await this._resolveAgentMdUrl(target);
990
- let response = await fetchWithTimeout(url, {
991
- method: 'GET',
992
- headers: { Accept: 'text/markdown' },
993
- redirect: 'follow',
994
- });
995
- if (response.status === 304 && typeof cached?.text === 'string') {
996
- return String(cached.text);
997
- }
998
- if (response.status === 304) {
999
- response = await fetchWithTimeout(url, {
1000
- method: 'GET',
1001
- headers: { Accept: 'text/markdown' },
1002
- cache: 'reload',
1003
- redirect: 'follow',
1004
- });
1005
- }
1006
- if (response.status === 404) {
1007
- throw new NotFoundError(`agent.md not found for aid: ${target}`);
1008
- }
1009
- if (!response.ok) {
1010
- const message = (await response.text()).trim();
1011
- throw new AUNError(`download agent.md failed: HTTP ${response.status}${message ? ` - ${message}` : ''}`);
1012
- }
1013
- const text = await response.text();
1014
- const etag = String(response.headers?.get('ETag') ?? response.headers?.get('etag') ?? '').trim();
1015
- const lastModified = String(response.headers?.get('Last-Modified') ?? response.headers?.get('last-modified') ?? '').trim();
1016
- this._agentMdCache.set(target, {
1017
- ...(cached ?? {}),
1018
- text,
1019
- etag,
1020
- lastModified,
1021
- remote_etag: etag,
1022
- last_modified: lastModified,
1023
- });
1024
- return text;
1025
- }
1026
- async _headAgentMd(aid) {
1027
- const target = String(aid ?? '').trim();
1028
- if (!target)
1029
- throw new ValidationError('headAgentMd requires non-empty aid');
1030
- const response = await fetchWithTimeout(await this._resolveAgentMdUrl(target), {
1031
- method: 'HEAD',
1032
- headers: { Accept: 'text/markdown' },
1033
- redirect: 'follow',
1034
- }, 15_000);
1035
- const cached = this._agentMdCache.get(target) ?? {};
1036
- const etag = String(response.headers?.get('ETag') ?? response.headers?.get('etag') ?? '').trim();
1037
- const lastModified = String(response.headers?.get('Last-Modified') ?? response.headers?.get('last-modified') ?? '').trim();
1038
- if (response.status === 404) {
1039
- return { aid: target, found: false, etag: '', last_modified: '', status: 404 };
1040
- }
1041
- const resultEtag = response.status === 304 ? (etag || String(cached.etag ?? cached.remote_etag ?? '')) : etag;
1042
- const resultLastModified = response.status === 304 ? (lastModified || String(cached.lastModified ?? cached.last_modified ?? '')) : lastModified;
1043
- if (response.status < 200 || (response.status >= 300 && response.status !== 304)) {
1044
- throw new AUNError(`head agent.md failed: HTTP ${response.status}`);
1045
- }
1046
- this._agentMdCache.set(target, {
1047
- ...cached,
1048
- etag: resultEtag,
1049
- lastModified: resultLastModified,
1050
- remote_etag: resultEtag,
1051
- last_modified: resultLastModified,
1052
- });
1053
- return { aid: target, found: true, etag: resultEtag, last_modified: resultLastModified, status: response.status };
1054
- }
1055
- async _verifyAgentMd(content, aid, certPem) {
1056
- const target = String(aid ?? '').trim();
1057
- if (!target)
1058
- throw new ValidationError('verifyAgentMd requires non-empty aid');
1059
- let peer = target === this._currentAid?.aid ? this._currentAid : null;
1060
- if (!peer) {
1061
- let resolvedCert = String(certPem ?? '').trim();
1062
- if (!resolvedCert) {
1063
- try {
1064
- resolvedCert = String(this._keystore.loadCert(target) ?? '').trim();
1065
- }
1066
- catch {
1067
- resolvedCert = '';
1068
- }
1069
- }
1070
- if (!resolvedCert) {
1071
- if (!this._gatewayUrl) {
1072
- try {
1073
- this._gatewayUrl = await this._resolveGatewayForAid(target);
1074
- }
1075
- catch { /* best-effort before cert fetch */ }
1076
- }
1077
- resolvedCert = String(await this._fetchPeerCert(target) ?? '').trim();
1078
- }
1079
- if (!resolvedCert)
1080
- throw new NotFoundError(`certificate not found for aid: ${target}`);
1081
- peer = AID._create({
1082
- aid: target,
1083
- aunPath: this._configModel.aunPath,
1084
- certPem: resolvedCert,
1085
- privateKeyPem: null,
1086
- certValid: true,
1087
- privateKeyValid: false,
1088
- });
1089
- }
1090
- const result = peer.verifyAgentMd(content);
1091
- if (!result.ok)
1092
- throw new AUNError(result.error.message);
1093
- const vd = result.data;
1094
- return { ...vd, verified: vd.status === 'verified' };
1095
- }
1096
- /**
1097
- * 读取 {agentMdPath}/{self_aid}/agent.md,签名后上传,并把签名结果原子写回本地。
1098
- */
1099
- async publishAgentMd() {
1100
- const target = this._agentMdOwnerAid();
1101
- if (!target || !this._currentAid) {
1102
- throw new ValidationError('publishAgentMd requires local AID');
1103
- }
1104
- const content = this._readAgentMdContent(target);
1105
- const signed = this._currentAid?.signAgentMd(content);
1106
- if (!signed?.ok) {
1107
- throw new StateError(signed?.error.message ?? 'publishAgentMd requires a valid local AID private key');
1108
- }
1109
- const signedContent = signed.data.signed;
1110
- const result = await this._uploadAgentMd(signedContent);
1111
- this._localAgentMdEtag = this._agentMdContentEtag(signedContent);
1112
- const remoteEtag = isJsonObject(result) ? String(result.etag ?? '').trim() : '';
1113
- if (remoteEtag)
1114
- this._remoteAgentMdEtag = remoteEtag;
1115
- this._saveAgentMdRecord(target, {
1116
- content: signedContent,
1117
- local_etag: this._localAgentMdEtag,
1118
- remote_etag: remoteEtag || undefined,
1119
- last_modified: isJsonObject(result) ? String(result.last_modified ?? '').trim() : '',
1120
- fetched_at: Date.now(),
1121
- remote_status: remoteEtag ? 'found' : 'unknown',
1122
- last_error: '',
1123
- });
1124
- return result;
1125
- }
1126
- async _startAgentMdFetchTask(target) {
1127
- const existing = this._agentMdFetchInflight.get(target);
1128
- if (existing) {
1129
- return await existing;
1130
- }
1131
- const task = this._fetchAgentMdOnce(target);
1132
- this._agentMdFetchInflight.set(target, task);
1133
- task.finally(() => {
1134
- if (this._agentMdFetchInflight.get(target) === task) {
1135
- this._agentMdFetchInflight.delete(target);
1136
- }
1137
- }).catch(() => undefined);
1138
- return await task;
1139
- }
1140
- async _fetchAgentMdOnce(target) {
1141
- const content = await this._downloadAgentMd(target);
1142
- const signature = await this._verifyAgentMd(content, target);
1143
- const isSelf = target === (this._aid ?? '');
1144
- const localEtag = this._agentMdContentEtag(content);
1145
- const cacheMeta = this._agentMdAuthCacheMeta(target);
1146
- const remoteEtag = String(cacheMeta.etag ?? '').trim();
1147
- const lastModified = String(cacheMeta.lastModified ?? cacheMeta.last_modified ?? '').trim();
1148
- if (isSelf) {
1149
- this._localAgentMdEtag = localEtag;
1150
- if (remoteEtag)
1151
- this._remoteAgentMdEtag = remoteEtag;
1152
- }
1153
- const saved = this._saveAgentMdRecord(target, {
1154
- content,
1155
- local_etag: localEtag,
1156
- remote_etag: remoteEtag || undefined,
1157
- last_modified: lastModified || undefined,
1158
- fetched_at: Date.now(),
1159
- remote_status: 'found',
1160
- verify_status: isJsonObject(signature) ? String(signature.status ?? '') : '',
1161
- verify_error: isJsonObject(signature) ? String(signature.reason ?? '') : '',
1162
- last_error: '',
1163
- });
1164
- let inSync = null;
1165
- if (isSelf) {
1166
- const remote = remoteEtag || this._remoteAgentMdEtag || '';
1167
- inSync = localEtag && remote ? localEtag === remote : false;
1168
- }
1169
- return {
1170
- aid: target,
1171
- content,
1172
- signature: signature,
1173
- in_sync: inSync,
1174
- saved_to: String(saved.saved_to ?? this._agentMdFilePath(target)),
1175
- save_error: null,
1176
- };
1177
- }
1178
- /**
1179
- * 设置 agent.md 本地存储根目录;为空时恢复默认 {aun_path}/AIDs。
1180
- */
1181
- _setAgentMdRoot(root) {
1182
- const raw = String(root ?? '').trim();
1183
- const next = raw || path.join(this._configModel.aunPath, 'AIDs');
1184
- fs.mkdirSync(next, { recursive: true });
1185
- this._agentMdPath = next;
1186
- this._agentMdCache.clear();
1187
- return this._agentMdPath;
1188
- }
1189
- /** 返回本地 agent.md 文件的 etag;未设置或读取失败时返回空串。 */
1190
- getLocalAgentMdEtag() {
1191
- return this._localAgentMdEtag;
1192
- }
1193
- /**
1194
- * 返回 gateway 在最近一次 RPC envelope._meta 注入的服务端 agent.md etag。
1195
- *
1196
- * 未收到过则为空串;不阻塞调用,纯内存读。
1197
- */
1198
- getRemoteAgentMdEtag() {
1199
- return this._remoteAgentMdEtag;
1200
- }
1201
- _agentMdContentEtag(content) {
1202
- return `"${crypto.createHash('sha256').update(String(content ?? ''), 'utf-8').digest('hex')}"`;
1203
- }
1204
- _agentMdOwnerAid() {
1205
- return String(this._aid ?? '').trim();
1206
- }
1207
- _agentMdSafeAid(aid) {
1208
- const target = String(aid ?? '').trim();
1209
- if (!target || target.includes('/') || target.includes('\\') || target.includes('\0')) {
1210
- throw new ValidationError('agent.md aid is empty or contains path separators');
1211
- }
1212
- return target;
1213
- }
1214
- _agentMdRoot() {
1215
- const root = this._agentMdPath || path.join(this._configModel.aunPath, 'AIDs');
1216
- fs.mkdirSync(root, { recursive: true });
1217
- return root;
1218
- }
1219
- _agentMdFilePath(aid) {
1220
- return path.join(this._agentMdRoot(), this._agentMdSafeAid(aid), 'agent.md');
1221
- }
1222
- _agentMdMetaPath(aid) {
1223
- return path.join(this._agentMdRoot(), this._agentMdSafeAid(aid), 'agentmd.json');
1224
- }
1225
- _atomicWriteText(filePath, content) {
1226
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
1227
- const tmp = path.join(path.dirname(filePath), `.${path.basename(filePath)}.${process.pid}.${crypto.randomUUID()}.tmp`);
1228
- let fd = null;
1229
- try {
1230
- fd = fs.openSync(tmp, 'w');
1231
- fs.writeFileSync(fd, content, 'utf-8');
1232
- fs.fsyncSync(fd);
1233
- fs.closeSync(fd);
1234
- fd = null;
1235
- fs.renameSync(tmp, filePath);
1236
- try {
1237
- const dirFd = fs.openSync(path.dirname(filePath), 'r');
1238
- try {
1239
- fs.fsyncSync(dirFd);
1240
- }
1241
- finally {
1242
- fs.closeSync(dirFd);
1243
- }
1244
- }
1245
- catch { /* best effort */ }
1246
- }
1247
- finally {
1248
- if (fd !== null) {
1249
- try {
1250
- fs.closeSync(fd);
1251
- }
1252
- catch { /* ignore */ }
1253
- }
1254
- if (fs.existsSync(tmp)) {
1255
- try {
1256
- fs.unlinkSync(tmp);
1257
- }
1258
- catch { /* ignore */ }
1259
- }
1260
- }
1261
- }
1262
- _sleepSync(ms) {
1263
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
1264
- }
1265
- _withAgentMdRecordLock(aid, fn) {
1266
- const lockPath = path.join(path.dirname(this._agentMdMetaPath(aid)), 'agentmd.json.lock');
1267
- fs.mkdirSync(path.dirname(lockPath), { recursive: true });
1268
- const deadline = Date.now() + 5000;
1269
- let fd = null;
1270
- while (fd === null) {
1271
- try {
1272
- fd = fs.openSync(lockPath, 'wx');
1273
- fs.writeFileSync(fd, `${process.pid}\n`, 'utf-8');
1274
- }
1275
- catch (err) {
1276
- if (err?.code !== 'EEXIST' || Date.now() >= deadline)
1277
- throw err;
1278
- try {
1279
- const st = fs.statSync(lockPath);
1280
- if (Date.now() - st.mtimeMs > 30000)
1281
- fs.unlinkSync(lockPath);
1282
- }
1283
- catch { /* ignore */ }
1284
- this._sleepSync(25);
1285
- }
1286
- }
1287
- try {
1288
- return fn();
1289
- }
1290
- finally {
1291
- if (fd !== null) {
1292
- try {
1293
- fs.closeSync(fd);
1294
- }
1295
- catch { /* ignore */ }
1296
- }
1297
- try {
1298
- fs.unlinkSync(lockPath);
1299
- }
1300
- catch { /* ignore */ }
1301
- }
1302
- }
1303
- _writeAgentMdRecordUnlocked(aid, record) {
1304
- const payload = {};
1305
- for (const [key, value] of Object.entries(record)) {
1306
- if (key !== 'content' && value !== undefined && value !== null)
1307
- payload[key] = value;
1308
- }
1309
- payload.aid = this._agentMdSafeAid(aid);
1310
- this._atomicWriteText(this._agentMdMetaPath(aid), `${JSON.stringify(payload, null, 2)}\n`);
1311
- }
1312
- _normalizeAgentMdRecord(aid, data) {
1313
- if (!isJsonObject(data))
1314
- return {};
1315
- const record = {};
1316
- for (const [key, value] of Object.entries(data)) {
1317
- if (key !== 'content')
1318
- record[key] = value;
1319
- }
1320
- record.aid = this._agentMdSafeAid(String(record.aid ?? aid));
1321
- for (const key of ['fetched_at', 'observed_at', 'checked_at', 'updated_at']) {
1322
- record[key] = Number(record[key] ?? 0) || 0;
1323
- }
1324
- return record;
1325
- }
1326
- _readAgentMdRecordUnlocked(aid) {
1327
- const filePath = this._agentMdMetaPath(aid);
1328
- if (!fs.existsSync(filePath))
1329
- return {};
1330
- try {
1331
- return this._normalizeAgentMdRecord(aid, JSON.parse(fs.readFileSync(filePath, 'utf-8')));
1332
- }
1333
- catch (err) {
1334
- this._clientLog.warn(`agent.md metadata damaged, ignoring: aid=${aid} err=${err instanceof Error ? err.message : String(err)}`);
1335
- return {};
1336
- }
1337
- }
1338
- _readAgentMdContent(aid) {
1339
- return fs.readFileSync(this._agentMdFilePath(aid), 'utf-8');
1340
- }
1341
- _writeAgentMdContent(aid, content) {
1342
- const filePath = this._agentMdFilePath(aid);
1343
- this._atomicWriteText(filePath, String(content ?? ''));
1344
- return filePath;
1345
- }
1346
- _agentMdAuthCacheMeta(aid) {
1347
- try {
1348
- const record = this._agentMdCache.get(String(aid ?? '').trim());
1349
- return record && typeof record === 'object' ? { ...record } : {};
1350
- }
1351
- catch {
1352
- return {};
1353
- }
1354
- }
1355
- _loadAgentMdRecord(aid) {
1356
- const target = String(aid ?? '').trim();
1357
- if (!target)
1358
- return null;
1359
- try {
1360
- const loaded = this._withAgentMdRecordLock(target, () => {
1361
- const record = this._readAgentMdRecordUnlocked(target);
1362
- const next = Object.keys(record).length > 0 ? { ...record, aid: target } : { aid: target };
1363
- try {
1364
- const content = this._readAgentMdContent(target);
1365
- next.content = content;
1366
- next.local_etag = this._agentMdContentEtag(content);
1367
- }
1368
- catch (err) {
1369
- if (fs.existsSync(this._agentMdMetaPath(target))) {
1370
- this._clientLog.warn(`agent.md content read failed: aid=${target} err=${err instanceof Error ? err.message : String(err)}`);
1371
- }
1372
- }
1373
- return next;
1374
- });
1375
- if (Object.keys(loaded).length <= 1)
1376
- return null;
1377
- this._agentMdCache.set(target, { ...loaded });
1378
- return { ...loaded };
1379
- }
1380
- catch (err) {
1381
- this._clientLog.debug(`agent.md cache load skipped: aid=${target} err=${err instanceof Error ? err.message : String(err)}`);
1382
- }
1383
- return null;
1384
- }
1385
- _saveAgentMdRecord(aid, fields) {
1386
- const target = String(aid ?? '').trim();
1387
- if (!target)
1388
- return {};
1389
- try {
1390
- const inputFields = { ...fields };
1391
- const hasContent = Object.prototype.hasOwnProperty.call(inputFields, 'content') && inputFields.content !== undefined && inputFields.content !== null;
1392
- let savedTo = '';
1393
- const record = this._withAgentMdRecordLock(target, () => {
1394
- if (hasContent) {
1395
- const content = String(inputFields.content ?? '');
1396
- savedTo = this._writeAgentMdContent(target, content);
1397
- if (!inputFields.local_etag)
1398
- inputFields.local_etag = this._agentMdContentEtag(content);
1399
- if (!inputFields.fetched_at)
1400
- inputFields.fetched_at = Date.now();
1401
- }
1402
- delete inputFields.content;
1403
- const next = { ...this._readAgentMdRecordUnlocked(target), aid: target };
1404
- for (const [key, value] of Object.entries(inputFields)) {
1405
- if (value !== undefined && value !== null)
1406
- next[key] = value;
1407
- }
1408
- next.updated_at = Date.now();
1409
- this._writeAgentMdRecordUnlocked(target, next);
1410
- return next;
1411
- });
1412
- const loaded = { ...record };
1413
- if (hasContent) {
1414
- loaded.content = String(fields.content ?? '');
1415
- if (savedTo)
1416
- loaded.saved_to = savedTo;
1417
- }
1418
- this._agentMdCache.set(target, { ...loaded });
1419
- const owner = this._agentMdOwnerAid();
1420
- if (target === owner) {
1421
- const localEtag = String(loaded.local_etag ?? '').trim();
1422
- const remoteEtag = String(loaded.remote_etag ?? '').trim();
1423
- if (localEtag)
1424
- this._localAgentMdEtag = localEtag;
1425
- if (remoteEtag)
1426
- this._remoteAgentMdEtag = remoteEtag;
1427
- }
1428
- return { ...loaded };
1429
- }
1430
- catch (err) {
1431
- this._clientLog.debug(`agent.md cache save skipped: aid=${target} err=${err instanceof Error ? err.message : String(err)}`);
1432
- }
1433
- return {};
1434
- }
1435
- _agentMdHasLocalContent(aid, record) {
1436
- if (record && typeof record.content === 'string' && record.content.length > 0)
1437
- return true;
1438
- try {
1439
- return fs.existsSync(this._agentMdFilePath(aid));
1440
- }
1441
- catch {
1442
- return false;
1443
- }
1444
- }
1445
- _agentMdCheckedAtFresh(checkedAtMs, maxUnsyncedDays) {
1446
- const days = Number(maxUnsyncedDays || 0);
1447
- if (!Number.isFinite(days) || days <= 0)
1448
- return false;
1449
- if (!Number.isFinite(checkedAtMs) || checkedAtMs <= 0)
1450
- return false;
1451
- return Date.now() - checkedAtMs <= days * 86400000;
1452
- }
1453
- _agentMdLastModifiedFresh(lastModified, maxUnsyncedDays) {
1454
- const days = Number(maxUnsyncedDays || 0);
1455
- if (!Number.isFinite(days) || days <= 0)
1456
- return false;
1457
- const ts = Date.parse(String(lastModified ?? '').trim());
1458
- if (!Number.isFinite(ts))
1459
- return false;
1460
- return Date.now() <= ts + days * 86400000;
1461
- }
1462
- _scheduleAgentMdFetchIfMissing(aid, record, source = '') {
1463
- const target = String(aid ?? '').trim();
1464
- if (!target || this._agentMdHasLocalContent(target, record))
1465
- return;
1466
- if (this._agentMdFetchInflight.has(target))
1467
- return;
1468
- void this._startAgentMdFetchTask(target).catch((err) => {
1469
- this._saveAgentMdRecord(target, {
1470
- last_error: err instanceof Error ? err.message : String(err),
1471
- remote_status: 'found',
1472
- });
1473
- this._clientLog.debug(`agent.md auto fetch failed: aid=${target} source=${source || '-'} err=${err instanceof Error ? err.message : String(err)}`);
1474
- });
1475
- }
1476
- _observeAgentMdMeta(aid, etag = '', lastModified = '', source = '') {
1477
- const target = String(aid ?? '').trim();
1478
- const remoteEtag = String(etag ?? '').trim();
1479
- const remoteLastModified = String(lastModified ?? '').trim();
1480
- if (!target || (!remoteEtag && !remoteLastModified))
1481
- return;
1482
- let before = this._agentMdCache.get(target);
1483
- if (!before || typeof before !== 'object')
1484
- before = this._loadAgentMdRecord(target) ?? {};
1485
- const same = (!remoteEtag || String(before.remote_etag ?? '').trim() === remoteEtag) &&
1486
- (!remoteLastModified || String(before.last_modified ?? '').trim() === remoteLastModified);
1487
- let record = { ...before };
1488
- if (!same || Object.keys(before).length === 0) {
1489
- const fields = {
1490
- observed_at: Date.now(),
1491
- remote_status: 'found',
1492
- };
1493
- if (remoteEtag)
1494
- fields.remote_etag = remoteEtag;
1495
- if (remoteLastModified)
1496
- fields.last_modified = remoteLastModified;
1497
- record = this._saveAgentMdRecord(target, fields) || record;
1498
- }
1499
- if (target === this._agentMdOwnerAid() && remoteEtag)
1500
- this._remoteAgentMdEtag = remoteEtag;
1501
- this._scheduleAgentMdFetchIfMissing(target, record, source);
1502
- this._clientLog.debug(`agent.md meta observed: aid=${target} etag=${remoteEtag || '-'} last_modified=${remoteLastModified || '-'} source=${source || '-'}`);
1503
- }
1504
- _observeAgentMdEtag(aid, etag, source = '') {
1505
- this._observeAgentMdMeta(aid, etag, '', source);
1506
- }
1507
- _observeAgentMdFromEnvelope(envelope) {
1508
- if (!isJsonObject(envelope))
1509
- return;
1510
- const env = envelope;
1511
- if (!isJsonObject(env.agent_md))
1512
- return;
1513
- const agentMd = env.agent_md;
1514
- if (!isJsonObject(agentMd.sender))
1515
- return;
1516
- const sender = agentMd.sender;
1517
- let senderAid = String(sender.aid ?? '').trim();
1518
- if (!senderAid) {
1519
- const aad = isJsonObject(env.aad) ? env.aad : {};
1520
- senderAid = String(aad.from ?? env.from ?? '').trim();
1521
- }
1522
- this._observeAgentMdMeta(senderAid, String(sender.etag ?? '').trim(), String(sender.last_modified ?? sender.lastModified ?? '').trim(), 'envelope');
1523
- }
1524
- async _checkAgentMdCache(aid, maxUnsyncedDays = 1) {
1525
- const target = String(aid ?? this._aid ?? '').trim();
1526
- if (!target)
1527
- throw new ValidationError('checkAgentMd requires aid (or local AID)');
1528
- const before = this._loadAgentMdRecord(target) ?? {};
1529
- const localEtag = String(before.local_etag ?? '').trim();
1530
- const localFound = !!(Object.keys(before).length > 0 && (String(before.content ?? '') || localEtag));
1531
- const remoteEtagCached = String(before.remote_etag ?? '').trim();
1532
- const lastModifiedCached = String(before.last_modified ?? '').trim();
1533
- const checkedAt = Number(before.checked_at ?? 0);
1534
- const fetchedAt = Number(before.fetched_at ?? 0);
1535
- const checkedAtCached = checkedAt > 0 ? checkedAt : fetchedAt;
1536
- const cachedInSync = !!(localFound && localEtag && remoteEtagCached && localEtag === remoteEtagCached);
1537
- // max_unsynced_days > 0 且距上次 HEAD 在窗口内 → 直接返回缓存;否则强制 HEAD。
1538
- if (cachedInSync && this._agentMdCheckedAtFresh(checkedAtCached, maxUnsyncedDays)) {
1539
- return {
1540
- aid: target,
1541
- local_found: true,
1542
- remote_found: true,
1543
- local_etag: localEtag,
1544
- remote_etag: remoteEtagCached,
1545
- in_sync: true,
1546
- last_modified: lastModifiedCached,
1547
- status: 200,
1548
- cached: true,
1549
- verify_status: String(before.verify_status ?? ''),
1550
- verify_error: String(before.verify_error ?? ''),
1551
- };
1552
- }
1553
- const remoteFoundCached = !!(remoteEtagCached || String(before.remote_status ?? '') === 'found');
1554
- if (!localFound &&
1555
- !remoteFoundCached &&
1556
- String(before.remote_status ?? '') === 'missing' &&
1557
- this._agentMdCheckedAtFresh(checkedAtCached, maxUnsyncedDays)) {
1558
- return {
1559
- aid: target,
1560
- local_found: false,
1561
- remote_found: false,
1562
- local_etag: '',
1563
- remote_etag: '',
1564
- in_sync: false,
1565
- last_modified: '',
1566
- status: 404,
1567
- cached: true,
1568
- verify_status: '',
1569
- verify_error: '',
1570
- };
1571
- }
1572
- const now = Date.now();
1573
- let remote;
1574
- try {
1575
- remote = await this._headAgentMd(target);
1576
- }
1577
- catch (err) {
1578
- this._saveAgentMdRecord(target, { checked_at: now, remote_status: 'error', last_error: err instanceof Error ? err.message : String(err) });
1579
- throw err;
1580
- }
1581
- const remoteFound = !!remote.found;
1582
- const remoteEtag = String(remote.etag ?? '').trim();
1583
- const lastModified = String(remote.last_modified ?? remote.lastModified ?? '').trim();
1584
- const saved = this._saveAgentMdRecord(target, {
1585
- remote_etag: remoteFound ? remoteEtag : '',
1586
- last_modified: lastModified,
1587
- checked_at: now,
1588
- remote_status: remoteFound ? 'found' : 'missing',
1589
- last_error: '',
1590
- });
1591
- if (target === this._agentMdOwnerAid() && remoteEtag)
1592
- this._remoteAgentMdEtag = remoteEtag;
1593
- const inSync = !!(localFound && remoteFound && localEtag && remoteEtag && localEtag === remoteEtag);
1594
- return {
1595
- aid: target,
1596
- local_found: localFound,
1597
- remote_found: remoteFound,
1598
- local_etag: localEtag,
1599
- remote_etag: remoteEtag,
1600
- in_sync: inSync,
1601
- last_modified: lastModified,
1602
- status: Number(remote.status ?? (remoteFound ? 200 : 404)),
1603
- cached: false,
1604
- verify_status: String(saved.verify_status ?? before.verify_status ?? ''),
1605
- verify_error: String(saved.verify_error ?? before.verify_error ?? ''),
1606
- };
927
+ async uploadAgentMd(content) {
928
+ return await this._agentMdManager.upload(content);
1607
929
  }
1608
930
  /** transport 的 meta observer:吸收 gateway 注入的 _meta 字段。失败不影响业务。 */
1609
931
  _observeRpcMeta(meta) {
1610
- if (!meta || typeof meta !== 'object')
1611
- return;
1612
- const etag = String(meta.agent_md_etag ?? '').trim();
1613
- if (etag) {
1614
- this._remoteAgentMdEtag = etag;
1615
- this._observeAgentMdMeta(this._aid ?? '', etag, '', 'rpc.self');
1616
- }
1617
- const etags = meta.agent_md_etags;
1618
- if (isJsonObject(etags)) {
1619
- // role key 优先级:requester / peer 是新规范,其余是兼容旧 SDK 的别名。
1620
- for (const key of ['requester', 'peer', 'receiver', 'target', 'to', 'sender', 'from']) {
1621
- const item = etags[key];
1622
- if (!isJsonObject(item))
1623
- continue;
1624
- this._observeAgentMdMeta(String(item.aid ?? ''), String(item.etag ?? ''), String(item.last_modified ?? item.lastModified ?? ''), `rpc.${key}`);
1625
- }
1626
- }
932
+ this._agentMdManager.observeRpcMeta(meta, this._aid);
1627
933
  }
1628
934
  /** 连接状态 */
1629
935
  get state() {
@@ -1781,8 +1087,8 @@ export class AUNClient {
1781
1087
  this._stopBackgroundTasks();
1782
1088
  this._stopReconnect();
1783
1089
  if (this.state === ConnectionState.NO_IDENTITY || this.state === ConnectionState.CLOSED) {
1784
- const closableKeyStore = this._keystore;
1785
- closableKeyStore.close?.();
1090
+ const closableStore = this._tokenStore;
1091
+ closableStore.close?.();
1786
1092
  this._state = 'closed';
1787
1093
  this._logger.close();
1788
1094
  this._resetSeqTrackingState();
@@ -1790,8 +1096,8 @@ export class AUNClient {
1790
1096
  return;
1791
1097
  }
1792
1098
  await this._transport.close();
1793
- const closableKeyStore = this._keystore;
1794
- closableKeyStore.close?.();
1099
+ const closableStore = this._tokenStore;
1100
+ closableStore.close?.();
1795
1101
  this._state = 'closed';
1796
1102
  this._logger.close();
1797
1103
  await this._dispatcher.publish('state_change', { state: this._publicState(this._state) });
@@ -2548,15 +1854,11 @@ export class AUNClient {
2548
1854
  // 注入本地/远端 agent.md etag,让应用层判断版本一致性;失败不影响业务。
2549
1855
  if (isJsonObject(payload)) {
2550
1856
  try {
2551
- const localEtag = this._localAgentMdEtag || '';
2552
- const remoteEtag = this._remoteAgentMdEtag || '';
2553
- if (localEtag || remoteEtag) {
1857
+ const snapshot = this._agentMdManager.eventSnapshot();
1858
+ if (snapshot) {
2554
1859
  const obj = payload;
2555
1860
  if (!('_agent_md' in obj)) {
2556
- obj._agent_md = {
2557
- local_etag: localEtag,
2558
- remote_etag: remoteEtag,
2559
- };
1861
+ obj._agent_md = snapshot;
2560
1862
  }
2561
1863
  }
2562
1864
  }
@@ -3369,8 +2671,8 @@ export class AUNClient {
3369
2671
  const membershipSnapshot = String(d.membership_snapshot ?? '').trim();
3370
2672
  const policySnapshot = String(d.policy_snapshot ?? '').trim();
3371
2673
  // 1. 验证 prev_state_hash 连续性
3372
- const loadFn = this._keystore.loadGroupState;
3373
- const localState = loadFn ? loadFn.call(this._keystore, groupId) : null;
2674
+ const loadFn = this._tokenStore.loadGroupState;
2675
+ const localState = loadFn ? loadFn.call(this._tokenStore, groupId) : null;
3374
2676
  if (localState && localState.state_hash && localState.state_hash !== prevStateHash) {
3375
2677
  this._clientLog.warn(`state_hash chain discontinuous group=${groupId} local_sv=${localState.state_version} event_sv=${stateVersion}`);
3376
2678
  // 回源同步
@@ -3396,9 +2698,9 @@ export class AUNClient {
3396
2698
  return;
3397
2699
  }
3398
2700
  }
3399
- const saveFn = this._keystore.saveGroupState;
2701
+ const saveFn = this._tokenStore.saveGroupState;
3400
2702
  if (saveFn) {
3401
- saveFn.call(this._keystore, groupId, sv, sHash, sEpoch, sMembersJson || membershipSnapshot, sPolicyJson || policySnapshot);
2703
+ saveFn.call(this._tokenStore, groupId, sv, sHash, sEpoch, sMembersJson || membershipSnapshot, sPolicyJson || policySnapshot);
3402
2704
  }
3403
2705
  }
3404
2706
  }
@@ -3419,9 +2721,9 @@ export class AUNClient {
3419
2721
  return;
3420
2722
  }
3421
2723
  // 3. 更新本地存储
3422
- const saveFn = this._keystore.saveGroupState;
2724
+ const saveFn = this._tokenStore.saveGroupState;
3423
2725
  if (saveFn) {
3424
- saveFn.call(this._keystore, groupId, stateVersion, stateHash, keyEpoch, membershipSnapshot, policySnapshot);
2726
+ saveFn.call(this._tokenStore, groupId, stateVersion, stateHash, keyEpoch, membershipSnapshot, policySnapshot);
3425
2727
  }
3426
2728
  this._clientLog.debug(`_onGroupStateCommitted exit: elapsed=${Date.now() - tStart}ms group=${groupId}`);
3427
2729
  }
@@ -3581,7 +2883,7 @@ export class AUNClient {
3581
2883
  }
3582
2884
  try {
3583
2885
  // peer 证书只存版本目录,不覆盖 cert.pem
3584
- this._keystore.saveCert(aid, certPem, certFingerprint, { makeActive: false });
2886
+ this._tokenStore.saveCert(aid, certPem, certFingerprint, { makeActive: false });
3585
2887
  }
3586
2888
  catch (exc) {
3587
2889
  this._clientLog.error(`failed to write cert to keystore (aid=${aid}, fp=${certFingerprint ?? ''}): ${formatCaughtError(exc)}`, exc instanceof Error ? exc : undefined);
@@ -3852,9 +3154,9 @@ export class AUNClient {
3852
3154
  return;
3853
3155
  try {
3854
3156
  // 优先从 seq_tracker 表按行读取
3855
- const loadAll = this._keystore.loadAllSeqs;
3157
+ const loadAll = this._tokenStore.loadAllSeqs;
3856
3158
  if (typeof loadAll === 'function') {
3857
- let state = loadAll.call(this._keystore, this._aid, this._deviceId, this._slotId);
3159
+ let state = loadAll.call(this._tokenStore, this._aid, this._deviceId, this._slotId);
3858
3160
  if (state && Object.keys(state).length > 0) {
3859
3161
  state = this._migrateSeqStateGroupIds(state);
3860
3162
  this._seqTracker.restoreState(state);
@@ -3862,9 +3164,9 @@ export class AUNClient {
3862
3164
  }
3863
3165
  }
3864
3166
  // fallback: 从旧 instance_state JSON blob 恢复
3865
- const loader = this._keystore.loadInstanceState;
3167
+ const loader = this._tokenStore.loadInstanceState;
3866
3168
  if (typeof loader === 'function') {
3867
- const instanceState = loader.call(this._keystore, this._aid, this._deviceId, this._slotId);
3169
+ const instanceState = loader.call(this._tokenStore, this._aid, this._deviceId, this._slotId);
3868
3170
  if (instanceState && typeof instanceState.seq_tracker_state === 'object') {
3869
3171
  let state = instanceState.seq_tracker_state;
3870
3172
  state = this._migrateSeqStateGroupIds(state);
@@ -3915,20 +3217,20 @@ export class AUNClient {
3915
3217
  }
3916
3218
  this._clientLog.info(`SeqTracker group_id migration: ${Object.keys(renameMap).length} namespaces rewritten`);
3917
3219
  // 落盘
3918
- const saver = this._keystore.saveSeq;
3919
- const deleter = this._keystore.deleteSeq;
3220
+ const saver = this._tokenStore.saveSeq;
3221
+ const deleter = this._tokenStore.deleteSeq;
3920
3222
  if (typeof saver === 'function' && this._aid) {
3921
3223
  for (const [oldNs, newNs] of Object.entries(renameMap)) {
3922
3224
  if (typeof deleter === 'function') {
3923
3225
  try {
3924
- deleter.call(this._keystore, this._aid, this._deviceId, this._slotId, oldNs);
3226
+ deleter.call(this._tokenStore, this._aid, this._deviceId, this._slotId, oldNs);
3925
3227
  }
3926
3228
  catch (e) {
3927
3229
  this._clientLog.debug(`delete old seq ns failed: ns=${oldNs} err=${formatCaughtError(e)}`);
3928
3230
  }
3929
3231
  }
3930
3232
  try {
3931
- saver.call(this._keystore, this._aid, this._deviceId, this._slotId, newNs, newState[newNs]);
3233
+ saver.call(this._tokenStore, this._aid, this._deviceId, this._slotId, newNs, newState[newNs]);
3932
3234
  }
3933
3235
  catch (e) {
3934
3236
  this._clientLog.debug(`write new seq ns failed: ns=${newNs} err=${formatCaughtError(e)}`);
@@ -3976,17 +3278,17 @@ export class AUNClient {
3976
3278
  return;
3977
3279
  try {
3978
3280
  // 优先按行写入 seq_tracker 表
3979
- const saveFn = this._keystore.saveSeq;
3281
+ const saveFn = this._tokenStore.saveSeq;
3980
3282
  if (typeof saveFn === 'function') {
3981
3283
  for (const [ns, seq] of Object.entries(state)) {
3982
- saveFn.call(this._keystore, this._aid, this._deviceId, this._slotId, ns, seq);
3284
+ saveFn.call(this._tokenStore, this._aid, this._deviceId, this._slotId, ns, seq);
3983
3285
  }
3984
3286
  return;
3985
3287
  }
3986
3288
  // fallback: 旧版 updateInstanceState JSON blob
3987
- const updater = this._keystore.updateInstanceState;
3289
+ const updater = this._tokenStore.updateInstanceState;
3988
3290
  if (typeof updater === 'function') {
3989
- updater.call(this._keystore, this._aid, this._deviceId, this._slotId, (metadata) => {
3291
+ updater.call(this._tokenStore, this._aid, this._deviceId, this._slotId, (metadata) => {
3990
3292
  metadata.seq_tracker_state = state;
3991
3293
  return metadata;
3992
3294
  });
@@ -4009,13 +3311,13 @@ export class AUNClient {
4009
3311
  return;
4010
3312
  const seq = this._seqTracker.getContiguousSeq(ns);
4011
3313
  try {
4012
- if (seq > 0 && typeof this._keystore.saveSeq === 'function') {
4013
- this._keystore.saveSeq(this._aid, this._deviceId, this._slotId, ns, seq);
3314
+ if (seq > 0 && typeof this._tokenStore.saveSeq === 'function') {
3315
+ this._tokenStore.saveSeq(this._aid, this._deviceId, this._slotId, ns, seq);
4014
3316
  return;
4015
3317
  }
4016
- const deleteSeq = this._keystore.deleteSeq;
3318
+ const deleteSeq = this._tokenStore.deleteSeq;
4017
3319
  if (seq <= 0 && typeof deleteSeq === 'function') {
4018
- deleteSeq.call(this._keystore, this._aid, this._deviceId, this._slotId, ns);
3320
+ deleteSeq.call(this._tokenStore, this._aid, this._deviceId, this._slotId, ns);
4019
3321
  return;
4020
3322
  }
4021
3323
  if (seq > 0) {
@@ -4247,17 +3549,7 @@ export class AUNClient {
4247
3549
  this._v2BootstrapCache.clear();
4248
3550
  }
4249
3551
  let identity = this._identity;
4250
- if (!identity) {
4251
- try {
4252
- identity = this._keystore.loadIdentity(this._aid);
4253
- if (identity)
4254
- this._identity = identity;
4255
- }
4256
- catch {
4257
- identity = null;
4258
- }
4259
- }
4260
- // 私钥由 AIDStore 管理,直接从 _currentAid 读取明文私钥
3552
+ // 私钥来自当前 AID 值对象,AUNClient 不从持久化存储读取私钥。
4261
3553
  const currentAid = this._currentAid;
4262
3554
  if (!currentAid?.privateKeyPem) {
4263
3555
  this._clientLog.warn('V2 session init skipped: no AID private key');
@@ -4271,8 +3563,8 @@ export class AUNClient {
4271
3563
  const aidPriv = _v2LeftPad32(_v2B64uToBytes(jwk.d));
4272
3564
  const pubDer = crypto.createPublicKey(privateKey).export({ format: 'der', type: 'spki' });
4273
3565
  const aidPubDer = new Uint8Array(pubDer);
4274
- const storeProvider = this._keystore;
4275
- const v2Store = storeProvider.getV2KeyStore?.call(this._keystore, this._aid);
3566
+ const storeProvider = this._tokenStore;
3567
+ const v2Store = storeProvider.getV2KeyStore?.call(this._tokenStore, this._aid);
4276
3568
  if (!v2Store) {
4277
3569
  throw new StateError('V2 key store is unavailable for current keystore');
4278
3570
  }
@@ -4282,6 +3574,28 @@ export class AUNClient {
4282
3574
  this._clientLog.debug(`V2 session initialized aid=${this._aid} device=${this._deviceId}`);
4283
3575
  // 群 state proposal 由服务端在 client.online 时定向通知。
4284
3576
  }
3577
+ _currentV2KeyStore() {
3578
+ if (this._v2KeyStore)
3579
+ return this._v2KeyStore;
3580
+ if (!this._aid)
3581
+ throw new StateError('V2 key store requires a loaded AID');
3582
+ const storeProvider = this._tokenStore;
3583
+ const v2Store = storeProvider.getV2KeyStore?.call(this._tokenStore, this._aid);
3584
+ if (!v2Store) {
3585
+ throw new StateError('V2 key store is unavailable for current identity');
3586
+ }
3587
+ this._v2KeyStore = v2Store;
3588
+ return v2Store;
3589
+ }
3590
+ _saveGroupIdentityToV2(groupAid, identity) {
3591
+ const privateKeyPem = String(identity.private_key_pem ?? '').trim();
3592
+ const publicKeyDerB64 = String(identity.public_key_der_b64 ?? '').trim();
3593
+ if (!groupAid || !privateKeyPem || !publicKeyDerB64) {
3594
+ throw new StateError('group identity is incomplete');
3595
+ }
3596
+ const pubDer = new Uint8Array(Buffer.from(publicKeyDerB64, 'base64'));
3597
+ this._currentV2KeyStore().saveGroupIdentity(this._deviceId, groupAid, privateKeyPem, pubDer);
3598
+ }
4285
3599
  async _v2TrustedIKPubDer(aid) {
4286
3600
  const normalizedAid = String(aid ?? '').trim();
4287
3601
  if (!normalizedAid)
@@ -5263,7 +4577,7 @@ export class AUNClient {
5263
4577
  return null;
5264
4578
  }
5265
4579
  const e2eeMeta = this._v2E2eeMeta(envelope);
5266
- this._observeAgentMdFromEnvelope(envelope);
4580
+ this._agentMdManager.observeEnvelope(envelope);
5267
4581
  let spkId = '';
5268
4582
  let recipientKeySource = '';
5269
4583
  if (isJsonObject(envelope.recipient)) {
@@ -5468,7 +4782,7 @@ export class AUNClient {
5468
4782
  _attachV2EnvelopeMetadataFromSource(message, source) {
5469
4783
  const envelope = this._extractV2EnvelopeFromSource(source);
5470
4784
  if (envelope) {
5471
- this._observeAgentMdFromEnvelope(envelope);
4785
+ this._agentMdManager.observeEnvelope(envelope);
5472
4786
  this._attachV2EnvelopeMetadata(message, this._v2E2eeMeta(envelope));
5473
4787
  }
5474
4788
  }
@@ -6555,9 +5869,9 @@ export class AUNClient {
6555
5869
  if (this._gatewayUrl)
6556
5870
  return this._gatewayUrl;
6557
5871
  try {
6558
- const loadMetadata = this._keystore.loadMetadata;
5872
+ const loadMetadata = this._tokenStore.loadMetadata;
6559
5873
  const cachedGateway = typeof loadMetadata === 'function'
6560
- ? String(loadMetadata.call(this._keystore, resolvedAid)?.gateway_url ?? '').trim()
5874
+ ? String(loadMetadata.call(this._tokenStore, resolvedAid)?.gateway_url ?? '').trim()
6561
5875
  : '';
6562
5876
  if (cachedGateway) {
6563
5877
  this._gatewayUrl = cachedGateway;
@@ -6579,9 +5893,9 @@ export class AUNClient {
6579
5893
  const gateway = await this._discovery.discover(url);
6580
5894
  this._gatewayUrl = gateway;
6581
5895
  try {
6582
- const saveMetadata = this._keystore.saveMetadata;
5896
+ const saveMetadata = this._tokenStore.saveMetadata;
6583
5897
  if (typeof saveMetadata === 'function') {
6584
- saveMetadata.call(this._keystore, resolvedAid, { gateway_url: gateway, gateway_cached_at: Date.now() });
5898
+ saveMetadata.call(this._tokenStore, resolvedAid, { gateway_url: gateway, gateway_cached_at: Date.now() });
6585
5899
  }
6586
5900
  }
6587
5901
  catch {
@@ -6627,9 +5941,8 @@ export class AUNClient {
6627
5941
  }
6628
5942
  /** 连接后同步身份信息 */
6629
5943
  _syncIdentityAfterConnect(accessToken) {
6630
- const identity = this._auth.loadIdentityOrNone(this._aid ?? undefined);
5944
+ const identity = this._identity;
6631
5945
  if (identity === null) {
6632
- this._identity = null;
6633
5946
  return;
6634
5947
  }
6635
5948
  identity.access_token = accessToken;
@@ -6640,9 +5953,7 @@ export class AUNClient {
6640
5953
  const persistIdentity = this._auth._persistIdentity;
6641
5954
  if (typeof persistIdentity === 'function') {
6642
5955
  persistIdentity.call(this._auth, identity);
6643
- return;
6644
5956
  }
6645
- this._keystore.saveIdentity(String(identity.aid), identity);
6646
5957
  }
6647
5958
  // ── 内部:参数处理 ────────────────────────────────────────
6648
5959
  /** 规范化连接参数 */
@@ -6826,7 +6137,7 @@ export class AUNClient {
6826
6137
  scheduleNext();
6827
6138
  return;
6828
6139
  }
6829
- let identity = this._identity ?? this._auth.loadIdentityOrNone() ?? null;
6140
+ let identity = this._identity;
6830
6141
  if (identity === null) {
6831
6142
  scheduleNext();
6832
6143
  return;
@@ -6954,8 +6265,8 @@ export class AUNClient {
6954
6265
  if ('device_id' in params && String(params.device_id ?? '').trim() !== this._deviceId) {
6955
6266
  throw new ValidationError('message.pull/message.ack device_id must match the current client instance');
6956
6267
  }
6957
- const slotId = normalizeInstanceId(params.slot_id ?? this._slotId, 'slot_id', { allowEmpty: true });
6958
- if (slotId !== this._slotId) {
6268
+ const slotId = normalizeSlotId(params.slot_id ?? this._slotId, this._slotId);
6269
+ if (slotIsolationKey(slotId) !== slotIsolationKey(this._slotId)) {
6959
6270
  throw new ValidationError('message.pull/message.ack slot_id must match the current client instance');
6960
6271
  }
6961
6272
  params.device_id = this._deviceId;
@@ -7175,8 +6486,7 @@ export class AUNClient {
7175
6486
  }
7176
6487
  // ── Named Group(命名群)高层 API ────────────────────────────
7177
6488
  /**
7178
- * 创建命名群:本地生成 P-256 keypair,调用 group.create 传入 public_key,
7179
- * 服务端签发群 AID 证书,返回后将证书和私钥存入 keystore。
6489
+ * 创建命名群:群/P2P 私钥由 V2 数据库存储,不写入 AID 身份私钥存储。
7180
6490
  */
7181
6491
  async createNamedGroup(groupName, opts = {}) {
7182
6492
  const tStart = Date.now();
@@ -7196,15 +6506,10 @@ export class AUNClient {
7196
6506
  const aidCert = result?.aid_cert;
7197
6507
  const groupAid = String(groupInfo?.group_aid ?? '');
7198
6508
  if (groupAid && aidCert) {
7199
- this._keystore.saveIdentity(groupAid, {
7200
- private_key_pem: identity.private_key_pem,
7201
- public_key: identity.public_key_der_b64,
7202
- curve: 'P-256',
7203
- type: 'group_identity',
7204
- });
6509
+ this._saveGroupIdentityToV2(groupAid, identity);
7205
6510
  const certPem = String(aidCert.cert ?? '');
7206
6511
  if (certPem) {
7207
- this._keystore.saveCert(groupAid, certPem);
6512
+ this._tokenStore.saveCert(groupAid, certPem);
7208
6513
  }
7209
6514
  }
7210
6515
  this._clientLog.debug(`createNamedGroup exit: elapsed=${Date.now() - tStart}ms groupAid=${groupAid}`);
@@ -7235,15 +6540,10 @@ export class AUNClient {
7235
6540
  const aidCert = result?.aid_cert;
7236
6541
  const groupAid = String(groupInfo?.group_aid ?? '');
7237
6542
  if (groupAid && aidCert) {
7238
- this._keystore.saveIdentity(groupAid, {
7239
- private_key_pem: identity.private_key_pem,
7240
- public_key: identity.public_key_der_b64,
7241
- curve: 'P-256',
7242
- type: 'group_identity',
7243
- });
6543
+ this._saveGroupIdentityToV2(groupAid, identity);
7244
6544
  const certPem = String(aidCert.cert ?? '');
7245
6545
  if (certPem) {
7246
- this._keystore.saveCert(groupAid, certPem);
6546
+ this._tokenStore.saveCert(groupAid, certPem);
7247
6547
  }
7248
6548
  }
7249
6549
  this._clientLog.debug(`bindGroupAid exit: elapsed=${Date.now() - tStart}ms groupAid=${groupAid}`);