@agentunion/fastaun 0.5.23 → 0.5.25

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 (54) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/_packed_docs/CHANGELOG.md +62 -0
  3. package/_packed_docs/INDEX.md +27 -15
  4. package/_packed_docs/KITE_DOCS_GUIDE.md +22 -19
  5. package/_packed_docs/TS/346/263/250/345/206/214/350/207/252/345/212/250/347/273/221/345/256/232Owner/347/232/204E2E/351/252/214/350/257/201/344/273/243/347/240/201.md +196 -0
  6. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2112.1.md +2 -2
  7. package/_packed_docs/sdk/0.5.24-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +401 -0
  8. package/_packed_docs/sdk/0.5.24-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +354 -0
  9. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +17 -7
  10. package/_packed_docs/sdk/INDEX.md +30 -6
  11. package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/2272.1.md +49 -2
  12. package/_packed_docs/sdk/Ownership2.1-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +123 -72
  13. package/_packed_docs/sdk/Ownership2.1-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +173 -96
  14. package/dist/agent-md.d.ts +2 -1
  15. package/dist/agent-md.js +52 -22
  16. package/dist/agent-md.js.map +1 -1
  17. package/dist/aid-store.d.ts +1 -0
  18. package/dist/aid-store.js +34 -12
  19. package/dist/aid-store.js.map +1 -1
  20. package/dist/auth.d.ts +1 -0
  21. package/dist/auth.js +44 -10
  22. package/dist/auth.js.map +1 -1
  23. package/dist/client/delivery.d.ts +3 -0
  24. package/dist/client/delivery.js +197 -47
  25. package/dist/client/delivery.js.map +1 -1
  26. package/dist/client/rpc-pipeline.js +10 -0
  27. package/dist/client/rpc-pipeline.js.map +1 -1
  28. package/dist/client/v2-e2ee.js +17 -5
  29. package/dist/client/v2-e2ee.js.map +1 -1
  30. package/dist/client.d.ts +10 -10
  31. package/dist/client.js +158 -119
  32. package/dist/client.js.map +1 -1
  33. package/dist/group-fs.js +5 -0
  34. package/dist/group-fs.js.map +1 -1
  35. package/dist/index.d.ts +1 -1
  36. package/dist/keystore/aid-db.d.ts +1 -8
  37. package/dist/keystore/aid-db.js +46 -204
  38. package/dist/keystore/aid-db.js.map +1 -1
  39. package/dist/keystore/index.d.ts +0 -15
  40. package/dist/keystore/local-token-store.d.ts +1 -6
  41. package/dist/keystore/local-token-store.js +0 -15
  42. package/dist/keystore/local-token-store.js.map +1 -1
  43. package/dist/ownership-v21.d.ts +4 -2
  44. package/dist/ownership-v21.js +86 -42
  45. package/dist/ownership-v21.js.map +1 -1
  46. package/dist/ownership-workflow.d.ts +30 -59
  47. package/dist/ownership-workflow.js +260 -1667
  48. package/dist/ownership-workflow.js.map +1 -1
  49. package/dist/tools/cross-sdk-agent.d.ts +1 -2
  50. package/dist/tools/cross-sdk-agent.js +7 -14
  51. package/dist/tools/cross-sdk-agent.js.map +1 -1
  52. package/dist/version.d.ts +1 -1
  53. package/dist/version.js +1 -1
  54. package/package.json +1 -1
package/dist/client.d.ts CHANGED
@@ -18,7 +18,7 @@ import { StorageVFS } from './storage/vfs.js';
18
18
  import { type JsonObject, type JsonValue, type RpcParams, type RpcResult, ConnectionState } from './types.js';
19
19
  import { AID } from './aid.js';
20
20
  import { type OwnershipConsensusResult } from './ownership-v21.js';
21
- import { type OwnershipTaskResult, type OwnershipTaskResetResult } from './ownership-workflow.js';
21
+ import { type OwnershipTaskResult } from './ownership-workflow.js';
22
22
  import { type CertificateMaterial } from './org-protocol.js';
23
23
  /**
24
24
  * 递归排序键的 JSON 序列化(Canonical JSON for AUN)
@@ -82,8 +82,6 @@ export declare class AUNClient {
82
82
  private _currentAid;
83
83
  private _serviceEventIdentityVersion;
84
84
  private _serviceEventIdentityAbort;
85
- /** ownership 关系只在 SDK 内存中编排;服务端不保存状态机。 */
86
- private _ownershipRecords;
87
85
  private _ownershipType;
88
86
  private _ownershipStorage;
89
87
  private _ownershipFiles;
@@ -92,9 +90,10 @@ export declare class AUNClient {
92
90
  private _getOwnershipFiles;
93
91
  private _createOwnershipFiles;
94
92
  private _getOwnershipWorkflow;
95
- private _persistOwnershipRecords;
96
- private _restoreOwnershipRecords;
97
- private _resumeOwnershipAfterConnect;
93
+ private _recoverOwnershipRequest;
94
+ private _restoreIdentityType;
95
+ private _ownershipOperation;
96
+ private _ownershipOperationGuard?;
98
97
  private _credentialRenewal?;
99
98
  private _credentialRenewalTimer;
100
99
  private _credentialRenewalAbort;
@@ -111,8 +110,6 @@ export declare class AUNClient {
111
110
  _rememberCredential(credential: JsonObject, material: Record<string, unknown>, certificate?: CertificateMaterial): void;
112
111
  private _verifyIncomingServiceEvent;
113
112
  private _handleCredentialRenewal;
114
- private _resumeRegistrationIntent;
115
- private _resumeOwnership;
116
113
  /** 实例级 protected_headers */
117
114
  private _instanceProtectedHeaders;
118
115
  /** 重连退避时间戳(ms) */
@@ -124,6 +121,7 @@ export declare class AUNClient {
124
121
  /** Gateway URL */
125
122
  private _gatewayUrl;
126
123
  private _gatewayCandidates;
124
+ private _peerGatewayCache;
127
125
  /** 是否正在关闭 */
128
126
  private _closing;
129
127
  private _disconnecting;
@@ -263,7 +261,6 @@ export declare class AUNClient {
263
261
  get stream(): StreamFacade;
264
262
  /** 当前 AID 值对象 */
265
263
  get currentAid(): AID | null;
266
- private _ownershipKey;
267
264
  getType(): string;
268
265
  get_type(): string;
269
266
  getOwner(): string | null;
@@ -272,7 +269,10 @@ export declare class AUNClient {
272
269
  get_ownership_status(): Record<string, unknown>;
273
270
  bindOwneraid(ownerAid: string): Promise<OwnershipTaskResult>;
274
271
  bindSubaid(subAid: string): Promise<OwnershipTaskResult>;
275
- resetOwnershipTask(): Promise<OwnershipTaskResetResult>;
272
+ bindLocalSubaid(subClient: AUNClient): Promise<OwnershipTaskResult>;
273
+ transferLocalSubaid(subClient: AUNClient, newOwnerClient: AUNClient): Promise<OwnershipTaskResult>;
274
+ private _createOwnershipOperationClient;
275
+ private _localOwnershipOperation;
276
276
  releaseSubaid(subAid: string): Promise<OwnershipTaskResult>;
277
277
  transferSubaid(subAid: string, newOwnerAid: string): Promise<OwnershipTaskResult>;
278
278
  checkOwnershipConsensus(subAid?: string): Promise<OwnershipConsensusResult>;
package/dist/client.js CHANGED
@@ -49,7 +49,7 @@ import { AID } from './aid.js';
49
49
  import { certFingerprint, certMatchesFingerprint, normalizeFingerprintHex, publicKeyMatchesFingerprint } from './cert-utils.js';
50
50
  import { validateAgentMdCertificate, validateAgentMdDocument } from './agent-md-schema.js';
51
51
  import { OwnershipFiles, OwnershipStorageAdapter, ownershipAid, ownershipFailure } from './ownership-v21.js';
52
- import { OwnershipWorkflow } from './ownership-workflow.js';
52
+ import { OwnershipWorkflow, OWNERSHIP_SDK_MESSAGE_TYPE, parseOwnershipMessage } from './ownership-workflow.js';
53
53
  import { canonicalJson, createCredential, createMemberCredential, signMembershipBinding, digest, validateMembershipBinding, verifyMembershipBinding, verifyCredentialSignature, createRepresentationProof, verifyServiceEvent, buildRequestBinding, validateRequestBinding, CredentialRenewal } from './org-protocol.js';
54
54
  function isPromiseLike(value) {
55
55
  return Boolean(value && typeof value.then === 'function');
@@ -352,7 +352,9 @@ function createAgentMdManagerForRuntime(opts) {
352
352
  entitySignerResolver: opts.fetchEntitySigner,
353
353
  gatewayResolver: async (aid) => {
354
354
  const gatewayUrl = await opts.gateway.resolve(aid);
355
- opts.gateway.set(gatewayUrl);
355
+ const localAid = opts.ownerAid();
356
+ if (localAid && aid.slice(aid.indexOf('.') + 1).trim().toLowerCase() === localAid.slice(localAid.indexOf('.') + 1).trim().toLowerCase())
357
+ opts.gateway.set(gatewayUrl);
356
358
  return gatewayUrl;
357
359
  },
358
360
  accessTokenResolver: async (aid, gatewayUrl) => {
@@ -446,7 +448,7 @@ function createAgentMdManagerForRuntime(opts) {
446
448
  }
447
449
  if (!opts.gateway.get()) {
448
450
  try {
449
- opts.gateway.set(await opts.gateway.resolve(target));
451
+ opts.gateway.set(await opts.gateway.resolve(opts.ownerAid() ?? target));
450
452
  }
451
453
  catch { /* best effort before cert fetch */ }
452
454
  }
@@ -479,8 +481,6 @@ export class AUNClient {
479
481
  _currentAid = null;
480
482
  _serviceEventIdentityVersion = 0;
481
483
  _serviceEventIdentityAbort = new AbortController();
482
- /** ownership 关系只在 SDK 内存中编排;服务端不保存状态机。 */
483
- _ownershipRecords = new Map();
484
484
  _ownershipType = 'agent';
485
485
  _ownershipStorage = null;
486
486
  _ownershipFiles = null;
@@ -489,7 +489,7 @@ export class AUNClient {
489
489
  return this._ownershipStorage ??= new OwnershipStorageAdapter({ currentAid: () => this._aid, call: (method, params) => this.call(method, params), httpPut: (url, data, headers) => this.storage.lowlevel.httpPut(url, data, headers) });
490
490
  }
491
491
  _getOwnershipFiles() {
492
- return this._ownershipFiles ??= this._createOwnershipFiles((subAid) => this._agentMdManager.readOwnershipFresh(subAid));
492
+ return this._ownershipFiles ??= this._createOwnershipFiles((subAid, stage) => this._agentMdManager.readOwnershipFresh(subAid, stage));
493
493
  }
494
494
  _createOwnershipFiles(readAgentMdFresh) {
495
495
  return new OwnershipFiles({
@@ -507,52 +507,56 @@ export class AUNClient {
507
507
  }
508
508
  _getOwnershipWorkflow() {
509
509
  return this._ownershipWorkflow ??= new OwnershipWorkflow({
510
- currentAid: () => this._aid, store: this._tokenStore, files: this._getOwnershipFiles(),
511
- currentType: () => this.getType(),
510
+ currentAid: () => this._aid, files: this._getOwnershipFiles(),
511
+ currentType: () => this.getType(), assertCurrent: () => this._ownershipOperationGuard?.(),
512
+ recoverRequest: (operationId) => this._recoverOwnershipRequest(operationId),
512
513
  send: (to, payload, messageId) => this.call('message.send', { to, encrypt: true, message_id: messageId, payload: payload }),
513
514
  emit: (event, payload) => this._dispatcher.enqueue(event, payload),
514
515
  });
515
516
  }
516
- _persistOwnershipRecords() {
517
- const aid = String(this._aid ?? '').trim();
518
- if (!aid)
519
- return;
520
- const metadata = this._tokenStore.loadMetadata?.(aid) ?? {};
521
- const existing = metadata.ownership_records;
522
- const records = existing && typeof existing === 'object' && !Array.isArray(existing)
523
- ? { ...existing } : {};
524
- for (const [key, record] of this._ownershipRecords) {
525
- if (![record.subject_aid, record.owner_aid].some((value) => String(value ?? '').trim().toLowerCase() === aid.toLowerCase()))
526
- continue;
527
- records[key] = Object.fromEntries(Object.entries(record).filter(([name]) => !['subject_identity', 'escrow_password', 'escrow_download_url'].includes(name)));
517
+ async _recoverOwnershipRequest(operationId) {
518
+ const identity = this._aid, version = this._serviceEventIdentityVersion;
519
+ const current = () => { if (this._aid !== identity || this._serviceEventIdentityVersion !== version)
520
+ throw ownershipFailure('conflict', 'ownership request identity changed'); };
521
+ const catalog = await this.call('message.peers', { filter: 'all', limit: 200 });
522
+ current();
523
+ for (const peer of Array.isArray(catalog.peers) ? catalog.peers.slice(0, 200) : []) {
524
+ const sender = String(peer.sender_aid ?? '');
525
+ const history = await this.call('message.history', { sender_aid: sender, before_seq: Number.MAX_SAFE_INTEGER, limit: 200 });
526
+ current();
527
+ for (const row of Array.isArray(history.messages) ? history.messages : []) {
528
+ const envelope = row, content = envelope.payload ?? envelope;
529
+ const raw = decryptedOwnershipJsonText(content) ?? content;
530
+ let value = raw;
531
+ if (typeof raw === 'string') {
532
+ try {
533
+ value = JSON.parse(raw);
534
+ }
535
+ catch {
536
+ continue;
537
+ }
538
+ }
539
+ if (!isJsonObject(value) || value.type !== OWNERSHIP_SDK_MESSAGE_TYPE || value.operation_id !== operationId || !['bind.request', 'transfer.request'].includes(String(value.action)))
540
+ continue;
541
+ const authenticated = String(envelope.from_aid ?? envelope.from ?? envelope.sender_aid ?? '');
542
+ if (authenticated !== sender)
543
+ throw ownershipFailure('forbidden', 'ownership history sender changed');
544
+ return { from: authenticated, payload: parseOwnershipMessage(raw, { authenticated_from_aid: authenticated, local_aid: identity }) };
545
+ }
528
546
  }
529
- this._tokenStore.saveMetadata?.(aid, { ownership_records: records });
547
+ return null;
530
548
  }
531
- _restoreOwnershipRecords() {
549
+ _restoreIdentityType() {
532
550
  const aid = String(this._aid ?? '').trim();
533
551
  if (!aid)
534
552
  return;
535
553
  const metadata = this._tokenStore.loadMetadata?.(aid);
536
- const intent = metadata?.registration_intent;
537
- if (intent?.type)
538
- this._ownershipType = String(intent.type);
539
- const records = metadata?.ownership_records;
540
- if (!records || typeof records !== 'object' || Array.isArray(records))
541
- return;
542
- for (const [key, value] of Object.entries(records)) {
543
- if (!value || typeof value !== 'object' || Array.isArray(value))
544
- continue;
545
- const record = value;
546
- this._ownershipRecords.set(key, record);
547
- if (String(record.subject_aid ?? '').toLowerCase() === aid.toLowerCase())
548
- this._ownershipType = String(record.type ?? this._ownershipType);
549
- }
550
- }
551
- async _resumeOwnershipAfterConnect() {
552
- await this._checkIdentityAdmissionAfterConnect();
553
- await this._resumeRegistrationIntent();
554
- await this._resumeOwnership();
554
+ const legacy = metadata?.registration_intent;
555
+ if (metadata?.identity_type ?? legacy?.type)
556
+ this._ownershipType = String(metadata?.identity_type ?? legacy?.type);
555
557
  }
558
+ _ownershipOperation = false;
559
+ _ownershipOperationGuard;
556
560
  _credentialRenewal;
557
561
  _credentialRenewalTimer = null;
558
562
  _credentialRenewalAbort = null;
@@ -750,18 +754,6 @@ export class AUNClient {
750
754
  await this._safeAsync(this._getCredentialRenewal().receive(String(envelope.from ?? envelope.from_aid ?? envelope.sender_aid ?? ''), parsed));
751
755
  return true;
752
756
  }
753
- async _resumeRegistrationIntent() {
754
- const intent = this._tokenStore.loadMetadata?.(this._aid)?.registration_intent;
755
- if (['group', 'org'].includes(String(intent?.type ?? this.getType())))
756
- return;
757
- if (intent?.protocol !== '2.1' || intent.pending !== true)
758
- return;
759
- await this._getOwnershipWorkflow().resumeRegistration(String(intent.owner), String(intent.operation_id));
760
- this._tokenStore.saveMetadata?.(this._aid, { registration_intent: { ...intent, pending: false } });
761
- }
762
- async _resumeOwnership() {
763
- await this._getOwnershipWorkflow().resume();
764
- }
765
757
  /** 实例级 protected_headers */
766
758
  _instanceProtectedHeaders = null;
767
759
  /** 重连退避时间戳(ms) */
@@ -773,6 +765,7 @@ export class AUNClient {
773
765
  /** Gateway URL */
774
766
  _gatewayUrl = null;
775
767
  _gatewayCandidates = [];
768
+ _peerGatewayCache = new Map();
776
769
  /** 是否正在关闭 */
777
770
  _closing = false;
778
771
  _disconnecting = false;
@@ -957,7 +950,7 @@ export class AUNClient {
957
950
  net: dnsNet,
958
951
  });
959
952
  this._aid = initAid;
960
- this._restoreOwnershipRecords();
953
+ this._restoreIdentityType();
961
954
  this._agentMdManager = createAgentMdManagerForRuntime({
962
955
  config: () => this._configModel,
963
956
  logger: () => this._logger.for('aun_core.agent_md'),
@@ -1138,9 +1131,6 @@ export class AUNClient {
1138
1131
  get currentAid() {
1139
1132
  return this._currentAid;
1140
1133
  }
1141
- _ownershipKey(subjectAid, ownerAid) {
1142
- return `${String(subjectAid ?? '').trim().toLowerCase()}|${String(ownerAid ?? '').trim().toLowerCase()}`;
1143
- }
1144
1134
  getType() {
1145
1135
  return String(this._ownershipType || 'agent').trim() || 'agent';
1146
1136
  }
@@ -1148,45 +1138,12 @@ export class AUNClient {
1148
1138
  return this.getType();
1149
1139
  }
1150
1140
  getOwner() {
1151
- const aid = String(this._aid ?? '').trim().toLowerCase();
1152
- let best = null;
1153
- let bestActive = false;
1154
- for (const record of this._ownershipRecords.values()) {
1155
- if (String(record.subject_aid ?? '').trim().toLowerCase() === aid) {
1156
- if (String(record.status ?? '').trim().toLowerCase() === 'released')
1157
- continue;
1158
- const owner = String(record.owner_aid ?? '').trim();
1159
- if (!owner)
1160
- continue;
1161
- const active = String(record.status ?? '').trim().toLowerCase() === 'active';
1162
- if (!best || active && !bestActive) {
1163
- best = owner;
1164
- bestActive = active;
1165
- }
1166
- }
1167
- }
1168
- return best;
1141
+ return null;
1169
1142
  }
1170
1143
  get_owner() {
1171
1144
  return this.getOwner();
1172
1145
  }
1173
1146
  getOwnershipStatus() {
1174
- const aid = String(this._aid ?? '').trim().toLowerCase();
1175
- let best = null;
1176
- let bestActive = false;
1177
- for (const record of this._ownershipRecords.values()) {
1178
- const subject = String(record.subject_aid ?? '').trim().toLowerCase();
1179
- const owner = String(record.owner_aid ?? '').trim().toLowerCase();
1180
- if (aid !== subject && aid !== owner)
1181
- continue;
1182
- const active = String(record.status ?? '').trim().toLowerCase() === 'active';
1183
- if (!best || active && !bestActive) {
1184
- best = record;
1185
- bestActive = active;
1186
- }
1187
- }
1188
- if (best)
1189
- return Object.fromEntries(Object.entries(best).filter(([key]) => key !== 'subject_identity' && key !== 'escrow_password' && key !== 'escrow_download_url'));
1190
1147
  return { subject_aid: this._aid ?? '', owner_aid: null, status: 'legacy_unbound', type: this.getType() };
1191
1148
  }
1192
1149
  get_ownership_status() {
@@ -1194,7 +1151,53 @@ export class AUNClient {
1194
1151
  }
1195
1152
  async bindOwneraid(ownerAid) { return this._getOwnershipWorkflow().bindOwneraid(ownerAid); }
1196
1153
  async bindSubaid(subAid) { return this._getOwnershipWorkflow().bindSubaid(subAid); }
1197
- async resetOwnershipTask() { return this._getOwnershipWorkflow().resetOwnershipTask(); }
1154
+ async bindLocalSubaid(subClient) { return this._localOwnershipOperation(subClient); }
1155
+ async transferLocalSubaid(subClient, newOwnerClient) { return this._localOwnershipOperation(subClient, newOwnerClient); }
1156
+ _createOwnershipOperationClient(slotId) {
1157
+ const aid = this._currentAid;
1158
+ const client = new AUNClient(AID._create({ aid: aid.aid, aunPath: aid.aunPath, certPem: aid.certPem, privateKeyPem: aid.privateKeyPem,
1159
+ certValid: aid.isCertValid(), privateKeyValid: aid.isPrivateKeyValid(), deviceId: this._deviceId, slotId,
1160
+ verifySsl: aid.verifySsl, rootCaPath: aid.rootCaPath, debug: aid.debug, aidStore: aid._aidStore }));
1161
+ client._ownershipOperation = true;
1162
+ client._ownershipType = this.getType();
1163
+ return client;
1164
+ }
1165
+ async _localOwnershipOperation(sub, next) {
1166
+ const sources = [this, sub, ...(next ? [next] : [])];
1167
+ if (sources.some((source) => !(source instanceof AUNClient) || !source._currentAid?.isPrivateKeyValid() || !source._currentAid.isCertValid() || !source._currentAid.privateKeyPem || source._currentAid.aid !== source._aid || source.getType() !== 'agent') || new Set(sources.map((source) => source._aid)).size !== sources.length)
1168
+ throw ownershipFailure('forbidden', 'local ownership requires distinct agent clients with private identities');
1169
+ const identities = sources.map((source) => [source._aid, source._currentAid, source._serviceEventIdentityVersion]);
1170
+ const guard = () => { if (sources.some((source, index) => source._aid !== identities[index][0] || source._currentAid !== identities[index][1] || source._serviceEventIdentityVersion !== identities[index][2]))
1171
+ throw ownershipFailure('conflict', 'local ownership source identity changed'); };
1172
+ for (const source of sources) {
1173
+ guard();
1174
+ const document = await source._agentMdManager.readAgentMdFresh(source._aid);
1175
+ guard();
1176
+ if (!document.exists)
1177
+ throw ownershipFailure('forbidden', 'local ownership requires a published agent document');
1178
+ const verified = await source._getOwnershipFiles().verifyFile(document.content, 'agent', source._aid);
1179
+ guard();
1180
+ if (verified.frontmatter.type !== 'agent')
1181
+ throw ownershipFailure('forbidden', 'local ownership requires verified agent identities');
1182
+ }
1183
+ const slot = `ownership-${crypto.randomUUID()}`, clients = [];
1184
+ try {
1185
+ for (const source of sources) {
1186
+ const client = source._createOwnershipOperationClient(slot);
1187
+ client._ownershipOperationGuard = guard;
1188
+ clients.push(client);
1189
+ }
1190
+ for (const client of clients) {
1191
+ guard();
1192
+ await client.connect({ connection_kind: 'short', background_sync: false, auto_reconnect: false });
1193
+ guard();
1194
+ }
1195
+ return next ? await clients[0]._getOwnershipWorkflow().transferLocalSubaid(clients[1]._getOwnershipWorkflow(), clients[2]._getOwnershipWorkflow()) : await clients[0]._getOwnershipWorkflow().bindLocalSubaid(clients[1]._getOwnershipWorkflow());
1196
+ }
1197
+ finally {
1198
+ await Promise.all(clients.map((client) => client.close()));
1199
+ }
1200
+ }
1198
1201
  async releaseSubaid(subAid) { return this._getOwnershipWorkflow().releaseSubaid(subAid); }
1199
1202
  async transferSubaid(subAid, newOwnerAid) { return this._getOwnershipWorkflow().transferSubaid(subAid, newOwnerAid); }
1200
1203
  async checkOwnershipConsensus(subAid) {
@@ -1224,7 +1227,28 @@ export class AUNClient {
1224
1227
  return false;
1225
1228
  const envelope = message;
1226
1229
  const content = 'payload' in envelope ? envelope.payload : message;
1227
- return this._getOwnershipWorkflow().receive(String(envelope.from_aid ?? ''), decryptedOwnershipJsonText(content) ?? content);
1230
+ const raw = decryptedOwnershipJsonText(content) ?? content, sender = String(envelope.from_aid ?? '');
1231
+ let value = raw;
1232
+ if (typeof value === 'string') {
1233
+ try {
1234
+ value = JSON.parse(value);
1235
+ }
1236
+ catch {
1237
+ return false;
1238
+ }
1239
+ }
1240
+ if (value && typeof value === 'object' && !Array.isArray(value) && value.type === OWNERSHIP_SDK_MESSAGE_TYPE) {
1241
+ const control = value, request = control.action === 'bind.request' || control.action === 'transfer.request';
1242
+ try {
1243
+ parseOwnershipMessage(raw, { authenticated_from_aid: sender, local_aid: this._aid, ...(request ? {} : { task: { ...control, initiator_aid: this._aid } }) });
1244
+ }
1245
+ catch (error) {
1246
+ if (error instanceof ValidationError && !error.retryable || error instanceof StateError && !error.retryable && ['invalid', 'forbidden'].includes(error.stringCode))
1247
+ return true;
1248
+ throw error;
1249
+ }
1250
+ }
1251
+ return this._getOwnershipWorkflow().receive(sender, raw);
1228
1252
  }
1229
1253
  get hasIdentity() {
1230
1254
  return this._currentAid !== null && this.state !== ConnectionState.CLOSED;
@@ -1339,6 +1363,7 @@ export class AUNClient {
1339
1363
  this._certCache.clear();
1340
1364
  this._gatewayUrl = null;
1341
1365
  this._gatewayCandidates = [];
1366
+ this._peerGatewayCache.clear();
1342
1367
  this._deviceId = aid.deviceId || getDeviceId(nextConfig.aunPath);
1343
1368
  this._slotId = aid.slotId || 'default';
1344
1369
  const debugFlag = nextConfig.debug;
@@ -1416,10 +1441,9 @@ export class AUNClient {
1416
1441
  this._serviceEventIdentityAbort = new AbortController();
1417
1442
  this._ensureReadStateIdentity();
1418
1443
  if (previousAid !== this._aid) {
1419
- this._ownershipRecords.clear();
1420
1444
  this._ownershipType = 'agent';
1421
1445
  }
1422
- this._restoreOwnershipRecords();
1446
+ this._restoreIdentityType();
1423
1447
  }
1424
1448
  setProtectedHeaders(headers) {
1425
1449
  if (!headers) {
@@ -1502,6 +1526,7 @@ export class AUNClient {
1502
1526
  }
1503
1527
  /** 关闭连接 */
1504
1528
  async close() {
1529
+ this._peerGatewayCache.clear();
1505
1530
  return this._lifecycle.close();
1506
1531
  }
1507
1532
  /**
@@ -2647,9 +2672,11 @@ export class AUNClient {
2647
2672
  on(event, handler) {
2648
2673
  const tStart = Date.now();
2649
2674
  this._clientLog.debug(`on enter: event=${event}`);
2675
+ const messageEvent = event === 'message.received' || event === 'message.recalled' || event === 'message.batch';
2676
+ const hadMessageConsumer = messageEvent && [...this._dispatcher._snapshotApplicationHandlers(['message.received', 'message.recalled', 'message.batch']).values()].some((handlers) => handlers.length > 0);
2650
2677
  const result = this._dispatcher.subscribe(event, handler);
2651
- if (event === 'message.received' || event === 'message.recalled' || event === 'message.batch') {
2652
- if (this._p2pPeerPullEnabled)
2678
+ if (messageEvent) {
2679
+ if (this._p2pPeerPullEnabled && !hadMessageConsumer)
2653
2680
  this._delivery.refreshPendingPeers();
2654
2681
  this._delivery.requestBackgroundPullRecovery();
2655
2682
  }
@@ -4041,7 +4068,8 @@ export class AUNClient {
4041
4068
  this._clientLog.warn(`schedule post-connect P2P gap fill failed: ${formatCaughtError(exc)}`);
4042
4069
  });
4043
4070
  }
4044
- this._safeAsync(this._resumeOwnershipAfterConnect());
4071
+ if (!this._ownershipOperation)
4072
+ this._safeAsync(this._checkIdentityAdmissionAfterConnect());
4045
4073
  this._clientLog.debug(`_connectOnce exit: elapsed=${Date.now() - tStart}ms gateway=${gatewayUrl}, aid=${this._aid ?? ''}`);
4046
4074
  }
4047
4075
  catch (err) {
@@ -4680,20 +4708,15 @@ export class AUNClient {
4680
4708
  return gateways[0];
4681
4709
  }
4682
4710
  async _resolveOwnershipCertificateGateway(aid) {
4683
- const issuer = aid.slice(aid.indexOf('.') + 1);
4684
- if (issuer === this._aid?.slice(this._aid.indexOf('.') + 1))
4711
+ const issuer = aid.slice(aid.indexOf('.') + 1).trim().toLowerCase();
4712
+ if (issuer === this._aid?.slice(this._aid.indexOf('.') + 1).trim().toLowerCase())
4685
4713
  return this._resolveGatewayForAid(aid);
4686
- const port = this._configModel.discoveryPort ? `:${this._configModel.discoveryPort}` : '';
4687
- let lastError;
4688
- for (const host of [aid, `gateway.${issuer}`]) {
4689
- try {
4690
- return await this._discovery.discover(`https://${host}${port}/.well-known/aun-gateway`);
4691
- }
4692
- catch (error) {
4693
- lastError = error;
4694
- }
4714
+ try {
4715
+ return await this._resolveGatewayForAid(aid);
4716
+ }
4717
+ catch (error) {
4718
+ throw ownershipFailure('unavailable', error instanceof Error ? error.message : 'signer Gateway discovery failed', true);
4695
4719
  }
4696
- throw ownershipFailure('unavailable', lastError instanceof Error ? lastError.message : 'signer Gateway discovery failed', true);
4697
4720
  }
4698
4721
  /** 发现并缓存全部 Gateway 候选,供首连和自动重连故障转移使用。 */
4699
4722
  async _resolveGatewayCandidatesForAid(aid) {
@@ -4701,17 +4724,26 @@ export class AUNClient {
4701
4724
  if (!resolvedAid) {
4702
4725
  throw new StateError('gateway discovery requires a loaded AID');
4703
4726
  }
4727
+ const dotIdx = resolvedAid.indexOf('.');
4728
+ const issuerDomain = (dotIdx >= 0 ? resolvedAid.slice(dotIdx + 1) : resolvedAid).trim().toLowerCase();
4729
+ const currentAid = this._aid;
4730
+ const sameIssuer = currentAid !== null && issuerDomain === currentAid.slice(currentAid.indexOf('.') + 1).trim().toLowerCase();
4704
4731
  const discovery = this._discovery;
4705
4732
  const tokenStore = this._tokenStore;
4706
4733
  const discoveryIsCurrent = () => (this._discovery === discovery
4707
4734
  && this._tokenStore === tokenStore
4708
4735
  && !this._closing);
4709
- if (this._gatewayCandidates.length > 0)
4736
+ if (sameIssuer && this._gatewayCandidates.length > 0)
4710
4737
  return [...this._gatewayCandidates];
4711
- if (this._gatewayUrl) {
4738
+ if (sameIssuer && this._gatewayUrl) {
4712
4739
  this._gatewayCandidates = [this._gatewayUrl];
4713
4740
  return [...this._gatewayCandidates];
4714
4741
  }
4742
+ if (!sameIssuer) {
4743
+ const cached = this._peerGatewayCache.get(issuerDomain);
4744
+ if (cached)
4745
+ return [...cached];
4746
+ }
4715
4747
  try {
4716
4748
  const loadMetadata = tokenStore.loadMetadata;
4717
4749
  const metadata = typeof loadMetadata === 'function'
@@ -4723,16 +4755,19 @@ export class AUNClient {
4723
4755
  const cachedGateway = cachedList[0]
4724
4756
  ?? (isJsonObject(metadata) ? String(metadata.gateway_url ?? '').trim() : '');
4725
4757
  if (cachedGateway) {
4726
- this._gatewayCandidates = [...new Set([cachedGateway, ...cachedList])];
4727
- this._gatewayUrl = this._gatewayCandidates[0];
4728
- return [...this._gatewayCandidates];
4758
+ const gatewayUrls = [...new Set([cachedGateway, ...cachedList])];
4759
+ if (sameIssuer) {
4760
+ this._gatewayCandidates = gatewayUrls;
4761
+ this._gatewayUrl = gatewayUrls[0];
4762
+ }
4763
+ else
4764
+ this._peerGatewayCache.set(issuerDomain, gatewayUrls);
4765
+ return [...gatewayUrls];
4729
4766
  }
4730
4767
  }
4731
4768
  catch {
4732
4769
  // 缓存读取失败不影响发现流程。
4733
4770
  }
4734
- const dotIdx = resolvedAid.indexOf('.');
4735
- const issuerDomain = dotIdx >= 0 ? resolvedAid.slice(dotIdx + 1) : resolvedAid;
4736
4771
  const portSuffix = this._configModel.discoveryPort ? `:${this._configModel.discoveryPort}` : '';
4737
4772
  const aidUrl = `https://${resolvedAid}${portSuffix}/.well-known/aun-gateway`;
4738
4773
  const gatewayDomainUrl = `https://gateway.${issuerDomain}${portSuffix}/.well-known/aun-gateway`;
@@ -4752,9 +4787,13 @@ export class AUNClient {
4752
4787
  const gatewayUrls = [...new Set(discovered.map((item) => String(item ?? '').trim()).filter(Boolean))];
4753
4788
  if (gatewayUrls.length === 0)
4754
4789
  throw new ValidationError('gateway discovery returned no candidates');
4755
- this._gatewayCandidates = gatewayUrls;
4756
4790
  const gateway = gatewayUrls[0];
4757
- this._gatewayUrl = gateway;
4791
+ if (sameIssuer) {
4792
+ this._gatewayCandidates = gatewayUrls;
4793
+ this._gatewayUrl = gateway;
4794
+ }
4795
+ else
4796
+ this._peerGatewayCache.set(issuerDomain, gatewayUrls);
4758
4797
  try {
4759
4798
  const saveMetadata = tokenStore.saveMetadata;
4760
4799
  if (typeof saveMetadata === 'function') {