@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.
- package/CHANGELOG.md +55 -0
- package/_packed_docs/CHANGELOG.md +55 -0
- package/_packed_docs/INDEX.md +23 -3
- package/_packed_docs/KITE_DOCS_GUIDE.md +15 -11
- 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
- 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
- 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
- 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
- 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
- package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +6 -4
- package/_packed_docs/sdk/INDEX.md +14 -4
- 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
- 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
- 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
- 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
- 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
- package/dist/agent-md-changes.d.ts +30 -0
- package/dist/agent-md-changes.js +168 -0
- package/dist/agent-md-changes.js.map +1 -0
- package/dist/agent-md-schema.d.ts +1 -0
- package/dist/agent-md-schema.js +29 -12
- package/dist/agent-md-schema.js.map +1 -1
- package/dist/agent-md.d.ts +9 -0
- package/dist/agent-md.js +107 -36
- package/dist/agent-md.js.map +1 -1
- package/dist/aid-store.d.ts +14 -1
- package/dist/aid-store.js +163 -61
- package/dist/aid-store.js.map +1 -1
- package/dist/auth.d.ts +10 -0
- package/dist/auth.js +44 -4
- package/dist/auth.js.map +1 -1
- package/dist/client/control-messages.d.ts +31 -0
- package/dist/client/control-messages.js +496 -0
- package/dist/client/control-messages.js.map +1 -0
- package/dist/client/delivery.js +12 -3
- package/dist/client/delivery.js.map +1 -1
- package/dist/client/group-state.js +2 -3
- package/dist/client/group-state.js.map +1 -1
- package/dist/client/lifecycle.js +8 -0
- package/dist/client/lifecycle.js.map +1 -1
- package/dist/client/rpc-pipeline.js +76 -13
- package/dist/client/rpc-pipeline.js.map +1 -1
- package/dist/client/v2-e2ee.d.ts +3 -0
- package/dist/client/v2-e2ee.js +13 -4
- package/dist/client/v2-e2ee.js.map +1 -1
- package/dist/client.d.ts +27 -1
- package/dist/client.js +348 -38
- package/dist/client.js.map +1 -1
- package/dist/discovery.d.ts +3 -0
- package/dist/discovery.js +53 -2
- package/dist/discovery.js.map +1 -1
- package/dist/events.js +14 -1
- package/dist/events.js.map +1 -1
- package/dist/ownership-v21.d.ts +2 -1
- package/dist/ownership-v21.js +17 -3
- package/dist/ownership-v21.js.map +1 -1
- package/dist/ownership-workflow.js +9 -5
- package/dist/ownership-workflow.js.map +1 -1
- package/dist/risk-control.d.ts +68 -0
- package/dist/risk-control.js +344 -0
- package/dist/risk-control.js.map +1 -0
- package/dist/semantic-facade.js +4 -2
- package/dist/semantic-facade.js.map +1 -1
- package/dist/semantic-protocol.js +2 -0
- package/dist/semantic-protocol.js.map +1 -1
- package/dist/storage/lowlevel.d.ts +1 -0
- package/dist/storage/lowlevel.js +2 -2
- package/dist/storage/lowlevel.js.map +1 -1
- package/dist/tools/cross-sdk-agent.d.ts +1 -1
- package/dist/tools/cross-sdk-agent.js +16 -34
- package/dist/tools/cross-sdk-agent.js.map +1 -1
- package/dist/transport.d.ts +21 -1
- package/dist/transport.js +199 -20
- package/dist/transport.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- 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
|
|
523
|
-
|
|
524
|
-
|
|
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
|
|
529
|
-
const
|
|
530
|
-
const
|
|
531
|
-
let
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
-
:
|
|
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),
|