@agentunion/fastaun 0.5.27 → 0.5.29

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 (77) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/_packed_docs/CHANGELOG.md +55 -0
  3. package/_packed_docs/INDEX.md +23 -3
  4. package/_packed_docs/KITE_DOCS_GUIDE.md +15 -11
  5. package/_packed_docs/sdk/0.5.28-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +228 -0
  6. package/_packed_docs/sdk/0.5.28-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +146 -0
  7. package/_packed_docs/sdk/0.5.29-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +180 -0
  8. package/_packed_docs/sdk/0.5.29-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +164 -0
  9. package/_packed_docs/sdk/09-/347/273/204/347/273/207/346/234/215/345/212/241RPC/346/211/213/345/206/214.md +90 -90
  10. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +6 -4
  11. package/_packed_docs/sdk/INDEX.md +14 -4
  12. package/_packed_docs/sdk//345/207/255/350/257/201/344/270/216/347/273/204/347/273/207/346/234/215/345/212/241SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +1 -1
  13. package/_packed_docs/sdk//345/207/255/350/257/201/344/270/216/347/273/204/347/273/207/346/234/215/345/212/241SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +1 -1
  14. package/_packed_docs//346/216/247/345/210/266/346/266/210/346/201/257/344/270/216/350/256/276/345/244/207/350/203/275/345/212/233/345/215/217/345/225/206/346/226/271/346/241/210.md +426 -0
  15. package/_packed_docs//346/266/210/346/201/257/345/220/214/346/255/245/344/270/216/345/216/206/345/217/262/346/213/211/345/217/226/344/273/243/347/240/201/345/256/241/346/237/245/351/227/256/351/242/230/346/270/205/345/215/225.md +279 -229
  16. package/_packed_docs//351/243/216/346/216/247/345/223/215/345/272/224/344/270/216SDK/345/215/217/345/220/214/346/226/271/346/241/210.md +381 -0
  17. package/dist/agent-md-changes.d.ts +30 -0
  18. package/dist/agent-md-changes.js +168 -0
  19. package/dist/agent-md-changes.js.map +1 -0
  20. package/dist/agent-md-schema.d.ts +1 -0
  21. package/dist/agent-md-schema.js +29 -12
  22. package/dist/agent-md-schema.js.map +1 -1
  23. package/dist/agent-md.d.ts +9 -0
  24. package/dist/agent-md.js +107 -36
  25. package/dist/agent-md.js.map +1 -1
  26. package/dist/aid-store.d.ts +14 -1
  27. package/dist/aid-store.js +163 -61
  28. package/dist/aid-store.js.map +1 -1
  29. package/dist/auth.d.ts +10 -0
  30. package/dist/auth.js +44 -4
  31. package/dist/auth.js.map +1 -1
  32. package/dist/client/control-messages.d.ts +31 -0
  33. package/dist/client/control-messages.js +496 -0
  34. package/dist/client/control-messages.js.map +1 -0
  35. package/dist/client/delivery.js +12 -3
  36. package/dist/client/delivery.js.map +1 -1
  37. package/dist/client/group-state.js +2 -3
  38. package/dist/client/group-state.js.map +1 -1
  39. package/dist/client/lifecycle.js +8 -0
  40. package/dist/client/lifecycle.js.map +1 -1
  41. package/dist/client/rpc-pipeline.js +76 -13
  42. package/dist/client/rpc-pipeline.js.map +1 -1
  43. package/dist/client/v2-e2ee.d.ts +3 -0
  44. package/dist/client/v2-e2ee.js +13 -4
  45. package/dist/client/v2-e2ee.js.map +1 -1
  46. package/dist/client.d.ts +27 -1
  47. package/dist/client.js +348 -38
  48. package/dist/client.js.map +1 -1
  49. package/dist/discovery.d.ts +3 -0
  50. package/dist/discovery.js +53 -2
  51. package/dist/discovery.js.map +1 -1
  52. package/dist/events.js +14 -1
  53. package/dist/events.js.map +1 -1
  54. package/dist/ownership-v21.d.ts +2 -1
  55. package/dist/ownership-v21.js +17 -3
  56. package/dist/ownership-v21.js.map +1 -1
  57. package/dist/ownership-workflow.js +9 -5
  58. package/dist/ownership-workflow.js.map +1 -1
  59. package/dist/risk-control.d.ts +68 -0
  60. package/dist/risk-control.js +344 -0
  61. package/dist/risk-control.js.map +1 -0
  62. package/dist/semantic-facade.js +4 -2
  63. package/dist/semantic-facade.js.map +1 -1
  64. package/dist/semantic-protocol.js +2 -0
  65. package/dist/semantic-protocol.js.map +1 -1
  66. package/dist/storage/lowlevel.d.ts +1 -0
  67. package/dist/storage/lowlevel.js +2 -2
  68. package/dist/storage/lowlevel.js.map +1 -1
  69. package/dist/tools/cross-sdk-agent.d.ts +1 -1
  70. package/dist/tools/cross-sdk-agent.js +16 -34
  71. package/dist/tools/cross-sdk-agent.js.map +1 -1
  72. package/dist/transport.d.ts +21 -1
  73. package/dist/transport.js +199 -20
  74. package/dist/transport.js.map +1 -1
  75. package/dist/version.d.ts +1 -1
  76. package/dist/version.js +1 -1
  77. package/package.json +8 -8
package/dist/client.js CHANGED
@@ -27,6 +27,7 @@ import { validateAIDFormat, validateGroupIDFormat } from './validators.js';
27
27
  import { RPCTransport } from './transport.js';
28
28
  import { AuthFlow } from './auth.js';
29
29
  import { AgentMdManager, buildDefaultAgentMd } from './agent-md.js';
30
+ import { AgentMdChangeCoordinator } from './agent-md-changes.js';
30
31
  import { GroupIndexMetaCache } from './group-index.js';
31
32
  import { SeqTracker } from './seq-tracker.js';
32
33
  import { ClientRuntime } from './client/runtime.js';
@@ -36,6 +37,8 @@ import { IdentityRuntimeManager } from './client/identity.js';
36
37
  import { LifecycleController } from './client/lifecycle.js';
37
38
  import { PeerDirectory } from './client/peers.js';
38
39
  import { RpcPipeline } from './client/rpc-pipeline.js';
40
+ import { RiskController } from './risk-control.js';
41
+ import { ControlMessageCoordinator, messageClassOf } from './client/control-messages.js';
39
42
  import { CollabClient } from './collab/client.js';
40
43
  import { DebugFacade, CredentialFacade, OrgFacade, GroupFacade, MailFacade, MessageFacade, StreamFacade } from './facades.js';
41
44
  import { StorageVFS } from './storage/vfs.js';
@@ -48,7 +51,7 @@ import { isJsonObject, ConnectionState, STATE_TO_PUBLIC, } from './types.js';
48
51
  import { AID } from './aid.js';
49
52
  import { certFingerprint, certMatchesFingerprint, normalizeFingerprintHex, publicKeyMatchesFingerprint } from './cert-utils.js';
50
53
  import { validateAgentMdCertificate, validateAgentMdDocument } from './agent-md-schema.js';
51
- import { OwnershipFiles, OwnershipStorageAdapter, ownershipAid, ownershipFailure } from './ownership-v21.js';
54
+ import { OwnershipFiles, OwnershipStorageAdapter, ownershipAid, ownershipFailure, ownershipSha256, parseOwnershipFile } from './ownership-v21.js';
52
55
  import { OwnershipWorkflow, OWNERSHIP_SDK_MESSAGE_TYPE, parseOwnershipMessage } from './ownership-workflow.js';
53
56
  import { canonicalJson, createCredential, createMemberCredential, signMembershipBinding, digest, validateMembershipBinding, verifyMembershipBinding, verifyCredentialSignature, createRepresentationProof, verifyServiceEvent, buildRequestBinding, validateRequestBinding, CredentialRenewal } from './org-protocol.js';
54
57
  import { SemanticProtocolError } from './semantic-protocol.js';
@@ -342,7 +345,7 @@ function createAgentMdManagerForRuntime(opts) {
342
345
  return new AgentMdManager({
343
346
  aunPath: opts.config().aunPath,
344
347
  verifySsl: opts.config().verifySsl,
345
- onChanged: (aid) => opts.dispatcher().enqueue('agentmd.changed', aid),
348
+ onChanged: (aid) => opts.dispatcher().enqueue('agentmd.changed', { aid }),
346
349
  discoveryPort: opts.config().discoveryPort,
347
350
  uploadViaGatewayHost: true,
348
351
  logger: opts.logger(),
@@ -486,6 +489,7 @@ export class AUNClient {
486
489
  _ownershipStorage = null;
487
490
  _ownershipFiles = null;
488
491
  _ownershipWorkflow = null;
492
+ _ownershipConsensusCache = new Map();
489
493
  _getOwnershipStorage() {
490
494
  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) });
491
495
  }
@@ -497,6 +501,10 @@ export class AUNClient {
497
501
  currentAid: () => { if (!this._currentAid)
498
502
  throw ownershipFailure('forbidden', 'ownership requires a loaded identity'); return this._currentAid; },
499
503
  currentType: () => this.getType(),
504
+ currentExttype: () => {
505
+ const metadata = this._tokenStore.loadMetadata?.(String(this._aid ?? ''));
506
+ return String(metadata?.identity_exttype ?? metadata?.registration_intent?.exttype ?? '');
507
+ },
500
508
  now: () => Math.trunc(Date.now() / 1000),
501
509
  readAgentMdFresh,
502
510
  submitAgentMdRaw: (subAid, raw, condition, assertActive, stage) => this._agentMdManager.submitOwnershipRaw(subAid, raw, condition, assertActive, stage),
@@ -512,40 +520,59 @@ export class AUNClient {
512
520
  currentType: () => this.getType(), assertCurrent: () => this._ownershipOperationGuard?.(),
513
521
  recoverRequest: (operationId) => this._recoverOwnershipRequest(operationId),
514
522
  send: (to, payload, messageId) => this.call('message.send', { to, encrypt: true, message_id: messageId, payload: payload }),
515
- emit: (event, payload) => this._dispatcher.enqueue(event, payload),
523
+ emit: (event, payload) => { this._ownershipConsensusCache = new Map(); this._dispatcher.enqueue(event, payload); },
516
524
  });
517
525
  }
518
526
  async _recoverOwnershipRequest(operationId) {
519
527
  const identity = this._aid, version = this._serviceEventIdentityVersion;
520
528
  const current = () => { if (this._aid !== identity || this._serviceEventIdentityVersion !== version)
521
529
  throw ownershipFailure('conflict', 'ownership request identity changed'); };
522
- const catalog = await this.call('message.peers', { filter: 'all', limit: 200 });
523
- current();
524
- for (const peer of Array.isArray(catalog.peers) ? catalog.peers.slice(0, 200) : []) {
525
- const sender = String(peer.sender_aid ?? '');
526
- const history = await this.call('message.history', { sender_aid: sender, before_seq: Number.MAX_SAFE_INTEGER, limit: 200 });
530
+ const query = { filter: 'all', limit: 200 }, tokens = new Set();
531
+ for (;;) {
532
+ const catalog = await this.call('message.peers', query);
527
533
  current();
528
- for (const row of Array.isArray(history.messages) ? history.messages : []) {
529
- const envelope = row, content = envelope.payload ?? envelope;
530
- const raw = decryptedOwnershipJsonText(content) ?? content;
531
- let value = raw;
532
- if (typeof raw === 'string') {
533
- try {
534
- value = JSON.parse(raw);
535
- }
536
- catch {
537
- continue;
534
+ for (const peer of Array.isArray(catalog.peers) ? catalog.peers : []) {
535
+ const sender = String(peer.sender_aid ?? ''), head = Number(peer.head_seq);
536
+ const snapshot = Number.isSafeInteger(head) && head > 0 && head < Number.MAX_SAFE_INTEGER ? { snapshot_head_seq: head } : {};
537
+ let before = snapshot.snapshot_head_seq === undefined ? Number.MAX_SAFE_INTEGER : head + 1;
538
+ for (;;) {
539
+ const history = await this.call('message.history', { sender_aid: sender, before_seq: before, ...snapshot, limit: 200 });
540
+ current();
541
+ for (const row of Array.isArray(history.messages) ? history.messages : []) {
542
+ const envelope = row, content = envelope.payload ?? envelope;
543
+ const raw = decryptedOwnershipJsonText(content) ?? content;
544
+ let value = raw;
545
+ if (typeof raw === 'string') {
546
+ try {
547
+ value = JSON.parse(raw);
548
+ }
549
+ catch {
550
+ continue;
551
+ }
552
+ }
553
+ if (!isJsonObject(value) || value.type !== OWNERSHIP_SDK_MESSAGE_TYPE || value.operation_id !== operationId || !['bind.request', 'transfer.request'].includes(String(value.action)))
554
+ continue;
555
+ const authenticated = String(envelope.from_aid ?? envelope.from ?? envelope.sender_aid ?? '');
556
+ if (authenticated !== sender)
557
+ throw ownershipFailure('forbidden', 'ownership history sender changed');
558
+ return { from: authenticated, payload: parseOwnershipMessage(raw, { authenticated_from_aid: authenticated, local_aid: identity }) };
538
559
  }
560
+ if (history.has_older !== true)
561
+ break;
562
+ const next = Number(history.next_before_seq);
563
+ if (!Number.isSafeInteger(next) || next <= 0 || next >= before)
564
+ throw ownershipFailure('unavailable', 'ownership history pagination did not advance');
565
+ before = next;
539
566
  }
540
- if (!isJsonObject(value) || value.type !== OWNERSHIP_SDK_MESSAGE_TYPE || value.operation_id !== operationId || !['bind.request', 'transfer.request'].includes(String(value.action)))
541
- continue;
542
- const authenticated = String(envelope.from_aid ?? envelope.from ?? envelope.sender_aid ?? '');
543
- if (authenticated !== sender)
544
- throw ownershipFailure('forbidden', 'ownership history sender changed');
545
- return { from: authenticated, payload: parseOwnershipMessage(raw, { authenticated_from_aid: authenticated, local_aid: identity }) };
546
567
  }
568
+ if (catalog.has_more !== true)
569
+ return null;
570
+ const token = catalog.next_page_token;
571
+ if (typeof token !== 'string' || !token || tokens.has(token))
572
+ throw ownershipFailure('unavailable', 'peer directory pagination did not advance');
573
+ tokens.add(token);
574
+ query.page_token = token;
547
575
  }
548
- return null;
549
576
  }
550
577
  _restoreIdentityType() {
551
578
  const aid = String(this._aid ?? '').trim();
@@ -929,6 +956,20 @@ export class AUNClient {
929
956
  _v2KeyStore;
930
957
  _v2SessionInitInFlight = null;
931
958
  _v2RuntimeGeneration = 0;
959
+ _controlMessageCoordinator;
960
+ _agentMdChanges;
961
+ _installAgentMdChangeHooks() {
962
+ this._agentMdManager.setOnCommitted((aid, notice) => this._getAgentMdChanges().committed(aid, notice));
963
+ }
964
+ _getAgentMdChanges() {
965
+ return this._agentMdChanges ??= new AgentMdChangeCoordinator(this);
966
+ }
967
+ _onSdkControl(protocol, version, payload, senderAid) {
968
+ return this._getAgentMdChanges().handle(protocol, version, payload, senderAid);
969
+ }
970
+ _controlMessages() {
971
+ return this._controlMessageCoordinator ??= new ControlMessageCoordinator(this);
972
+ }
932
973
  /** V2 bootstrap 缓存:aid/group:id → 设备列表 + 时间戳 */
933
974
  _v2BootstrapCache = new Map();
934
975
  _v2SigCache = new Map();
@@ -949,6 +990,27 @@ export class AUNClient {
949
990
  _reconnectTask = null;
950
991
  _reconnectEventDispatchDepth = 0;
951
992
  _gatewayConnectionId = '';
993
+ _negotiatedCapabilities = {};
994
+ _riskNegotiated = false;
995
+ _riskRestoredAid = '';
996
+ _riskEvents = [];
997
+ _riskApplying = false;
998
+ _riskApplyTail = Promise.resolve();
999
+ _riskScalarReconnectAt = 0;
1000
+ _riskHttpRetryAt = new Map();
1001
+ _risk = new RiskController({
1002
+ persist: async (state) => {
1003
+ if (!this._aid || !this._tokenStore.saveMetadata || !isJsonObject(state.target) || state.target.aid !== this._aid || state.target.device_id !== this._deviceId)
1004
+ throw new Error('risk persistence unavailable');
1005
+ this._tokenStore.saveMetadata(this._aid, { ...(this._tokenStore.loadMetadata?.(this._aid) ?? {}), [`risk_state_v1:${this._deviceId}`]: state });
1006
+ },
1007
+ onState: (event) => {
1008
+ if (this._riskApplying)
1009
+ this._riskEvents.push(event);
1010
+ else
1011
+ this._dispatcher.enqueue('risk_state_changed', event);
1012
+ },
1013
+ });
952
1014
  _migrationPending = false;
953
1015
  _migrationRevision = new Map();
954
1016
  _serverKicked = false;
@@ -1021,6 +1083,7 @@ export class AUNClient {
1021
1083
  this._ownershipStorage = null;
1022
1084
  this._ownershipFiles = null;
1023
1085
  this._ownershipWorkflow = null;
1086
+ this._ownershipConsensusCache = new Map();
1024
1087
  this._slotId = inputAid?.slotId || 'default';
1025
1088
  this._connectDeliveryMode = normalizeDeliveryModeConfig({ mode: 'fanout' });
1026
1089
  this._defaultConnectDeliveryMode = { ...this._connectDeliveryMode };
@@ -1059,6 +1122,7 @@ export class AUNClient {
1059
1122
  fetchEntityIdentity: (target, type) => this._freshEntityCertificate(target, type),
1060
1123
  fetchEntitySigner: (target, fingerprint) => this._freshEntitySigner(target, fingerprint),
1061
1124
  });
1125
+ this._installAgentMdChangeHooks();
1062
1126
  this._transport = new RPCTransport({
1063
1127
  eventDispatcher: this._dispatcher,
1064
1128
  timeout: 35_000,
@@ -1066,6 +1130,8 @@ export class AUNClient {
1066
1130
  logger: this._logger.for('aun_core.transport'),
1067
1131
  dnsNet,
1068
1132
  });
1133
+ this._installRiskHooks();
1134
+ this._transport.setClassMessageHandler((event, payload) => this._controlMessages().onPush(payload, event.includes('group.')));
1069
1135
  this._transport.setProtocolMetaObserver((meta) => this._observeRpcMeta(meta));
1070
1136
  this._transport.setServiceEventVerifier((event, payload) => this._verifyIncomingServiceEvent(event, payload), () => this._identity?.aid ?? '', () => this._serviceEventIdentityVersion);
1071
1137
  this._transport.setProtocolDisconnectCallback((err, closeCode) => this._handleTransportDisconnect(err, closeCode));
@@ -1100,6 +1166,8 @@ export class AUNClient {
1100
1166
  this._dispatcher.subscribeProtocol('_raw.message.read_changed', (data) => this._onRawReadChanged(data, 'p2p'));
1101
1167
  // V2 P2P 推送通知:收到通知后自动走 message.v2.pull 拉取并解密
1102
1168
  this._dispatcher.subscribeProtocol('_raw.peer.v2.message_received', (data) => {
1169
+ if (this._controlMessages().onPush(data))
1170
+ return;
1103
1171
  const sender = isJsonObject(data) ? String(data.sender_aid ?? '') : '';
1104
1172
  const ns = this._p2pPeerPullEnabled ? (sender ? this._delivery.peerNamespace(sender) : '') : this._aid ? `p2p:${this._aid}` : '';
1105
1173
  const hasInline = isJsonObject(data)
@@ -1111,6 +1179,8 @@ export class AUNClient {
1111
1179
  });
1112
1180
  // V2 群组消息推送通知:收到通知后自动走 group.v2.pull 拉取并解密
1113
1181
  this._dispatcher.subscribeProtocol('_raw.group.v2.message_created', (data) => {
1182
+ if (this._controlMessages().onPush(data, true))
1183
+ return;
1114
1184
  const groupId = isJsonObject(data)
1115
1185
  ? String(data.group_id ?? '').trim()
1116
1186
  : '';
@@ -1290,11 +1360,48 @@ export class AUNClient {
1290
1360
  }
1291
1361
  async releaseSubaid(subAid) { return this._getOwnershipWorkflow().releaseSubaid(subAid); }
1292
1362
  async transferSubaid(subAid, newOwnerAid) { return this._getOwnershipWorkflow().transferSubaid(subAid, newOwnerAid); }
1293
- async checkOwnershipConsensus(subAid) {
1294
- return this._createOwnershipFiles((aid) => this._agentMdManager._readOwnershipQuery(aid)).checkConsensus(ownershipAid(subAid ?? this._aid, 'sub_aid'));
1363
+ async checkOwnershipConsensus(subAid, force = false) {
1364
+ const target = ownershipAid(subAid ?? this._aid, 'sub_aid');
1365
+ const record = this._agentMdManager.loadRecord(target);
1366
+ const observedEtag = String(record?.remote_etag ?? '');
1367
+ const cache = this._ownershipConsensusCache ??= new Map();
1368
+ const cached = cache.get(target);
1369
+ if (!force && cached?.result.status === 'consensus' && record?.remote_status !== 'missing'
1370
+ && (observedEtag === cached.observedEtag || observedEtag === cached.result.agent_md_etag)) {
1371
+ cached.observedEtag = observedEtag;
1372
+ return { ...cached.result };
1373
+ }
1374
+ cache.delete(target);
1375
+ const result = { status: 'unavailable', sub_aid: target, owner_aid: null, relation_version: 0, lock: 'unlocked', agent_md_etag: '', agent_md_sha256: '' };
1376
+ const entry = { observedEtag, result: { ...result } };
1377
+ cache.set(target, entry);
1378
+ try {
1379
+ const snapshot = await this._agentMdManager.readOwnershipFresh(target);
1380
+ Object.assign(result, { relation_version: snapshot.relation_version, lock: snapshot.lock, agent_md_etag: snapshot.etag,
1381
+ agent_md_sha256: snapshot.exists ? ownershipSha256(snapshot.content) : '' });
1382
+ if (snapshot.ownership_status)
1383
+ result.ownership_status = snapshot.ownership_status;
1384
+ const statement = snapshot.exists ? parseOwnershipFile(snapshot.content, 'agent', { expectedSubAid: target }).statement : undefined;
1385
+ if (snapshot.relation_version !== (statement?.relation_version ?? 0) || snapshot.lock === 'locked' && !statement
1386
+ || snapshot.ownership_status === 'pending' && snapshot.lock !== 'unlocked')
1387
+ throw ownershipFailure('unavailable', 'NS relationship metadata does not match agent.md', true);
1388
+ result.status = snapshot.ownership_status === 'pending' ? 'single_sided' : statement ? snapshot.lock === 'locked' ? 'consensus' : 'released' : 'unbound';
1389
+ result.owner_aid = result.status === 'consensus' ? statement.owner_aid : null;
1390
+ }
1391
+ catch (error) {
1392
+ const code = error instanceof StateError && error.stringCode ? error.stringCode : error instanceof ValidationError ? 'invalid' : 'unavailable';
1393
+ result.status = code === 'not_applicable' ? 'not_applicable' : code === 'invalid' ? 'invalid' : 'unavailable';
1394
+ result.owner_aid = null;
1395
+ result.code = code;
1396
+ result.reason = error instanceof Error ? error.message : String(error);
1397
+ result.retryable = result.status === 'unavailable';
1398
+ }
1399
+ if (cache.get(target) === entry)
1400
+ entry.result = { ...result };
1401
+ return result;
1295
1402
  }
1296
1403
  async _checkOwnershipConsensusFresh(subAid) {
1297
- return this._getOwnershipWorkflow().checkOwnershipConsensus(subAid);
1404
+ return this.checkOwnershipConsensus(subAid, true);
1298
1405
  }
1299
1406
  async bindAid(subjectAid, options = {}) {
1300
1407
  throw ownershipFailure('unsupported_protocol', 'legacy bindAid is unsupported; use bindSubaid');
@@ -1391,6 +1498,12 @@ export class AUNClient {
1391
1498
  return session.aid === this._aid && session.deviceId === this._deviceId;
1392
1499
  }
1393
1500
  _resetV2IdentityRuntime() {
1501
+ this._controlMessageCoordinator?.close();
1502
+ this._agentMdChanges?.close();
1503
+ this._agentMdChanges = undefined;
1504
+ this._negotiatedCapabilities = {};
1505
+ this._riskNegotiated = false;
1506
+ this._riskRestoredAid = '';
1394
1507
  this._v2RuntimeGeneration += 1;
1395
1508
  const keyStore = this._v2KeyStore;
1396
1509
  try {
@@ -1473,6 +1586,7 @@ export class AUNClient {
1473
1586
  this._ownershipStorage = null;
1474
1587
  this._ownershipFiles = null;
1475
1588
  this._ownershipWorkflow = null;
1589
+ this._ownershipConsensusCache = new Map();
1476
1590
  this._auth = new AuthFlow({
1477
1591
  tokenStore,
1478
1592
  crypto: new CryptoProvider(),
@@ -1506,6 +1620,7 @@ export class AUNClient {
1506
1620
  fetchEntityIdentity: (target, type) => this._freshEntityCertificate(target, type),
1507
1621
  fetchEntitySigner: (target, fingerprint) => this._freshEntitySigner(target, fingerprint),
1508
1622
  });
1623
+ this._installAgentMdChangeHooks();
1509
1624
  this._transport = new RPCTransport({
1510
1625
  eventDispatcher: this._dispatcher,
1511
1626
  timeout: 35_000,
@@ -1513,6 +1628,8 @@ export class AUNClient {
1513
1628
  logger: this._logger.for('aun_core.transport'),
1514
1629
  dnsNet,
1515
1630
  });
1631
+ this._installRiskHooks();
1632
+ this._transport.setClassMessageHandler((event, payload) => this._controlMessages().onPush(payload, event.includes('group.')));
1516
1633
  this._transport.setProtocolMetaObserver((meta) => this._observeRpcMeta(meta));
1517
1634
  this._transport.setServiceEventVerifier((event, payload) => this._verifyIncomingServiceEvent(event, payload), () => this._identity?.aid ?? '', () => this._serviceEventIdentityVersion);
1518
1635
  this._transport.setProtocolDisconnectCallback((err, closeCode) => this._handleTransportDisconnect(err, closeCode));
@@ -1616,6 +1733,7 @@ export class AUNClient {
1616
1733
  }
1617
1734
  /** 关闭连接 */
1618
1735
  async close() {
1736
+ this._risk.close();
1619
1737
  this._peerGatewayCache.clear();
1620
1738
  return this._lifecycle.close();
1621
1739
  }
@@ -1626,6 +1744,155 @@ export class AUNClient {
1626
1744
  async disconnect() {
1627
1745
  return this._lifecycle.disconnect();
1628
1746
  }
1747
+ get riskState() { return Object.freeze(structuredClone(this._risk.state)); }
1748
+ _installRiskHooks() {
1749
+ this._transport.setRiskControlHandler((payload) => this._onRiskControl(payload));
1750
+ this._transport.setRequestAdmission((request) => this._risk.acquire(request));
1751
+ this._transport.setRequestCheck((request) => this._risk.check(request));
1752
+ this._auth.setRequestAdmission((request) => this._risk.acquire(request));
1753
+ this._auth.setRiskSupport(() => this._gatewayUrl?.startsWith('wss://') === true);
1754
+ this._auth.setHttpHandler((url, init, perform) => this._riskFetch(url, init, perform, 'http:gateway:pki'));
1755
+ this._discovery.setHttpHandler((url, init, perform) => this._riskFetch(url, init, perform, new URL(url).pathname === '/health' ? 'http:gateway:health' : 'http:discovery:gateway'));
1756
+ const aid = this._aid;
1757
+ if (aid)
1758
+ this._aidStore?.setRiskController(aid, async () => {
1759
+ if (this._aid !== aid || this._deviceId !== this._aidStore?.deviceId)
1760
+ return null;
1761
+ await this._restoreRiskState();
1762
+ return this._risk.appliesTo(aid, this._deviceId) ? this._risk : null;
1763
+ });
1764
+ this._agentMdManager.setHttpHandler((url, init, perform) => {
1765
+ const headers = new Headers(init.headers);
1766
+ const scope = headers.has('X-AUN-Ownership-Check') || headers.has('X-AUN-Ownership-Stage') ? 'http:nameservice:ownership' : 'http:nameservice:agent_md';
1767
+ return this._riskFetch(url, init, perform, scope);
1768
+ });
1769
+ }
1770
+ async _restoreRiskState() {
1771
+ const aid = this._aid;
1772
+ if (!aid || this._riskRestoredAid === aid)
1773
+ return;
1774
+ this._risk.reset();
1775
+ this._riskHttpRetryAt.clear();
1776
+ this._riskScalarReconnectAt = 0;
1777
+ const raw = this._tokenStore.loadMetadata?.(aid)?.[`risk_state_v1:${this._deviceId}`];
1778
+ if (raw) {
1779
+ try {
1780
+ const state = typeof raw === 'string' ? JSON.parse(raw) : raw;
1781
+ if (!isJsonObject(state) || !isJsonObject(state.target) || state.target.aid !== aid || state.target.device_id !== this._deviceId || !isJsonObject(state.signing_keys))
1782
+ throw new Error('risk state invalid');
1783
+ const [keyId, key] = Object.entries(state.signing_keys)[0] ?? [];
1784
+ if (!keyId || !Array.isArray(key))
1785
+ throw new Error('risk signing key unavailable');
1786
+ this._risk.configure({ aid, device_id: this._deviceId, slot_id: this._slotId, connection_id: '' }, { key_id: keyId, public_key: Buffer.from(key).toString('base64url'), algorithm: 'ECDSA-P256-SHA256' }, Number(state.server_time_ms));
1787
+ await this._risk.restore(state);
1788
+ }
1789
+ catch {
1790
+ throw new StateError('persisted risk state could not be verified', { stringCode: 'risk_state_unavailable', retryable: true });
1791
+ }
1792
+ }
1793
+ this._riskRestoredAid = aid;
1794
+ }
1795
+ _assertRiskReconnect() {
1796
+ const delay = this._riskReconnectDelay();
1797
+ if (delay > 0)
1798
+ throw new ConnectionError('connection postponed by risk policy', { stringCode: 'risk_restricted', retryable: true, data: { retry_after_ms: Math.ceil(delay) } });
1799
+ }
1800
+ _riskReconnectDelay() { return Math.max(this._risk.reconnectDelayMs(), this._riskScalarReconnectAt - (typeof performance !== 'undefined' ? performance.now() : Date.now()), 0); }
1801
+ async _applyRiskHello(hello) {
1802
+ this._riskNegotiated = false;
1803
+ if (!isJsonObject(hello.negotiated_capabilities) || hello.negotiated_capabilities.risk_test_v1 !== true)
1804
+ return;
1805
+ if (!this._gatewayUrl?.startsWith('wss://') || !this._configModel.verifySsl || !this._aid || !isJsonObject(hello.risk_signing_key))
1806
+ return;
1807
+ const key = hello.risk_signing_key;
1808
+ const initial = isJsonObject(hello.risk_state) ? hello.risk_state : {};
1809
+ this._risk.configure({ aid: this._aid, device_id: this._deviceId, slot_id: this._slotId, connection_id: this._gatewayConnectionId }, { key_id: String(key.key_id ?? ''), public_key: String(key.public_key ?? ''), algorithm: String(key.algorithm ?? '') }, Number(hello.server_time_ms ?? initial.server_time_ms));
1810
+ this._riskNegotiated = true;
1811
+ await this._transport.setRiskSnapshot(this._risk.exportState(true));
1812
+ if (Array.isArray(initial.states))
1813
+ for (const state of initial.states)
1814
+ if (isJsonObject(state))
1815
+ await this._onRiskControl(state);
1816
+ }
1817
+ async _onRiskControl(payload) {
1818
+ const identity = JSON.stringify([this._aid, this._deviceId, this._slotId, this._serviceEventIdentityVersion, this._transport._connectionGeneration]);
1819
+ const run = this._riskApplyTail.then(async () => {
1820
+ const current = () => identity === JSON.stringify([this._aid, this._deviceId, this._slotId, this._serviceEventIdentityVersion, this._transport._connectionGeneration]);
1821
+ if (current())
1822
+ await this._applyRiskControl(payload, current);
1823
+ });
1824
+ this._riskApplyTail = run.catch(() => { });
1825
+ await run;
1826
+ }
1827
+ async _applyRiskControl(payload, current) {
1828
+ if (!this._riskNegotiated)
1829
+ return;
1830
+ this._riskApplying = true;
1831
+ let status;
1832
+ try {
1833
+ status = await this._risk.apply(payload);
1834
+ }
1835
+ catch {
1836
+ this._riskApplying = false;
1837
+ this._riskEvents = [];
1838
+ return;
1839
+ }
1840
+ this._riskApplying = false;
1841
+ await this._transport.setRiskSnapshot(this._risk.exportState(true));
1842
+ if (!current()) {
1843
+ this._riskEvents = [];
1844
+ return;
1845
+ }
1846
+ const params = { version: 1, delivery_id: payload.delivery_id, subject: payload.subject, revision: payload.revision, status: this._risk.persistenceFailed ? 'failed' : status };
1847
+ if (this._risk.persistenceFailed)
1848
+ params.error_code = 'persistence_unavailable';
1849
+ const acknowledgement = this._transport.call('gateway.risk_ack', params, 500).catch(() => { });
1850
+ for (const event of this._riskEvents.splice(0))
1851
+ this._dispatcher.enqueue('risk_state_changed', event);
1852
+ if (this._risk.shouldDisconnect()) {
1853
+ await acknowledgement;
1854
+ if (current())
1855
+ void this._handleTransportDisconnect(new ConnectionError('connection restricted by risk policy', { code: 4029, stringCode: 'risk_restricted', retryable: true }), 4029);
1856
+ }
1857
+ }
1858
+ async _riskFetch(input, init = {}, perform = fetch, requestScope) {
1859
+ await this._restoreRiskState();
1860
+ const url = new URL(input);
1861
+ const route = `${url.origin}${url.pathname}`;
1862
+ const monotonicNow = () => typeof performance !== 'undefined' ? performance.now() : Date.now();
1863
+ const retryAt = this._riskHttpRetryAt.get(route) ?? 0;
1864
+ if (retryAt > monotonicNow())
1865
+ throw new StateError('HTTP request postponed by risk response', { stringCode: 'risk_restricted', retryable: true, data: { retry_after_ms: Math.ceil(retryAt - monotonicNow()) } });
1866
+ const release = this._risk.acquire(requestScope ?? (['GET', 'HEAD'].includes(String(init.method ?? 'GET').toUpperCase()) ? 'http:storage:read' : 'http:storage:write'));
1867
+ try {
1868
+ const response = await perform(input, init);
1869
+ if (response.status === 429) {
1870
+ try {
1871
+ const body = await response.clone().json();
1872
+ const detail = isJsonObject(body) && isJsonObject(body.error) && isJsonObject(body.error.data) ? body.error.data : isJsonObject(body) && isJsonObject(body.data) ? body.data : isJsonObject(body) ? body : {};
1873
+ const wrapper = detail.risk_state;
1874
+ if (this._riskNegotiated && isJsonObject(wrapper) && Array.isArray(wrapper.states))
1875
+ for (const state of wrapper.states)
1876
+ if (isJsonObject(state))
1877
+ await this._onRiskControl(state);
1878
+ const delayMs = Number(detail.retry_after_ms);
1879
+ if (Number.isSafeInteger(delayMs) && delayMs > 0)
1880
+ this._riskHttpRetryAt.set(route, monotonicNow() + delayMs);
1881
+ }
1882
+ catch { /* 无签名状态的 HTTP 错误由调用入口处理 */ }
1883
+ const header = response.headers.get('Retry-After');
1884
+ if (header) {
1885
+ const delayMs = /^\d+$/.test(header) ? Number(header) * 1000 : Math.max(0, Date.parse(header) - Date.now());
1886
+ if (Number.isFinite(delayMs) && delayMs > 0)
1887
+ this._riskHttpRetryAt.set(route, Math.max(this._riskHttpRetryAt.get(route) ?? 0, monotonicNow() + delayMs));
1888
+ }
1889
+ }
1890
+ return response;
1891
+ }
1892
+ finally {
1893
+ release();
1894
+ }
1895
+ }
1629
1896
  // ── RPC ───────────────────────────────────────────────────
1630
1897
  /**
1631
1898
  * 发送 JSON-RPC 调用。
@@ -1708,7 +1975,7 @@ export class AUNClient {
1708
1975
  }
1709
1976
  async _ensureInitialAgentMd() {
1710
1977
  const aid = this._aid;
1711
- if (!aid || !this._currentAid)
1978
+ if (!aid || !this._currentAid || this.getType() !== 'human')
1712
1979
  return false;
1713
1980
  const observed = await this._agentMdManager.readAgentMdFresh(aid);
1714
1981
  if (observed.exists || String(this._agentMdManager._loadRecord?.(aid, true)?.local_draft_content ?? this._agentMdManager.loadRecord(aid)?.local_draft_content ?? '') || this._agentMdManager.pendingUpload(aid))
@@ -1859,7 +2126,7 @@ export class AUNClient {
1859
2126
  this._aidStore = store;
1860
2127
  let identity = store.load(orgAid);
1861
2128
  if (!identity.ok) {
1862
- const registered = await store.register(orgAid, { type: 'org', owner: owner.aid });
2129
+ const registered = await store._registerIdentity(orgAid, { type: 'org', owner: owner.aid, exttype: String(params.exttype ?? '') });
1863
2130
  if (!registered.ok)
1864
2131
  throw ownershipFailure('unavailable', registered.error.message, true);
1865
2132
  identity = store.load(orgAid);
@@ -2077,7 +2344,7 @@ export class AUNClient {
2077
2344
  return;
2078
2345
  const identity = this._localEntityCertificate(orgAid);
2079
2346
  const content = String(params.org_agent_md ?? buildDefaultAgentMd(orgAid, { type: 'org', owner: owner.aid, entityCertFingerprint: identity.certFingerprint, name: String(params.name ?? orgAid) }));
2080
- const uploaded = await store.uploadAgentMd(orgAid, content, { uploaderAid: owner.aid, entityOwnerResolver: (aid, type) => this._resolveEntityAgentMdOwner(aid, type, bootstrap), entityIdentityGetter: (aid) => this._freshEntityCertificate(aid, 'org') });
2347
+ const uploaded = await store._uploadAgentMd(orgAid, content, { uploaderAid: owner.aid, entityOwnerResolver: (aid, type) => this._resolveEntityAgentMdOwner(aid, type, bootstrap), entityIdentityGetter: (aid) => this._freshEntityCertificate(aid, 'org') }, (aid, notice, manager) => this._getAgentMdChanges().committed(aid, notice, manager));
2081
2348
  if (!uploaded.ok) {
2082
2349
  const code = ['invalid', 'unavailable', 'forbidden', 'conflict', 'persistence_unavailable'].includes(uploaded.error.code) ? uploaded.error.code : uploaded.error.code === 'NETWORK_ERROR' ? 'unavailable' : 'invalid';
2083
2350
  throw ownershipFailure(code, uploaded.error.message, code === 'unavailable');
@@ -2278,6 +2545,7 @@ export class AUNClient {
2278
2545
  aunPath: store.aunPath,
2279
2546
  verifySsl: identityStore._verifySsl,
2280
2547
  logger: identityStore._log.for('aun_core.agent_md'),
2548
+ onCommitted: (aid, notice) => this._getAgentMdChanges().committed(aid, notice, manager),
2281
2549
  uploadViaGatewayHost: true,
2282
2550
  ownerAidGetter: () => identity.aid,
2283
2551
  currentAidGetter: () => identity,
@@ -2305,7 +2573,7 @@ export class AUNClient {
2305
2573
  const bootstrap = initialCertificate ? { aid: groupAid, ownerAid: String(group.owner_aid ?? '').trim(), certFingerprint: certFingerprint(initialCertificate), type: 'group' } : undefined;
2306
2574
  if (await this._resumeGroupAgentMdUpload(store, groupAid, bootstrap))
2307
2575
  return;
2308
- const uploaded = await store.uploadAgentMd(groupAid, this._groupAgentMdContent(groupAid, params, group), { uploaderAid, entityOwnerResolver: (aid, type) => this._resolveEntityAgentMdOwner(aid, type, bootstrap), entityIdentityGetter: (aid) => this._freshEntityCertificate(aid) });
2576
+ const uploaded = await store._uploadAgentMd(groupAid, this._groupAgentMdContent(groupAid, params, group), { uploaderAid, entityOwnerResolver: (aid, type) => this._resolveEntityAgentMdOwner(aid, type, bootstrap), entityIdentityGetter: (aid) => this._freshEntityCertificate(aid) }, (aid, notice, manager) => this._getAgentMdChanges().committed(aid, notice, manager));
2309
2577
  if (!uploaded.ok) {
2310
2578
  const code = ['invalid', 'unavailable', 'forbidden', 'conflict', 'persistence_unavailable'].includes(uploaded.error.code) ? uploaded.error.code : uploaded.error.code === 'NETWORK_ERROR' ? 'unavailable' : 'invalid';
2311
2579
  throw ownershipFailure(code, `group agent.md upload failed: ${uploaded.error.message}`, code === 'unavailable');
@@ -3020,6 +3288,8 @@ export class AUNClient {
3020
3288
  // ── 事件自动解密管线 ──────────────────────────────────────
3021
3289
  /** 处理 transport 层推送的原始 P2P 消息 */
3022
3290
  async _onRawMessageReceived(data) {
3291
+ if (this._controlMessages().onPush(data))
3292
+ return;
3023
3293
  return this._delivery.onRawMessageReceived(data);
3024
3294
  }
3025
3295
  async _onRawMessageRecalled(data) {
@@ -3027,6 +3297,8 @@ export class AUNClient {
3027
3297
  }
3028
3298
  /** 处理群组消息推送:自动解密后 re-publish */
3029
3299
  async _onRawGroupMessageCreated(data) {
3300
+ if (this._controlMessages().onPush(data, true))
3301
+ return;
3030
3302
  return this._delivery.onRawGroupMessageCreated(data);
3031
3303
  }
3032
3304
  async _onRawGroupMessageRecalled(data) {
@@ -3114,6 +3386,8 @@ export class AUNClient {
3114
3386
  this._delivery.markPublishedSeq(ns, seq);
3115
3387
  }
3116
3388
  _publishAppEvent(event, payload, source = 'direct', deliveryContext) {
3389
+ if (messageClassOf(payload) === 'sdk_control')
3390
+ return this._controlMessages().consume(payload);
3117
3391
  if (event === 'message.received') {
3118
3392
  return this._handleOwnershipMessage(payload).then((handled) => {
3119
3393
  if (!handled)
@@ -3596,7 +3870,7 @@ export class AUNClient {
3596
3870
  throw new ValidationError('gateway url unavailable for e2ee cert fetch');
3597
3871
  }
3598
3872
  const peerGatewayUrl = AUNClient._resolvePeerGatewayUrl(gatewayUrl, aid);
3599
- const certPem = await _httpGetText(AUNClient._buildCertUrl(peerGatewayUrl, aid, certFingerprint), this._configModel.verifySsl, timeoutMs);
3873
+ const certPem = await this._auth._fetchRiskText(AUNClient._buildCertUrl(peerGatewayUrl, aid, certFingerprint));
3600
3874
  if (auth !== this._auth || pkiEpoch !== auth._strictPkiEpoch)
3601
3875
  throw new ValidationError('peer certificate fetch context changed');
3602
3876
  const validated = await this._validateAndCachePeerCert({
@@ -4037,6 +4311,9 @@ export class AUNClient {
4037
4311
  // ── 内部:连接 ────────────────────────────────────────────
4038
4312
  /** 执行一次连接流程 */
4039
4313
  async _connectOnce(params, allowReauth, reconnectOwner, connectionOwner) {
4314
+ await this._restoreRiskState();
4315
+ this._assertRiskReconnect();
4316
+ this._riskNegotiated = false;
4040
4317
  const tStart = Date.now();
4041
4318
  connectionOwner ??= this._connectAbort ?? undefined;
4042
4319
  this._lifecycle.assertConnectionAttemptOwner(connectionOwner);
@@ -4046,8 +4323,15 @@ export class AUNClient {
4046
4323
  this._captureCapabilitiesFromConnect(params);
4047
4324
  this._connectDeliveryMode = { ...(params.delivery_mode ?? this._connectDeliveryMode) };
4048
4325
  const extraInfo = (params.extra_info && typeof params.extra_info === 'object' && !Array.isArray(params.extra_info))
4049
- ? params.extra_info
4050
- : undefined;
4326
+ ? { ...params.extra_info }
4327
+ : {};
4328
+ if (typeof this._tokenStore.updateInstanceState === 'function') {
4329
+ extraInfo._capabilities = {
4330
+ ...(isJsonObject(extraInfo._capabilities) ? extraInfo._capabilities : {}),
4331
+ message_classes_v1: { p2p: ['normal', 'control', 'sdk_control'], group: ['normal', 'control', 'sdk_control'] },
4332
+ sdk_control_protocols: { 'aun.agent_md.changed': [1] },
4333
+ };
4334
+ }
4051
4335
  const prevState = this._state;
4052
4336
  this._auth.setInstanceContext({ deviceId: this._deviceId, slotId: this._slotId });
4053
4337
  this._state = 'connecting';
@@ -4097,6 +4381,9 @@ export class AUNClient {
4097
4381
  this._applyServerHeartbeatInterval(auth.hello.heartbeat_interval, 'auth');
4098
4382
  }
4099
4383
  this._gatewayConnectionId = isJsonObject(auth.hello) && isJsonObject(auth.hello.connection) ? String(auth.hello.connection.id ?? '') : '';
4384
+ this._negotiatedCapabilities = isJsonObject(auth.hello) && isJsonObject(auth.hello.negotiated_capabilities) ? auth.hello.negotiated_capabilities : {};
4385
+ if (isJsonObject(auth.hello))
4386
+ await this._applyRiskHello(auth.hello);
4100
4387
  }
4101
4388
  }
4102
4389
  else {
@@ -4115,6 +4402,9 @@ export class AUNClient {
4115
4402
  this._applyServerHeartbeatInterval(hello.heartbeat_interval, 'auth');
4116
4403
  }
4117
4404
  this._gatewayConnectionId = isJsonObject(hello) && isJsonObject(hello.connection) ? String(hello.connection.id ?? '') : '';
4405
+ this._negotiatedCapabilities = isJsonObject(hello) && isJsonObject(hello.negotiated_capabilities) ? hello.negotiated_capabilities : {};
4406
+ if (isJsonObject(hello))
4407
+ await this._applyRiskHello(hello);
4118
4408
  }
4119
4409
  this._assertReconnectOwner(reconnectOwner);
4120
4410
  this._lifecycle.assertConnectionAttemptOwner(connectionOwner);
@@ -4178,6 +4468,10 @@ export class AUNClient {
4178
4468
  else {
4179
4469
  this._clientLog.debug('V2 session init deferred for short connection');
4180
4470
  }
4471
+ this._controlMessages().close();
4472
+ this._safeAsync(this._controlMessages().recover());
4473
+ this._agentMdChanges?.close();
4474
+ this._agentMdChanges = new AgentMdChangeCoordinator(this);
4181
4475
  // connect/reconnect 成功后自动触发一次 P2P message.v2.pull,补齐离线期间积压
4182
4476
  // 群消息按惰性触发,不在此处主动 pull
4183
4477
  if (backgroundSyncEnabled) {
@@ -4415,6 +4709,8 @@ export class AUNClient {
4415
4709
  this._clientLog.warn(`V2 decrypt: invalid envelope_json for msg seq=${String(msg.seq)}`);
4416
4710
  return null;
4417
4711
  }
4712
+ if (messageClassOf({ ...msg, envelope }) === 'sdk_control' && source !== 'control_pull')
4713
+ return null;
4418
4714
  const e2eeMeta = this._v2E2eeMeta(envelope);
4419
4715
  if (observeAgentMd) {
4420
4716
  if (deferredEffects)
@@ -4808,6 +5104,8 @@ export class AUNClient {
4808
5104
  return this._groupState.autoConfirmPendingProposals();
4809
5105
  }
4810
5106
  async _onV2PushNotification(data, expectedVersion, inlineGeneration) {
5107
+ if (this._controlMessages().onPush(data))
5108
+ return;
4811
5109
  return this._delivery.onV2PushNotification(data, expectedVersion, inlineGeneration);
4812
5110
  }
4813
5111
  async _onV2StateProposed(data) {
@@ -4820,6 +5118,8 @@ export class AUNClient {
4820
5118
  return this._groupState.onV2StateConfirmed(data);
4821
5119
  }
4822
5120
  async _onRawGroupV2MessageCreated(data, expectedVersion, inlineGeneration) {
5121
+ if (this._controlMessages().onPush(data, true))
5122
+ return;
4823
5123
  return this._delivery.onRawGroupV2MessageCreated(data, expectedVersion, inlineGeneration);
4824
5124
  }
4825
5125
  /** Push 通知带 payload 时的就地解密(复用 _decryptV2Message) */
@@ -5417,15 +5717,25 @@ export class AUNClient {
5417
5717
  * 透传到应用层可订阅事件 'gateway.disconnect',方便业务定位被踢原因。
5418
5718
  */
5419
5719
  async _onGatewayDisconnect(data) {
5420
- const payload = (data && typeof data === 'object') ? data : {};
5720
+ const payload = (data && typeof data === 'object') ? structuredClone(data) : {};
5421
5721
  const code = payload.code;
5422
5722
  const reason = payload.reason ?? '';
5423
5723
  const detail = (payload.detail && typeof payload.detail === 'object' && !Array.isArray(payload.detail))
5424
5724
  ? payload.detail
5425
5725
  : {};
5726
+ const wrapper = payload.risk_state ?? detail.risk_state;
5727
+ delete payload.risk_state;
5728
+ delete detail.risk_state;
5729
+ if (isJsonObject(wrapper) && Array.isArray(wrapper.states))
5730
+ for (const state of wrapper.states)
5731
+ if (isJsonObject(state))
5732
+ await this._onRiskControl(state);
5733
+ const retryMs = Number(detail.retry_after_ms ?? payload.retry_after_ms ?? 0);
5734
+ if (Number(code) === 4029 && Number.isSafeInteger(retryMs) && retryMs > 0)
5735
+ this._riskScalarReconnectAt = (typeof performance !== 'undefined' ? performance.now() : Date.now()) + retryMs;
5426
5736
  this._clientLog.warn(`server initiated disconnect: code=${code}, reason=${reason}, detail=${JSON.stringify(detail)}`);
5427
5737
  const reasonText = String(reason ?? '').trim().toLowerCase();
5428
- const retryable = Number(code) === 4012
5738
+ const retryable = Number(code) === 4012 || Number(code) === 4029
5429
5739
  || reasonText.includes('long_connection_already_exists')
5430
5740
  || reasonText.includes('long connection already exists');
5431
5741
  this._serverKicked = !retryable;
@@ -5638,7 +5948,7 @@ export class AUNClient {
5638
5948
  break;
5639
5949
  }
5640
5950
  this._retryAttempt = attempt;
5641
- this._nextRetryAt = Date.now() + reconnectSleepDelayMs(delay, maxBaseDelay);
5951
+ this._nextRetryAt = Date.now() + Math.max(reconnectSleepDelayMs(delay, maxBaseDelay), this._riskReconnectDelay());
5642
5952
  this._state = 'retry_backoff';
5643
5953
  if (!reconnectAbort || !await this._publishReconnectEvent(reconnectAbort, 'state_change', {
5644
5954
  state: this._publicState(this._state),