@agentunion/fastaun 0.5.27 → 0.5.28

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 (57) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/_packed_docs/CHANGELOG.md +36 -0
  3. package/_packed_docs/INDEX.md +17 -2
  4. package/_packed_docs/KITE_DOCS_GUIDE.md +12 -9
  5. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +2 -2
  6. package/_packed_docs/sdk/INDEX.md +5 -3
  7. package/dist/agent-md-changes.d.ts +42 -0
  8. package/dist/agent-md-changes.js +339 -0
  9. package/dist/agent-md-changes.js.map +1 -0
  10. package/dist/agent-md-schema.d.ts +1 -0
  11. package/dist/agent-md-schema.js +30 -13
  12. package/dist/agent-md-schema.js.map +1 -1
  13. package/dist/agent-md.d.ts +11 -0
  14. package/dist/agent-md.js +86 -15
  15. package/dist/agent-md.js.map +1 -1
  16. package/dist/aid-store.d.ts +11 -0
  17. package/dist/aid-store.js +102 -21
  18. package/dist/aid-store.js.map +1 -1
  19. package/dist/auth.d.ts +10 -0
  20. package/dist/auth.js +44 -4
  21. package/dist/auth.js.map +1 -1
  22. package/dist/client/control-messages.d.ts +31 -0
  23. package/dist/client/control-messages.js +474 -0
  24. package/dist/client/control-messages.js.map +1 -0
  25. package/dist/client/delivery.js +10 -2
  26. package/dist/client/delivery.js.map +1 -1
  27. package/dist/client/lifecycle.js +10 -0
  28. package/dist/client/lifecycle.js.map +1 -1
  29. package/dist/client/rpc-pipeline.js +76 -13
  30. package/dist/client/rpc-pipeline.js.map +1 -1
  31. package/dist/client/v2-e2ee.d.ts +3 -0
  32. package/dist/client/v2-e2ee.js +5 -0
  33. package/dist/client/v2-e2ee.js.map +1 -1
  34. package/dist/client.d.ts +26 -0
  35. package/dist/client.js +267 -7
  36. package/dist/client.js.map +1 -1
  37. package/dist/discovery.d.ts +3 -0
  38. package/dist/discovery.js +52 -2
  39. package/dist/discovery.js.map +1 -1
  40. package/dist/events.js +14 -1
  41. package/dist/events.js.map +1 -1
  42. package/dist/risk-control.d.ts +68 -0
  43. package/dist/risk-control.js +344 -0
  44. package/dist/risk-control.js.map +1 -0
  45. package/dist/storage/lowlevel.d.ts +1 -0
  46. package/dist/storage/lowlevel.js +2 -2
  47. package/dist/storage/lowlevel.js.map +1 -1
  48. package/dist/tools/cross-sdk-agent.d.ts +1 -1
  49. package/dist/transport.d.ts +21 -1
  50. package/dist/transport.js +199 -20
  51. package/dist/transport.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
  55. package/_packed_docs/cli//345/207/255/350/257/201/344/270/216/347/273/204/347/273/207/346/234/215/345/212/241CLI/345/274/200/345/217/221/344/270/216/344/275/277/347/224/250/346/214/207/345/215/227.md +0 -869
  56. package/_packed_docs/sdk/0.5.27-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +0 -408
  57. package/_packed_docs/sdk/0.5.27-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +0 -410
package/dist/client.d.ts CHANGED
@@ -12,6 +12,7 @@
12
12
  */
13
13
  import { AIDStore } from './aid-store.js';
14
14
  import { type Subscription, type EventHandler } from './events.js';
15
+ import { type RiskStateEvent } from './risk-control.js';
15
16
  import { CollabClient } from './collab/client.js';
16
17
  import { DebugFacade, CredentialFacade, OrgFacade, GroupFacade, MailFacade, MessageFacade, StreamFacade } from './facades.js';
17
18
  import { StorageVFS } from './storage/vfs.js';
@@ -207,6 +208,13 @@ export declare class AUNClient {
207
208
  private _v2KeyStore?;
208
209
  private _v2SessionInitInFlight;
209
210
  private _v2RuntimeGeneration;
211
+ private _controlMessageCoordinator?;
212
+ private _agentMdChanges?;
213
+ private _agentMdCommitUnsubscribe?;
214
+ private _installAgentMdChangeHooks;
215
+ private _getAgentMdChanges;
216
+ private _onSdkControl;
217
+ private _controlMessages;
210
218
  /** V2 bootstrap 缓存:aid/group:id → 设备列表 + 时间戳 */
211
219
  private _v2BootstrapCache;
212
220
  private _v2SigCache;
@@ -227,6 +235,15 @@ export declare class AUNClient {
227
235
  private _reconnectTask;
228
236
  private _reconnectEventDispatchDepth;
229
237
  private _gatewayConnectionId;
238
+ private _negotiatedCapabilities;
239
+ private _riskNegotiated;
240
+ private _riskRestoredAid;
241
+ private _riskEvents;
242
+ private _riskApplying;
243
+ private _riskApplyTail;
244
+ private _riskScalarReconnectAt;
245
+ private _riskHttpRetryAt;
246
+ private _risk;
230
247
  private _migrationPending;
231
248
  private _migrationRevision;
232
249
  private _serverKicked;
@@ -366,6 +383,15 @@ export declare class AUNClient {
366
383
  * disconnect 是可恢复的:停止心跳、关闭 WebSocket,但不清理 keystore 等状态。
367
384
  */
368
385
  disconnect(): Promise<void>;
386
+ get riskState(): Readonly<RiskStateEvent>;
387
+ private _installRiskHooks;
388
+ private _restoreRiskState;
389
+ private _assertRiskReconnect;
390
+ private _riskReconnectDelay;
391
+ private _applyRiskHello;
392
+ private _onRiskControl;
393
+ private _applyRiskControl;
394
+ _riskFetch(input: string, init?: RequestInit, perform?: (url: string, init: RequestInit) => Promise<Response>, requestScope?: string): Promise<Response>;
369
395
  /**
370
396
  * 发送 JSON-RPC 调用。
371
397
  * 自动处理内部方法限制、E2EE 加解密、客户端签名等。
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';
@@ -929,6 +932,27 @@ export class AUNClient {
929
932
  _v2KeyStore;
930
933
  _v2SessionInitInFlight = null;
931
934
  _v2RuntimeGeneration = 0;
935
+ _controlMessageCoordinator;
936
+ _agentMdChanges;
937
+ _agentMdCommitUnsubscribe;
938
+ _installAgentMdChangeHooks() {
939
+ this._agentMdCommitUnsubscribe?.();
940
+ this._agentMdCommitUnsubscribe = AgentMdManager.subscribeCommits(this._configModel.aunPath, () => {
941
+ const coordinator = this._getAgentMdChanges();
942
+ if (this._state === 'ready')
943
+ void coordinator.flushNotifications().catch((error) => this._clientLog.debug(String(error)));
944
+ coordinator.schedule();
945
+ });
946
+ }
947
+ _getAgentMdChanges() {
948
+ return this._agentMdChanges ??= new AgentMdChangeCoordinator(this);
949
+ }
950
+ _onSdkControl(protocol, version, payload, senderAid) {
951
+ return this._getAgentMdChanges().handle(protocol, version, payload, senderAid);
952
+ }
953
+ _controlMessages() {
954
+ return this._controlMessageCoordinator ??= new ControlMessageCoordinator(this);
955
+ }
932
956
  /** V2 bootstrap 缓存:aid/group:id → 设备列表 + 时间戳 */
933
957
  _v2BootstrapCache = new Map();
934
958
  _v2SigCache = new Map();
@@ -949,6 +973,27 @@ export class AUNClient {
949
973
  _reconnectTask = null;
950
974
  _reconnectEventDispatchDepth = 0;
951
975
  _gatewayConnectionId = '';
976
+ _negotiatedCapabilities = {};
977
+ _riskNegotiated = false;
978
+ _riskRestoredAid = '';
979
+ _riskEvents = [];
980
+ _riskApplying = false;
981
+ _riskApplyTail = Promise.resolve();
982
+ _riskScalarReconnectAt = 0;
983
+ _riskHttpRetryAt = new Map();
984
+ _risk = new RiskController({
985
+ persist: async (state) => {
986
+ if (!this._aid || !this._tokenStore.saveMetadata || !isJsonObject(state.target) || state.target.aid !== this._aid || state.target.device_id !== this._deviceId)
987
+ throw new Error('risk persistence unavailable');
988
+ this._tokenStore.saveMetadata(this._aid, { ...(this._tokenStore.loadMetadata?.(this._aid) ?? {}), [`risk_state_v1:${this._deviceId}`]: state });
989
+ },
990
+ onState: (event) => {
991
+ if (this._riskApplying)
992
+ this._riskEvents.push(event);
993
+ else
994
+ this._dispatcher.enqueue('risk_state_changed', event);
995
+ },
996
+ });
952
997
  _migrationPending = false;
953
998
  _migrationRevision = new Map();
954
999
  _serverKicked = false;
@@ -1059,6 +1104,7 @@ export class AUNClient {
1059
1104
  fetchEntityIdentity: (target, type) => this._freshEntityCertificate(target, type),
1060
1105
  fetchEntitySigner: (target, fingerprint) => this._freshEntitySigner(target, fingerprint),
1061
1106
  });
1107
+ this._installAgentMdChangeHooks();
1062
1108
  this._transport = new RPCTransport({
1063
1109
  eventDispatcher: this._dispatcher,
1064
1110
  timeout: 35_000,
@@ -1066,6 +1112,8 @@ export class AUNClient {
1066
1112
  logger: this._logger.for('aun_core.transport'),
1067
1113
  dnsNet,
1068
1114
  });
1115
+ this._installRiskHooks();
1116
+ this._transport.setClassMessageHandler((event, payload) => this._controlMessages().onPush(payload, event.includes('group.')));
1069
1117
  this._transport.setProtocolMetaObserver((meta) => this._observeRpcMeta(meta));
1070
1118
  this._transport.setServiceEventVerifier((event, payload) => this._verifyIncomingServiceEvent(event, payload), () => this._identity?.aid ?? '', () => this._serviceEventIdentityVersion);
1071
1119
  this._transport.setProtocolDisconnectCallback((err, closeCode) => this._handleTransportDisconnect(err, closeCode));
@@ -1100,6 +1148,8 @@ export class AUNClient {
1100
1148
  this._dispatcher.subscribeProtocol('_raw.message.read_changed', (data) => this._onRawReadChanged(data, 'p2p'));
1101
1149
  // V2 P2P 推送通知:收到通知后自动走 message.v2.pull 拉取并解密
1102
1150
  this._dispatcher.subscribeProtocol('_raw.peer.v2.message_received', (data) => {
1151
+ if (this._controlMessages().onPush(data))
1152
+ return;
1103
1153
  const sender = isJsonObject(data) ? String(data.sender_aid ?? '') : '';
1104
1154
  const ns = this._p2pPeerPullEnabled ? (sender ? this._delivery.peerNamespace(sender) : '') : this._aid ? `p2p:${this._aid}` : '';
1105
1155
  const hasInline = isJsonObject(data)
@@ -1111,6 +1161,8 @@ export class AUNClient {
1111
1161
  });
1112
1162
  // V2 群组消息推送通知:收到通知后自动走 group.v2.pull 拉取并解密
1113
1163
  this._dispatcher.subscribeProtocol('_raw.group.v2.message_created', (data) => {
1164
+ if (this._controlMessages().onPush(data, true))
1165
+ return;
1114
1166
  const groupId = isJsonObject(data)
1115
1167
  ? String(data.group_id ?? '').trim()
1116
1168
  : '';
@@ -1291,7 +1343,9 @@ export class AUNClient {
1291
1343
  async releaseSubaid(subAid) { return this._getOwnershipWorkflow().releaseSubaid(subAid); }
1292
1344
  async transferSubaid(subAid, newOwnerAid) { return this._getOwnershipWorkflow().transferSubaid(subAid, newOwnerAid); }
1293
1345
  async checkOwnershipConsensus(subAid) {
1294
- return this._createOwnershipFiles((aid) => this._agentMdManager._readOwnershipQuery(aid)).checkConsensus(ownershipAid(subAid ?? this._aid, 'sub_aid'));
1346
+ const target = ownershipAid(subAid ?? this._aid, 'sub_aid');
1347
+ const result = await this._createOwnershipFiles((aid) => this._agentMdManager._readOwnershipQuery(aid)).checkConsensus(target);
1348
+ return result.status === 'consensus' ? result : this._checkOwnershipConsensusFresh(target);
1295
1349
  }
1296
1350
  async _checkOwnershipConsensusFresh(subAid) {
1297
1351
  return this._getOwnershipWorkflow().checkOwnershipConsensus(subAid);
@@ -1391,6 +1445,12 @@ export class AUNClient {
1391
1445
  return session.aid === this._aid && session.deviceId === this._deviceId;
1392
1446
  }
1393
1447
  _resetV2IdentityRuntime() {
1448
+ this._controlMessageCoordinator?.close();
1449
+ this._agentMdChanges?.close();
1450
+ this._agentMdChanges = undefined;
1451
+ this._negotiatedCapabilities = {};
1452
+ this._riskNegotiated = false;
1453
+ this._riskRestoredAid = '';
1394
1454
  this._v2RuntimeGeneration += 1;
1395
1455
  const keyStore = this._v2KeyStore;
1396
1456
  try {
@@ -1506,6 +1566,7 @@ export class AUNClient {
1506
1566
  fetchEntityIdentity: (target, type) => this._freshEntityCertificate(target, type),
1507
1567
  fetchEntitySigner: (target, fingerprint) => this._freshEntitySigner(target, fingerprint),
1508
1568
  });
1569
+ this._installAgentMdChangeHooks();
1509
1570
  this._transport = new RPCTransport({
1510
1571
  eventDispatcher: this._dispatcher,
1511
1572
  timeout: 35_000,
@@ -1513,6 +1574,8 @@ export class AUNClient {
1513
1574
  logger: this._logger.for('aun_core.transport'),
1514
1575
  dnsNet,
1515
1576
  });
1577
+ this._installRiskHooks();
1578
+ this._transport.setClassMessageHandler((event, payload) => this._controlMessages().onPush(payload, event.includes('group.')));
1516
1579
  this._transport.setProtocolMetaObserver((meta) => this._observeRpcMeta(meta));
1517
1580
  this._transport.setServiceEventVerifier((event, payload) => this._verifyIncomingServiceEvent(event, payload), () => this._identity?.aid ?? '', () => this._serviceEventIdentityVersion);
1518
1581
  this._transport.setProtocolDisconnectCallback((err, closeCode) => this._handleTransportDisconnect(err, closeCode));
@@ -1616,6 +1679,7 @@ export class AUNClient {
1616
1679
  }
1617
1680
  /** 关闭连接 */
1618
1681
  async close() {
1682
+ this._risk.close();
1619
1683
  this._peerGatewayCache.clear();
1620
1684
  return this._lifecycle.close();
1621
1685
  }
@@ -1626,6 +1690,155 @@ export class AUNClient {
1626
1690
  async disconnect() {
1627
1691
  return this._lifecycle.disconnect();
1628
1692
  }
1693
+ get riskState() { return Object.freeze(structuredClone(this._risk.state)); }
1694
+ _installRiskHooks() {
1695
+ this._transport.setRiskControlHandler((payload) => this._onRiskControl(payload));
1696
+ this._transport.setRequestAdmission((request) => this._risk.acquire(request));
1697
+ this._transport.setRequestCheck((request) => this._risk.check(request));
1698
+ this._auth.setRequestAdmission((request) => this._risk.acquire(request));
1699
+ this._auth.setRiskSupport(() => this._gatewayUrl?.startsWith('wss://') === true);
1700
+ this._auth.setHttpHandler((url, init, perform) => this._riskFetch(url, init, perform, 'http:gateway:pki'));
1701
+ this._discovery.setHttpHandler((url, init, perform) => this._riskFetch(url, init, perform, new URL(url).pathname === '/health' ? 'http:gateway:health' : 'http:discovery:gateway'));
1702
+ const aid = this._aid;
1703
+ if (aid)
1704
+ this._aidStore?.setRiskController(aid, async () => {
1705
+ if (this._aid !== aid || this._deviceId !== this._aidStore?.deviceId)
1706
+ return null;
1707
+ await this._restoreRiskState();
1708
+ return this._risk.appliesTo(aid, this._deviceId) ? this._risk : null;
1709
+ });
1710
+ this._agentMdManager.setHttpHandler((url, init, perform) => {
1711
+ const headers = new Headers(init.headers);
1712
+ const scope = headers.has('X-AUN-Ownership-Check') || headers.has('X-AUN-Ownership-Stage') ? 'http:nameservice:ownership' : 'http:nameservice:agent_md';
1713
+ return this._riskFetch(url, init, perform, scope);
1714
+ });
1715
+ }
1716
+ async _restoreRiskState() {
1717
+ const aid = this._aid;
1718
+ if (!aid || this._riskRestoredAid === aid)
1719
+ return;
1720
+ this._risk.reset();
1721
+ this._riskHttpRetryAt.clear();
1722
+ this._riskScalarReconnectAt = 0;
1723
+ const raw = this._tokenStore.loadMetadata?.(aid)?.[`risk_state_v1:${this._deviceId}`];
1724
+ if (raw) {
1725
+ try {
1726
+ const state = typeof raw === 'string' ? JSON.parse(raw) : raw;
1727
+ if (!isJsonObject(state) || !isJsonObject(state.target) || state.target.aid !== aid || state.target.device_id !== this._deviceId || !isJsonObject(state.signing_keys))
1728
+ throw new Error('risk state invalid');
1729
+ const [keyId, key] = Object.entries(state.signing_keys)[0] ?? [];
1730
+ if (!keyId || !Array.isArray(key))
1731
+ throw new Error('risk signing key unavailable');
1732
+ 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));
1733
+ await this._risk.restore(state);
1734
+ }
1735
+ catch {
1736
+ throw new StateError('persisted risk state could not be verified', { stringCode: 'risk_state_unavailable', retryable: true });
1737
+ }
1738
+ }
1739
+ this._riskRestoredAid = aid;
1740
+ }
1741
+ _assertRiskReconnect() {
1742
+ const delay = this._riskReconnectDelay();
1743
+ if (delay > 0)
1744
+ throw new ConnectionError('connection postponed by risk policy', { stringCode: 'risk_restricted', retryable: true, data: { retry_after_ms: Math.ceil(delay) } });
1745
+ }
1746
+ _riskReconnectDelay() { return Math.max(this._risk.reconnectDelayMs(), this._riskScalarReconnectAt - (typeof performance !== 'undefined' ? performance.now() : Date.now()), 0); }
1747
+ async _applyRiskHello(hello) {
1748
+ this._riskNegotiated = false;
1749
+ if (!isJsonObject(hello.negotiated_capabilities) || hello.negotiated_capabilities.risk_test_v1 !== true)
1750
+ return;
1751
+ if (!this._gatewayUrl?.startsWith('wss://') || !this._configModel.verifySsl || !this._aid || !isJsonObject(hello.risk_signing_key))
1752
+ return;
1753
+ const key = hello.risk_signing_key;
1754
+ const initial = isJsonObject(hello.risk_state) ? hello.risk_state : {};
1755
+ 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));
1756
+ this._riskNegotiated = true;
1757
+ await this._transport.setRiskSnapshot(this._risk.exportState(true));
1758
+ if (Array.isArray(initial.states))
1759
+ for (const state of initial.states)
1760
+ if (isJsonObject(state))
1761
+ await this._onRiskControl(state);
1762
+ }
1763
+ async _onRiskControl(payload) {
1764
+ const identity = JSON.stringify([this._aid, this._deviceId, this._slotId, this._serviceEventIdentityVersion, this._transport._connectionGeneration]);
1765
+ const run = this._riskApplyTail.then(async () => {
1766
+ const current = () => identity === JSON.stringify([this._aid, this._deviceId, this._slotId, this._serviceEventIdentityVersion, this._transport._connectionGeneration]);
1767
+ if (current())
1768
+ await this._applyRiskControl(payload, current);
1769
+ });
1770
+ this._riskApplyTail = run.catch(() => { });
1771
+ await run;
1772
+ }
1773
+ async _applyRiskControl(payload, current) {
1774
+ if (!this._riskNegotiated)
1775
+ return;
1776
+ this._riskApplying = true;
1777
+ let status;
1778
+ try {
1779
+ status = await this._risk.apply(payload);
1780
+ }
1781
+ catch {
1782
+ this._riskApplying = false;
1783
+ this._riskEvents = [];
1784
+ return;
1785
+ }
1786
+ this._riskApplying = false;
1787
+ await this._transport.setRiskSnapshot(this._risk.exportState(true));
1788
+ if (!current()) {
1789
+ this._riskEvents = [];
1790
+ return;
1791
+ }
1792
+ const params = { version: 1, delivery_id: payload.delivery_id, subject: payload.subject, revision: payload.revision, status: this._risk.persistenceFailed ? 'failed' : status };
1793
+ if (this._risk.persistenceFailed)
1794
+ params.error_code = 'persistence_unavailable';
1795
+ const acknowledgement = this._transport.call('gateway.risk_ack', params, 500).catch(() => { });
1796
+ for (const event of this._riskEvents.splice(0))
1797
+ this._dispatcher.enqueue('risk_state_changed', event);
1798
+ if (this._risk.shouldDisconnect()) {
1799
+ await acknowledgement;
1800
+ if (current())
1801
+ void this._handleTransportDisconnect(new ConnectionError('connection restricted by risk policy', { code: 4029, stringCode: 'risk_restricted', retryable: true }), 4029);
1802
+ }
1803
+ }
1804
+ async _riskFetch(input, init = {}, perform = fetch, requestScope) {
1805
+ await this._restoreRiskState();
1806
+ const url = new URL(input);
1807
+ const route = `${url.origin}${url.pathname}`;
1808
+ const monotonicNow = () => typeof performance !== 'undefined' ? performance.now() : Date.now();
1809
+ const retryAt = this._riskHttpRetryAt.get(route) ?? 0;
1810
+ if (retryAt > monotonicNow())
1811
+ throw new StateError('HTTP request postponed by risk response', { stringCode: 'risk_restricted', retryable: true, data: { retry_after_ms: Math.ceil(retryAt - monotonicNow()) } });
1812
+ const release = this._risk.acquire(requestScope ?? (['GET', 'HEAD'].includes(String(init.method ?? 'GET').toUpperCase()) ? 'http:storage:read' : 'http:storage:write'));
1813
+ try {
1814
+ const response = await perform(input, init);
1815
+ if (response.status === 429) {
1816
+ try {
1817
+ const body = await response.clone().json();
1818
+ const detail = isJsonObject(body) && isJsonObject(body.error) && isJsonObject(body.error.data) ? body.error.data : isJsonObject(body) && isJsonObject(body.data) ? body.data : isJsonObject(body) ? body : {};
1819
+ const wrapper = detail.risk_state;
1820
+ if (this._riskNegotiated && isJsonObject(wrapper) && Array.isArray(wrapper.states))
1821
+ for (const state of wrapper.states)
1822
+ if (isJsonObject(state))
1823
+ await this._onRiskControl(state);
1824
+ const delayMs = Number(detail.retry_after_ms);
1825
+ if (Number.isSafeInteger(delayMs) && delayMs > 0)
1826
+ this._riskHttpRetryAt.set(route, monotonicNow() + delayMs);
1827
+ }
1828
+ catch { /* 无签名状态的 HTTP 错误由调用入口处理 */ }
1829
+ const header = response.headers.get('Retry-After');
1830
+ if (header) {
1831
+ const delayMs = /^\d+$/.test(header) ? Number(header) * 1000 : Math.max(0, Date.parse(header) - Date.now());
1832
+ if (Number.isFinite(delayMs) && delayMs > 0)
1833
+ this._riskHttpRetryAt.set(route, Math.max(this._riskHttpRetryAt.get(route) ?? 0, monotonicNow() + delayMs));
1834
+ }
1835
+ }
1836
+ return response;
1837
+ }
1838
+ finally {
1839
+ release();
1840
+ }
1841
+ }
1629
1842
  // ── RPC ───────────────────────────────────────────────────
1630
1843
  /**
1631
1844
  * 发送 JSON-RPC 调用。
@@ -2783,6 +2996,10 @@ export class AUNClient {
2783
2996
  const messageEvent = event === 'message.received' || event === 'message.recalled' || event === 'message.batch';
2784
2997
  const hadMessageConsumer = messageEvent && [...this._dispatcher._snapshotApplicationHandlers(['message.received', 'message.recalled', 'message.batch']).values()].some((handlers) => handlers.length > 0);
2785
2998
  const result = this._dispatcher.subscribe(event, handler);
2999
+ if (event === 'agent_md_changed') {
3000
+ this._getAgentMdChanges().schedule();
3001
+ void this._getAgentMdChanges().deliverEvents().catch((error) => this._clientLog.debug(String(error)));
3002
+ }
2786
3003
  if (messageEvent) {
2787
3004
  if (this._p2pPeerPullEnabled && !hadMessageConsumer)
2788
3005
  this._delivery.refreshPendingPeers();
@@ -3020,6 +3237,8 @@ export class AUNClient {
3020
3237
  // ── 事件自动解密管线 ──────────────────────────────────────
3021
3238
  /** 处理 transport 层推送的原始 P2P 消息 */
3022
3239
  async _onRawMessageReceived(data) {
3240
+ if (this._controlMessages().onPush(data))
3241
+ return;
3023
3242
  return this._delivery.onRawMessageReceived(data);
3024
3243
  }
3025
3244
  async _onRawMessageRecalled(data) {
@@ -3027,6 +3246,8 @@ export class AUNClient {
3027
3246
  }
3028
3247
  /** 处理群组消息推送:自动解密后 re-publish */
3029
3248
  async _onRawGroupMessageCreated(data) {
3249
+ if (this._controlMessages().onPush(data, true))
3250
+ return;
3030
3251
  return this._delivery.onRawGroupMessageCreated(data);
3031
3252
  }
3032
3253
  async _onRawGroupMessageRecalled(data) {
@@ -3114,6 +3335,8 @@ export class AUNClient {
3114
3335
  this._delivery.markPublishedSeq(ns, seq);
3115
3336
  }
3116
3337
  _publishAppEvent(event, payload, source = 'direct', deliveryContext) {
3338
+ if (messageClassOf(payload) === 'sdk_control')
3339
+ return this._controlMessages().consume(payload);
3117
3340
  if (event === 'message.received') {
3118
3341
  return this._handleOwnershipMessage(payload).then((handled) => {
3119
3342
  if (!handled)
@@ -3596,7 +3819,7 @@ export class AUNClient {
3596
3819
  throw new ValidationError('gateway url unavailable for e2ee cert fetch');
3597
3820
  }
3598
3821
  const peerGatewayUrl = AUNClient._resolvePeerGatewayUrl(gatewayUrl, aid);
3599
- const certPem = await _httpGetText(AUNClient._buildCertUrl(peerGatewayUrl, aid, certFingerprint), this._configModel.verifySsl, timeoutMs);
3822
+ const certPem = await this._auth._fetchRiskText(AUNClient._buildCertUrl(peerGatewayUrl, aid, certFingerprint));
3600
3823
  if (auth !== this._auth || pkiEpoch !== auth._strictPkiEpoch)
3601
3824
  throw new ValidationError('peer certificate fetch context changed');
3602
3825
  const validated = await this._validateAndCachePeerCert({
@@ -4037,6 +4260,9 @@ export class AUNClient {
4037
4260
  // ── 内部:连接 ────────────────────────────────────────────
4038
4261
  /** 执行一次连接流程 */
4039
4262
  async _connectOnce(params, allowReauth, reconnectOwner, connectionOwner) {
4263
+ await this._restoreRiskState();
4264
+ this._assertRiskReconnect();
4265
+ this._riskNegotiated = false;
4040
4266
  const tStart = Date.now();
4041
4267
  connectionOwner ??= this._connectAbort ?? undefined;
4042
4268
  this._lifecycle.assertConnectionAttemptOwner(connectionOwner);
@@ -4046,8 +4272,15 @@ export class AUNClient {
4046
4272
  this._captureCapabilitiesFromConnect(params);
4047
4273
  this._connectDeliveryMode = { ...(params.delivery_mode ?? this._connectDeliveryMode) };
4048
4274
  const extraInfo = (params.extra_info && typeof params.extra_info === 'object' && !Array.isArray(params.extra_info))
4049
- ? params.extra_info
4050
- : undefined;
4275
+ ? { ...params.extra_info }
4276
+ : {};
4277
+ if (typeof this._tokenStore.updateInstanceState === 'function') {
4278
+ extraInfo._capabilities = {
4279
+ ...(isJsonObject(extraInfo._capabilities) ? extraInfo._capabilities : {}),
4280
+ message_classes_v1: { p2p: ['normal', 'control', 'sdk_control'], group: ['normal', 'control', 'sdk_control'] },
4281
+ sdk_control_protocols: { 'aun.agent_md.changed': [1] },
4282
+ };
4283
+ }
4051
4284
  const prevState = this._state;
4052
4285
  this._auth.setInstanceContext({ deviceId: this._deviceId, slotId: this._slotId });
4053
4286
  this._state = 'connecting';
@@ -4097,6 +4330,9 @@ export class AUNClient {
4097
4330
  this._applyServerHeartbeatInterval(auth.hello.heartbeat_interval, 'auth');
4098
4331
  }
4099
4332
  this._gatewayConnectionId = isJsonObject(auth.hello) && isJsonObject(auth.hello.connection) ? String(auth.hello.connection.id ?? '') : '';
4333
+ this._negotiatedCapabilities = isJsonObject(auth.hello) && isJsonObject(auth.hello.negotiated_capabilities) ? auth.hello.negotiated_capabilities : {};
4334
+ if (isJsonObject(auth.hello))
4335
+ await this._applyRiskHello(auth.hello);
4100
4336
  }
4101
4337
  }
4102
4338
  else {
@@ -4115,6 +4351,9 @@ export class AUNClient {
4115
4351
  this._applyServerHeartbeatInterval(hello.heartbeat_interval, 'auth');
4116
4352
  }
4117
4353
  this._gatewayConnectionId = isJsonObject(hello) && isJsonObject(hello.connection) ? String(hello.connection.id ?? '') : '';
4354
+ this._negotiatedCapabilities = isJsonObject(hello) && isJsonObject(hello.negotiated_capabilities) ? hello.negotiated_capabilities : {};
4355
+ if (isJsonObject(hello))
4356
+ await this._applyRiskHello(hello);
4118
4357
  }
4119
4358
  this._assertReconnectOwner(reconnectOwner);
4120
4359
  this._lifecycle.assertConnectionAttemptOwner(connectionOwner);
@@ -4178,6 +4417,11 @@ export class AUNClient {
4178
4417
  else {
4179
4418
  this._clientLog.debug('V2 session init deferred for short connection');
4180
4419
  }
4420
+ this._controlMessages().close();
4421
+ this._safeAsync(this._controlMessages().recover());
4422
+ this._agentMdChanges?.close();
4423
+ this._agentMdChanges = new AgentMdChangeCoordinator(this);
4424
+ this._agentMdChanges.schedule();
4181
4425
  // connect/reconnect 成功后自动触发一次 P2P message.v2.pull,补齐离线期间积压
4182
4426
  // 群消息按惰性触发,不在此处主动 pull
4183
4427
  if (backgroundSyncEnabled) {
@@ -4415,6 +4659,8 @@ export class AUNClient {
4415
4659
  this._clientLog.warn(`V2 decrypt: invalid envelope_json for msg seq=${String(msg.seq)}`);
4416
4660
  return null;
4417
4661
  }
4662
+ if (messageClassOf({ ...msg, envelope }) === 'sdk_control' && source !== 'control_pull')
4663
+ return null;
4418
4664
  const e2eeMeta = this._v2E2eeMeta(envelope);
4419
4665
  if (observeAgentMd) {
4420
4666
  if (deferredEffects)
@@ -4808,6 +5054,8 @@ export class AUNClient {
4808
5054
  return this._groupState.autoConfirmPendingProposals();
4809
5055
  }
4810
5056
  async _onV2PushNotification(data, expectedVersion, inlineGeneration) {
5057
+ if (this._controlMessages().onPush(data))
5058
+ return;
4811
5059
  return this._delivery.onV2PushNotification(data, expectedVersion, inlineGeneration);
4812
5060
  }
4813
5061
  async _onV2StateProposed(data) {
@@ -4820,6 +5068,8 @@ export class AUNClient {
4820
5068
  return this._groupState.onV2StateConfirmed(data);
4821
5069
  }
4822
5070
  async _onRawGroupV2MessageCreated(data, expectedVersion, inlineGeneration) {
5071
+ if (this._controlMessages().onPush(data, true))
5072
+ return;
4823
5073
  return this._delivery.onRawGroupV2MessageCreated(data, expectedVersion, inlineGeneration);
4824
5074
  }
4825
5075
  /** Push 通知带 payload 时的就地解密(复用 _decryptV2Message) */
@@ -5417,15 +5667,25 @@ export class AUNClient {
5417
5667
  * 透传到应用层可订阅事件 'gateway.disconnect',方便业务定位被踢原因。
5418
5668
  */
5419
5669
  async _onGatewayDisconnect(data) {
5420
- const payload = (data && typeof data === 'object') ? data : {};
5670
+ const payload = (data && typeof data === 'object') ? structuredClone(data) : {};
5421
5671
  const code = payload.code;
5422
5672
  const reason = payload.reason ?? '';
5423
5673
  const detail = (payload.detail && typeof payload.detail === 'object' && !Array.isArray(payload.detail))
5424
5674
  ? payload.detail
5425
5675
  : {};
5676
+ const wrapper = payload.risk_state ?? detail.risk_state;
5677
+ delete payload.risk_state;
5678
+ delete detail.risk_state;
5679
+ if (isJsonObject(wrapper) && Array.isArray(wrapper.states))
5680
+ for (const state of wrapper.states)
5681
+ if (isJsonObject(state))
5682
+ await this._onRiskControl(state);
5683
+ const retryMs = Number(detail.retry_after_ms ?? payload.retry_after_ms ?? 0);
5684
+ if (Number(code) === 4029 && Number.isSafeInteger(retryMs) && retryMs > 0)
5685
+ this._riskScalarReconnectAt = (typeof performance !== 'undefined' ? performance.now() : Date.now()) + retryMs;
5426
5686
  this._clientLog.warn(`server initiated disconnect: code=${code}, reason=${reason}, detail=${JSON.stringify(detail)}`);
5427
5687
  const reasonText = String(reason ?? '').trim().toLowerCase();
5428
- const retryable = Number(code) === 4012
5688
+ const retryable = Number(code) === 4012 || Number(code) === 4029
5429
5689
  || reasonText.includes('long_connection_already_exists')
5430
5690
  || reasonText.includes('long connection already exists');
5431
5691
  this._serverKicked = !retryable;
@@ -5638,7 +5898,7 @@ export class AUNClient {
5638
5898
  break;
5639
5899
  }
5640
5900
  this._retryAttempt = attempt;
5641
- this._nextRetryAt = Date.now() + reconnectSleepDelayMs(delay, maxBaseDelay);
5901
+ this._nextRetryAt = Date.now() + Math.max(reconnectSleepDelayMs(delay, maxBaseDelay), this._riskReconnectDelay());
5642
5902
  this._state = 'retry_backoff';
5643
5903
  if (!reconnectAbort || !await this._publishReconnectEvent(reconnectAbort, 'state_change', {
5644
5904
  state: this._publicState(this._state),