@agentunion/fastaun 0.5.34 → 0.5.36
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 +56 -1
- package/_packed_docs/CHANGELOG.md +56 -1
- package/_packed_docs/INDEX.md +18 -11
- package/_packed_docs/KITE_DOCS_GUIDE.md +9 -9
- package/_packed_docs/agent.md/SCHEMA.md +1 -1
- package/_packed_docs/aun/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +2 -2
- package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2112.1.md +17 -17
- package/_packed_docs/protocol/echo-probe-gateway.md +99 -0
- package/_packed_docs/protocol/echo-probe-sdk.md +91 -0
- package/_packed_docs/sdk/0.5.35-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +189 -0
- package/_packed_docs/sdk/0.5.35-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +204 -0
- package/_packed_docs/sdk/0.5.36-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +130 -0
- package/_packed_docs/sdk/0.5.36-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +176 -0
- package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +2 -2
- package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +32 -6
- package/_packed_docs/sdk/09-group-rpc-manual.md +7 -7
- package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +13 -13
- package/_packed_docs/sdk/AUN/344/272/213/344/273/266/345/217/221/345/270/203/350/256/242/351/230/205/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +73 -3
- package/_packed_docs/sdk/AUN/344/272/213/344/273/266/345/217/221/345/270/203/350/256/242/351/230/205/345/274/200/345/217/221/347/247/273/346/244/215/346/214/207/345/215/227.md +50 -8
- package/_packed_docs/sdk/INDEX.md +23 -18
- package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/2272.1.md +1 -1
- package/_packed_docs/sdk/Ownership2.1-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +5 -5
- package/_packed_docs/sdk/Ownership2.1-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +5 -5
- package/_packed_docs/sdk/Ownership2.1/350/257/246/347/273/206/350/256/276/350/256/241.md +9 -9
- package/_packed_docs//346/266/210/346/201/257/345/274/271/346/200/247/350/241/245/346/213/211/351/200/232/351/201/223/345/256/236/346/226/275/350/256/241/345/210/222.md +400 -0
- package/_packed_docs//346/266/210/346/201/257/345/274/271/346/200/247/350/241/245/346/213/211/351/200/232/351/201/223/346/226/271/346/241/210.md +485 -0
- package/dist/agent-md.d.ts +2 -1
- package/dist/agent-md.js +33 -6
- package/dist/agent-md.js.map +1 -1
- package/dist/aid-store.js +5 -1
- package/dist/aid-store.js.map +1 -1
- package/dist/auth.js +36 -45
- package/dist/auth.js.map +1 -1
- package/dist/client/delivery.d.ts +8 -1
- package/dist/client/delivery.js +203 -50
- package/dist/client/delivery.js.map +1 -1
- package/dist/client/lifecycle.js +10 -1
- package/dist/client/lifecycle.js.map +1 -1
- package/dist/client/rpc-pipeline.d.ts +4 -0
- package/dist/client/rpc-pipeline.js +93 -10
- package/dist/client/rpc-pipeline.js.map +1 -1
- package/dist/client/sync-channels.d.ts +2 -1
- package/dist/client/sync-channels.js +33 -14
- package/dist/client/sync-channels.js.map +1 -1
- package/dist/client/v2-e2ee.d.ts +1 -0
- package/dist/client/v2-e2ee.js +24 -6
- package/dist/client/v2-e2ee.js.map +1 -1
- package/dist/client.d.ts +15 -0
- package/dist/client.js +329 -56
- package/dist/client.js.map +1 -1
- package/dist/events.d.ts +1 -0
- package/dist/events.js +5 -0
- package/dist/events.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/mail-client.d.ts +101 -0
- package/dist/mail-client.js +369 -0
- package/dist/mail-client.js.map +1 -0
- package/dist/ownership-v21.d.ts +2 -1
- package/dist/ownership-v21.js +19 -9
- package/dist/ownership-v21.js.map +1 -1
- package/dist/pubsub/binding.js +9 -2
- package/dist/pubsub/binding.js.map +1 -1
- package/dist/pubsub/channel.d.ts +11 -3
- package/dist/pubsub/channel.js +91 -10
- package/dist/pubsub/channel.js.map +1 -1
- package/dist/pubsub/client.d.ts +8 -2
- package/dist/pubsub/client.js +62 -21
- package/dist/pubsub/client.js.map +1 -1
- package/dist/pubsub/index.d.ts +2 -2
- package/dist/pubsub/index.js +1 -1
- package/dist/pubsub/index.js.map +1 -1
- package/dist/pubsub/service.d.ts +6 -0
- package/dist/pubsub/service.js +101 -10
- package/dist/pubsub/service.js.map +1 -1
- package/dist/pubsub/types.d.ts +11 -1
- package/dist/tools/cross-sdk-agent.d.ts +1 -1
- package/dist/transport.d.ts +4 -0
- package/dist/transport.js +39 -2
- package/dist/transport.js.map +1 -1
- package/dist/udp-standby.d.ts +88 -0
- package/dist/udp-standby.js +483 -0
- package/dist/udp-standby.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +5 -1
- package/scripts/patch-imapflow-external.mjs +69 -0
package/dist/client.js
CHANGED
|
@@ -26,6 +26,7 @@ import { AUNLogger } from './logger.js';
|
|
|
26
26
|
import { normalizeGroupId } from './group-id.js';
|
|
27
27
|
import { validateAIDFormat, validateGroupIDFormat } from './validators.js';
|
|
28
28
|
import { RPCTransport } from './transport.js';
|
|
29
|
+
import { UdpStandbyController } from './udp-standby.js';
|
|
29
30
|
import { SyncChannels } from './client/sync-channels.js';
|
|
30
31
|
import { AuthFlow } from './auth.js';
|
|
31
32
|
import { AgentMdManager, buildDefaultAgentMd, updateOwnershipAgentMd } from './agent-md.js';
|
|
@@ -909,6 +910,9 @@ export class AUNClient {
|
|
|
909
910
|
_discovery;
|
|
910
911
|
/** 传输层 */
|
|
911
912
|
_transport;
|
|
913
|
+
_udpStandby = null;
|
|
914
|
+
_udpStandbyPolicy;
|
|
915
|
+
_udpStandbyConnectOptions;
|
|
912
916
|
/** 认证流程 */
|
|
913
917
|
_auth;
|
|
914
918
|
resolveCredentialService;
|
|
@@ -932,6 +936,8 @@ export class AUNClient {
|
|
|
932
936
|
_groupIdentityOperations = new Map();
|
|
933
937
|
_entityOwnershipConnection = 0;
|
|
934
938
|
_entityOwnershipChecks = new Map();
|
|
939
|
+
_postConnectRecoveryTimer = null;
|
|
940
|
+
_postConnectRecoveryAbort = null;
|
|
935
941
|
_groupIndexMetaCache = new GroupIndexMetaCache();
|
|
936
942
|
/** 消息序列号跟踪器(群消息 + P2P 空洞检测) */
|
|
937
943
|
_p2pPeerPullEnabled = true;
|
|
@@ -1409,25 +1415,30 @@ export class AUNClient {
|
|
|
1409
1415
|
const observation = { root: manager.root, etag: observedEtag, version: Number(record?.observation_version ?? 0), content_hash: typeof record?.content === 'string' ? ownershipSha256(record.content) : '' };
|
|
1410
1416
|
const cache = this._ownershipConsensusCache ??= new Map();
|
|
1411
1417
|
const cached = cache.get(target);
|
|
1412
|
-
if (
|
|
1418
|
+
if (cached?.result.status === 'consensus' && cached.observedVersion === observation.version && record?.remote_status !== 'missing'
|
|
1419
|
+
&& (!record?.remote_owner_md_sha256 || record.remote_owner_md_sha256 === cached.result.owner_md_sha256)
|
|
1413
1420
|
&& (observedEtag === cached.observedEtag || observedEtag === cached.result.agent_md_etag)
|
|
1414
1421
|
&& (typeof record?.content !== 'string' || ownershipSha256(record.content) === cached.result.agent_md_sha256)) {
|
|
1415
1422
|
cached.observedEtag = observedEtag;
|
|
1416
1423
|
return { ...cached.result };
|
|
1417
1424
|
}
|
|
1418
|
-
if (!
|
|
1425
|
+
if (!cached) {
|
|
1419
1426
|
const local = manager.localOwnershipConsensus(target, record);
|
|
1420
1427
|
if (local) {
|
|
1421
|
-
cache.set(target, { observedEtag, result: local });
|
|
1428
|
+
cache.set(target, { observedEtag, observedVersion: observation.version, result: local });
|
|
1422
1429
|
return { ...local };
|
|
1423
1430
|
}
|
|
1424
1431
|
}
|
|
1425
1432
|
cache.delete(target);
|
|
1426
1433
|
const result = { status: 'unavailable', sub_aid: target, owner_aid: null, relation_version: 0, lock: 'unlocked', agent_md_etag: '', agent_md_sha256: '' };
|
|
1427
|
-
const entry = { observedEtag, result: { ...result } };
|
|
1434
|
+
const entry = { observedEtag, observedVersion: observation.version, result: { ...result } };
|
|
1428
1435
|
cache.set(target, entry);
|
|
1436
|
+
let consensusContent;
|
|
1429
1437
|
try {
|
|
1430
1438
|
const snapshot = await manager.readOwnershipFresh(target);
|
|
1439
|
+
consensusContent = snapshot.content;
|
|
1440
|
+
if (snapshot.owner_md_sha256)
|
|
1441
|
+
result.owner_md_sha256 = snapshot.owner_md_sha256;
|
|
1431
1442
|
Object.assign(result, { relation_version: snapshot.relation_version, lock: snapshot.lock, agent_md_etag: snapshot.etag,
|
|
1432
1443
|
agent_md_sha256: snapshot.exists ? ownershipSha256(snapshot.content) : '' });
|
|
1433
1444
|
if (snapshot.ownership_status)
|
|
@@ -1448,11 +1459,15 @@ export class AUNClient {
|
|
|
1448
1459
|
result.retryable = result.status === 'unavailable';
|
|
1449
1460
|
}
|
|
1450
1461
|
if (this._agentMdManager === manager && this._ownershipConsensusCache === cache && cache.get(target) === entry) {
|
|
1451
|
-
entry.result = { ...result };
|
|
1452
1462
|
try {
|
|
1453
|
-
manager.cacheOwnershipSnapshot(target, result, observation)
|
|
1463
|
+
if (manager.cacheOwnershipSnapshot(target, result, observation, consensusContent))
|
|
1464
|
+
entry.result = { ...result };
|
|
1465
|
+
else
|
|
1466
|
+
cache.delete(target);
|
|
1467
|
+
}
|
|
1468
|
+
catch {
|
|
1469
|
+
cache.delete(target);
|
|
1454
1470
|
}
|
|
1455
|
-
catch { }
|
|
1456
1471
|
}
|
|
1457
1472
|
return result;
|
|
1458
1473
|
}
|
|
@@ -1598,6 +1613,10 @@ export class AUNClient {
|
|
|
1598
1613
|
}
|
|
1599
1614
|
_applyAidRuntimeContext(aid) {
|
|
1600
1615
|
this._aidStore = aid._aidStore;
|
|
1616
|
+
this._udpStandby?.close();
|
|
1617
|
+
this._udpStandby = null;
|
|
1618
|
+
this._udpStandbyPolicy = undefined;
|
|
1619
|
+
this._udpStandbyConnectOptions = undefined;
|
|
1601
1620
|
const oldTransport = this._transport;
|
|
1602
1621
|
try {
|
|
1603
1622
|
const closeResult = oldTransport?.close?.();
|
|
@@ -1850,10 +1869,14 @@ export class AUNClient {
|
|
|
1850
1869
|
}
|
|
1851
1870
|
/** 连接到 Gateway;身份来自构造函数或 loadIdentity(aid),认证由 SDK 内部自动完成。 */
|
|
1852
1871
|
async connect(opts) {
|
|
1872
|
+
if (this._udpStandby && ['standby', 'preparing', 'waking'].includes(this._udpStandby.state))
|
|
1873
|
+
return this._udpStandby.ensureActive();
|
|
1874
|
+
this._udpStandbyConnectOptions = opts ? { ...opts } : undefined;
|
|
1853
1875
|
await this._lifecycle.connect(opts);
|
|
1854
1876
|
}
|
|
1855
1877
|
/** 关闭连接 */
|
|
1856
1878
|
async close() {
|
|
1879
|
+
this._udpStandby?.close();
|
|
1857
1880
|
this._risk.close();
|
|
1858
1881
|
this._peerGatewayCache.clear();
|
|
1859
1882
|
return this._lifecycle.close();
|
|
@@ -1863,6 +1886,7 @@ export class AUNClient {
|
|
|
1863
1886
|
* disconnect 是可恢复的:停止心跳、关闭 WebSocket,但不清理 keystore 等状态。
|
|
1864
1887
|
*/
|
|
1865
1888
|
async disconnect() {
|
|
1889
|
+
this._udpStandby?.close();
|
|
1866
1890
|
return this._lifecycle.disconnect();
|
|
1867
1891
|
}
|
|
1868
1892
|
get riskState() { return Object.freeze(structuredClone(this._risk.state)); }
|
|
@@ -2020,27 +2044,47 @@ export class AUNClient {
|
|
|
2020
2044
|
* 自动处理内部方法限制、E2EE 加解密、客户端签名等。
|
|
2021
2045
|
*/
|
|
2022
2046
|
async call(method, params, options) {
|
|
2023
|
-
const
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2047
|
+
const echoStarted = performance.now();
|
|
2048
|
+
if ((method === 'message.send' || method === 'group.send') && params && this._isEchoPayload(params.payload ?? params.content)) {
|
|
2049
|
+
params = { ...params };
|
|
2050
|
+
this._echoStarts.set(params, echoStarted);
|
|
2051
|
+
}
|
|
2052
|
+
const lifecycle = ['group.transfer_owner', 'org.lifecycle.transfer', 'group.dissolve', 'org.lifecycle.dissolve'].includes(method);
|
|
2053
|
+
let standbyRelease;
|
|
2054
|
+
if (lifecycle && this._udpStandby) {
|
|
2055
|
+
const deadline = Math.min(options?.deadline ?? Infinity, typeof params?._rpc_deadline === 'number' ? params._rpc_deadline : Infinity, Date.now() + Number(this._sessionOptions?.timeouts?.call ?? 35) * 1000);
|
|
2056
|
+
standbyRelease = await this._udpStandby.acquire(method, deadline);
|
|
2057
|
+
options = { ...options, deadline };
|
|
2034
2058
|
}
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2059
|
+
try {
|
|
2060
|
+
const assertCurrent = lifecycle ? this._entityOwnershipGuard() : undefined;
|
|
2061
|
+
const recoverOwnership = lifecycle ? await this._prepareEntityOwnershipLifecycle(method, params ?? {}) : undefined;
|
|
2062
|
+
await this._checkEntityOwnershipOnAccess(method, params ?? {});
|
|
2063
|
+
const readIdentity = ['message.read', 'message.read_state', 'group.read', 'group.read_state'].includes(method)
|
|
2064
|
+
? this._ensureReadStateIdentity() : null;
|
|
2065
|
+
try {
|
|
2066
|
+
const result = await (options === undefined ? this._rpcPipeline.call(method, params, assertCurrent) : this._rpcPipeline.call(method, params, assertCurrent, options));
|
|
2067
|
+
await this._safeAsync(this._getCredentialRenewal().observe(method, params ?? {}, result));
|
|
2068
|
+
if (readIdentity !== null && readIdentity === this._ensureReadStateIdentity())
|
|
2069
|
+
this._observeReadStateResult(method, result, params ?? {});
|
|
2070
|
+
return result;
|
|
2071
|
+
}
|
|
2072
|
+
catch (err) {
|
|
2073
|
+
if (recoverOwnership) {
|
|
2074
|
+
try {
|
|
2075
|
+
await recoverOwnership(err);
|
|
2076
|
+
}
|
|
2077
|
+
catch { }
|
|
2039
2078
|
}
|
|
2040
|
-
|
|
2079
|
+
await this._maybeHandleHalfOpenRpcFailure(err);
|
|
2080
|
+
const returned = await this._echoFailure(method, params, err);
|
|
2081
|
+
if (returned)
|
|
2082
|
+
return returned;
|
|
2083
|
+
throw err;
|
|
2041
2084
|
}
|
|
2042
|
-
|
|
2043
|
-
|
|
2085
|
+
}
|
|
2086
|
+
finally {
|
|
2087
|
+
standbyRelease?.();
|
|
2044
2088
|
}
|
|
2045
2089
|
}
|
|
2046
2090
|
_entityOwnershipGuard() {
|
|
@@ -2083,6 +2127,7 @@ export class AUNClient {
|
|
|
2083
2127
|
throw ownershipFailure('conflict', 'entity lifecycle ownership action changed');
|
|
2084
2128
|
const ownership = { statement: declaration.statement, action, ...(transfer ? { transfer: { new_owner_aid: ownershipAid(newOwner, 'new_owner_aid'), target_relation_version: declaration.statement.relation_version + 1, expires_at: Number(params.expires_at ?? Math.floor(Date.now() / 1000) + 3600) } } : {}) };
|
|
2085
2129
|
const raw = signOwnershipFile(owner, `---\nowner_aid: ${owner.aid}\nsub_aid: ${target}\nownership: ${JSON.stringify(ownership)}\n---\n`, 'owner');
|
|
2130
|
+
await this._getOwnershipFiles().verifyFile(raw, 'owner', target, owner.aid, true);
|
|
2086
2131
|
await storage.writeOwnerMd(owner.aid, target, raw, original, assertCurrent);
|
|
2087
2132
|
return async (error) => {
|
|
2088
2133
|
if (!(error instanceof AUNError) || error instanceof TimeoutError || error instanceof ConnectionError || error.retryable || isJsonObject(error.data) && error.data.committed === true || ![-32602, 4000, 4030, 403].includes(error.code) && !['INVALID_ARGUMENT', 'ROLE_ACCESS_DENIED', 'MEMBER_NOT_FOUND', 'permission_denied', 'member_banned', 'member_blocked'].includes(error.stringCode))
|
|
@@ -2228,12 +2273,14 @@ export class AUNClient {
|
|
|
2228
2273
|
else {
|
|
2229
2274
|
const payload = `---\nowner_aid: ${owner.aid}\nsub_aid: ${aid}\nownership: ${JSON.stringify({ statement, action: 'declare' })}\n---\n`;
|
|
2230
2275
|
const raw = signOwnershipFile(owner, payload, 'owner');
|
|
2276
|
+
await this._getOwnershipFiles().verifyFile(raw, 'owner', aid, owner.aid, true);
|
|
2231
2277
|
assertCurrent();
|
|
2232
2278
|
await storage.writeOwnerMd(owner.aid, aid, raw, original, assertCurrent);
|
|
2233
2279
|
}
|
|
2234
2280
|
const source = content ?? (observed.exists ? observed.content : buildDefaultAgentMd(aid, { type, name: String(entity.name ?? aid) }));
|
|
2235
2281
|
const updated = updateOwnershipAgentMd(source, { owner: owner.aid, ownership: { statement } }).replace(/^entity_cert_fingerprint:.*(?:\r?\n|$)/m, '');
|
|
2236
2282
|
const signed = signOwnershipFile(local.data.aid, updated, 'agent');
|
|
2283
|
+
await this._getOwnershipFiles().verifyFile(signed, 'agent', aid, undefined, true);
|
|
2237
2284
|
if (content === undefined) {
|
|
2238
2285
|
if (!previous || previous.owner_aid !== owner.aid || previous.relation_version !== version) {
|
|
2239
2286
|
assertCurrent();
|
|
@@ -2268,9 +2315,27 @@ export class AUNClient {
|
|
|
2268
2315
|
const groupId = String(params.group_aid ?? params.groupAid ?? params.group_id ?? params.groupId ?? message.slice(prefix.length)).trim();
|
|
2269
2316
|
const target = normalizeGroupId(groupId.toLowerCase(), { localIssuer: this._aid?.split('.').slice(1).join('.') });
|
|
2270
2317
|
const key = `${this._aid}:${this._serviceEventIdentityVersion}:${this._entityOwnershipConnection}:${target}`;
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2318
|
+
const assertCurrent = this._entityOwnershipGuard();
|
|
2319
|
+
try {
|
|
2320
|
+
assertCurrent();
|
|
2321
|
+
const pending = this._groupIdentityOperations.get(key);
|
|
2322
|
+
if (pending && !['group.create', 'group.bind_group_aid'].includes(_method)) {
|
|
2323
|
+
const local = this._aidStore?.load(target);
|
|
2324
|
+
if (!local?.ok || !local.data.aid.isPrivateKeyValid()) {
|
|
2325
|
+
await pending.task;
|
|
2326
|
+
assertCurrent();
|
|
2327
|
+
return true;
|
|
2328
|
+
}
|
|
2329
|
+
}
|
|
2330
|
+
if (!this._entityOwnershipChecks.has(key))
|
|
2331
|
+
await this._checkEntityOwnershipOnAccess('group.get_info', { group_id: groupId });
|
|
2332
|
+
const recovered = await this._entityOwnershipChecks.get(key) ?? false;
|
|
2333
|
+
assertCurrent();
|
|
2334
|
+
return recovered;
|
|
2335
|
+
}
|
|
2336
|
+
catch {
|
|
2337
|
+
return false;
|
|
2338
|
+
}
|
|
2274
2339
|
}
|
|
2275
2340
|
}
|
|
2276
2341
|
const agentPrefix = 'valid agent.md is required for ';
|
|
@@ -2319,17 +2384,14 @@ export class AUNClient {
|
|
|
2319
2384
|
catch (exc) {
|
|
2320
2385
|
this._clientLog.warn(`post-connect agent.md check failed: ${formatCaughtError(exc)}`);
|
|
2321
2386
|
}
|
|
2322
|
-
const store = this._aidStore;
|
|
2323
|
-
if (!store)
|
|
2324
|
-
return;
|
|
2325
|
-
await this._resumeOrgIdentityAfterConnect(store);
|
|
2326
2387
|
}
|
|
2327
2388
|
async _runGroupIdentityOperation(groupId, operation, context = '') {
|
|
2328
2389
|
const aid = String(this._aid ?? '').trim();
|
|
2329
2390
|
const dot = aid.indexOf('.');
|
|
2330
|
-
const
|
|
2331
|
-
if (!
|
|
2391
|
+
const target = normalizeGroupId(groupId, { localIssuer: dot >= 0 ? aid.slice(dot + 1) : '' }).toLowerCase();
|
|
2392
|
+
if (!target)
|
|
2332
2393
|
return operation();
|
|
2394
|
+
const key = `${this._aid}:${this._serviceEventIdentityVersion}:${this._entityOwnershipConnection}:${target}`;
|
|
2333
2395
|
const pending = this._groupIdentityOperations.get(key);
|
|
2334
2396
|
if (pending && pending.context !== context)
|
|
2335
2397
|
throw ownershipFailure('conflict', 'pending Org creation context changed');
|
|
@@ -2413,12 +2475,42 @@ export class AUNClient {
|
|
|
2413
2475
|
tokens.saveMetadata(orgAid, { org_identity_creation: null });
|
|
2414
2476
|
return checkpoint.result;
|
|
2415
2477
|
}
|
|
2416
|
-
|
|
2478
|
+
_schedulePostConnectRecovery() {
|
|
2479
|
+
this._postConnectRecoveryAbort?.abort();
|
|
2480
|
+
if (this._postConnectRecoveryTimer !== null)
|
|
2481
|
+
clearTimeout(this._postConnectRecoveryTimer);
|
|
2482
|
+
this._postConnectRecoveryTimer = null;
|
|
2483
|
+
const store = this._aidStore;
|
|
2484
|
+
if (this._sessionOptions.background_sync === false)
|
|
2485
|
+
return;
|
|
2486
|
+
const abort = new AbortController();
|
|
2487
|
+
this._postConnectRecoveryAbort = abort;
|
|
2488
|
+
const aid = this._aid, version = this._serviceEventIdentityVersion, connection = this._entityOwnershipConnection;
|
|
2489
|
+
const current = () => !abort.signal.aborted && aid === this._aid && version === this._serviceEventIdentityVersion && connection === this._entityOwnershipConnection && ['ready', 'connected'].includes(this._state) && !this._closing && !this._disconnecting;
|
|
2490
|
+
this._postConnectRecoveryTimer = setTimeout(() => {
|
|
2491
|
+
this._postConnectRecoveryTimer = null;
|
|
2492
|
+
if (!current())
|
|
2493
|
+
return;
|
|
2494
|
+
this._safeAsync(this._delivery.restoreMessageClassPulls());
|
|
2495
|
+
void this._fillP2pGap().catch((exc) => {
|
|
2496
|
+
this._clientLog.warn(`schedule post-connect P2P gap fill failed: ${formatCaughtError(exc)}`);
|
|
2497
|
+
});
|
|
2498
|
+
if (store && !this._ownershipOperation)
|
|
2499
|
+
this._safeAsync(this._resumeOrgIdentityAfterConnect(store, current));
|
|
2500
|
+
}, 30_000 + Math.random() * 30_000);
|
|
2501
|
+
this._postConnectRecoveryTimer.unref?.();
|
|
2502
|
+
}
|
|
2503
|
+
async _resumeOrgIdentityAfterConnect(store, current = () => true) {
|
|
2504
|
+
if (!current())
|
|
2505
|
+
return;
|
|
2417
2506
|
const keyStore = store._keystore;
|
|
2418
2507
|
const tokens = store._tokenStore;
|
|
2419
2508
|
if (typeof keyStore?.listIdentities !== 'function' || typeof tokens?.loadMetadata !== 'function')
|
|
2420
2509
|
return;
|
|
2421
2510
|
for (const aid of keyStore.listIdentities()) {
|
|
2511
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
2512
|
+
if (!current())
|
|
2513
|
+
return;
|
|
2422
2514
|
const metadata = typeof tokens._loadMetadataStrict === 'function' ? tokens._loadMetadataStrict(aid) : tokens.loadMetadata(aid);
|
|
2423
2515
|
const creation = metadata?.org_identity_creation;
|
|
2424
2516
|
const transfer = metadata?.org_identity_transfer;
|
|
@@ -3593,6 +3685,8 @@ export class AUNClient {
|
|
|
3593
3685
|
// ── 事件自动解密管线 ──────────────────────────────────────
|
|
3594
3686
|
/** 处理 transport 层推送的原始 P2P 消息 */
|
|
3595
3687
|
async _onRawMessageReceived(data) {
|
|
3688
|
+
if (isJsonObject(data))
|
|
3689
|
+
this._echoStarts.set(data, performance.now());
|
|
3596
3690
|
if (this._controlMessages().onPush(data))
|
|
3597
3691
|
return;
|
|
3598
3692
|
return this._delivery.onRawMessageReceived(data);
|
|
@@ -3602,6 +3696,8 @@ export class AUNClient {
|
|
|
3602
3696
|
}
|
|
3603
3697
|
/** 处理群组消息推送:自动解密后 re-publish */
|
|
3604
3698
|
async _onRawGroupMessageCreated(data) {
|
|
3699
|
+
if (isJsonObject(data))
|
|
3700
|
+
this._echoStarts.set(data, performance.now());
|
|
3605
3701
|
if (this._controlMessages().onPush(data, true))
|
|
3606
3702
|
return;
|
|
3607
3703
|
return this._delivery.onRawGroupMessageCreated(data);
|
|
@@ -3716,21 +3812,70 @@ export class AUNClient {
|
|
|
3716
3812
|
const ms = String(now.getMilliseconds()).padStart(3, '0');
|
|
3717
3813
|
return `${hh}:${mm}:${ss}.${ms}`;
|
|
3718
3814
|
}
|
|
3815
|
+
_echoStarts = new WeakMap();
|
|
3719
3816
|
_isEchoPayload(payload) {
|
|
3720
3817
|
if (!payload || typeof payload !== 'object' || Array.isArray(payload))
|
|
3721
3818
|
return false;
|
|
3722
3819
|
const text = payload.text;
|
|
3723
|
-
if (typeof text !== 'string'
|
|
3820
|
+
if (typeof text !== 'string')
|
|
3724
3821
|
return false;
|
|
3725
|
-
|
|
3822
|
+
const first = text.split('\n', 1)[0].trimStart();
|
|
3823
|
+
return /^(?:echo\.(?:ecdiag|ask)(?:\s|$)|echo(?:\s|\[|$))/i.test(first);
|
|
3824
|
+
}
|
|
3825
|
+
_echoHop(payload) {
|
|
3826
|
+
if (Number.isInteger(payload.hop)) {
|
|
3827
|
+
if (!Number.isSafeInteger(payload.hop))
|
|
3828
|
+
throw new ValidationError('echo hop must be a JSON safe integer');
|
|
3829
|
+
return Number(payload.hop);
|
|
3830
|
+
}
|
|
3831
|
+
const limit = /(?:^|\s)hop=([+-]?\d+)(?=\s|$)/i.exec(payload.text.split('\n', 1)[0]);
|
|
3832
|
+
const hop = limit ? Number(limit[1]) : 100;
|
|
3833
|
+
if (!Number.isSafeInteger(hop))
|
|
3834
|
+
throw new ValidationError('echo hop must be a JSON safe integer');
|
|
3835
|
+
return hop;
|
|
3836
|
+
}
|
|
3837
|
+
_isEchoReturn(payload) {
|
|
3838
|
+
return this._echoHop(payload) < 0 || payload.dir === 'ret' || payload.dir !== 'fwd' && payload.text.split('\n').some(line => line.trim() === '[AUN-Echo.return]');
|
|
3839
|
+
}
|
|
3840
|
+
_appendEchoTrace(payload, stage, started, endpoint = false) {
|
|
3841
|
+
const previousHop = this._echoHop(payload);
|
|
3842
|
+
const returning = endpoint || this._isEchoReturn(payload);
|
|
3843
|
+
const hop = returning ? previousHop - 1 : Math.max(previousHop - 1, 0);
|
|
3844
|
+
if (!Number.isSafeInteger(hop))
|
|
3845
|
+
throw new ValidationError('echo hop decrement exceeds the JSON safe integer range');
|
|
3846
|
+
const now = new Date();
|
|
3847
|
+
const offset = -now.getTimezoneOffset();
|
|
3848
|
+
const tz = `${offset < 0 ? '-' : '+'}${String(Math.floor(Math.abs(offset) / 60)).padStart(2, '0')}:${String(Math.abs(offset) % 60).padStart(2, '0')}`;
|
|
3849
|
+
const uptime = this._connectedAt ? Math.floor((now.getTime() - this._connectedAt) / 1000) : 0;
|
|
3850
|
+
const duration = Math.max(0, Math.round((performance.now() - started) * 1000) / 1000);
|
|
3851
|
+
const trace = `${this._echoTimestamp()} [AUN-SDK.${stage}] dir=${returning ? 'ret' : 'fwd'} hop=${hop} enc=0 ver=${VERSION} epoch=${now.getTime()} tz=${tz}${returning ? ` duration_ms=${duration}` : ''} aid=${this._aid ?? '-'} conn_uptime=${uptime}s`;
|
|
3852
|
+
const result = { ...payload, type: 'text', hop, dir: returning ? 'ret' : 'fwd', text: payload.text.trimStart() + '\n' + trace };
|
|
3853
|
+
delete result.max_hop;
|
|
3854
|
+
delete result.rhop;
|
|
3855
|
+
return result;
|
|
3726
3856
|
}
|
|
3727
3857
|
_maybeAppendEchoTraceSend(params) {
|
|
3858
|
+
const reflected = params._echo_reflection === true;
|
|
3859
|
+
delete params._echo_reflection;
|
|
3860
|
+
for (const key of ['hop', 'max_hop', 'rhop', 'dir'])
|
|
3861
|
+
delete params[key];
|
|
3728
3862
|
const payload = params.payload;
|
|
3729
|
-
if (!this._isEchoPayload(payload))
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3863
|
+
if (!this._isEchoPayload(payload)) {
|
|
3864
|
+
if (payload && typeof payload === 'object' && !Array.isArray(payload)) {
|
|
3865
|
+
const clean = { ...payload };
|
|
3866
|
+
for (const key of ['hop', 'max_hop', 'rhop', 'dir'])
|
|
3867
|
+
delete clean[key];
|
|
3868
|
+
params.payload = clean;
|
|
3869
|
+
}
|
|
3870
|
+
return false;
|
|
3871
|
+
}
|
|
3872
|
+
if (reflected)
|
|
3873
|
+
return false;
|
|
3874
|
+
const started = this._echoStarts.get(params) ?? performance.now();
|
|
3875
|
+
this._echoStarts.set(params, started);
|
|
3876
|
+
const next = this._appendEchoTrace(payload, 'send', started);
|
|
3877
|
+
params.payload = next;
|
|
3878
|
+
return next.dir === 'fwd' && next.hop === 0;
|
|
3734
3879
|
}
|
|
3735
3880
|
_shouldSkipEventSignature(event) {
|
|
3736
3881
|
if (event.encrypted || event.encrypt)
|
|
@@ -3738,14 +3883,79 @@ export class AUNClient {
|
|
|
3738
3883
|
return this._isEchoPayload(event.payload);
|
|
3739
3884
|
}
|
|
3740
3885
|
_maybeAppendEchoTraceReceive(msg) {
|
|
3741
|
-
if (msg.encrypted)
|
|
3742
|
-
return;
|
|
3886
|
+
if (msg.encrypted || msg.encrypt)
|
|
3887
|
+
return false;
|
|
3888
|
+
const payload = msg.payload;
|
|
3889
|
+
if (!this._isEchoPayload(payload) || String(payload.type ?? '').startsWith('e2ee.') || payload.ciphertext)
|
|
3890
|
+
return false;
|
|
3891
|
+
const started = this._echoStarts.get(msg) ?? performance.now();
|
|
3892
|
+
this._echoStarts.set(msg, started);
|
|
3893
|
+
const next = this._appendEchoTrace(payload, 'receive', started);
|
|
3894
|
+
msg.payload = next;
|
|
3895
|
+
return next.dir === 'fwd' && next.hop === 0;
|
|
3896
|
+
}
|
|
3897
|
+
async _returnReceivedEcho(msg) {
|
|
3743
3898
|
const payload = msg.payload;
|
|
3744
|
-
|
|
3899
|
+
const sender = String(msg.from ?? msg.sender_aid ?? msg.from_aid ?? '');
|
|
3900
|
+
const returned = this._appendEchoTrace(payload, 'receive', this._echoStarts.get(msg) ?? performance.now(), true);
|
|
3901
|
+
returned.text = String(returned.text) + '\n[AUN-Echo.return]';
|
|
3902
|
+
const groupId = String(msg.group_id ?? '');
|
|
3903
|
+
await this._rpcPipeline.call('message.send', {
|
|
3904
|
+
to: sender, encrypt: false, _echo_reflection: true,
|
|
3905
|
+
...(groupId ? { group_id: groupId } : {}),
|
|
3906
|
+
payload: returned,
|
|
3907
|
+
});
|
|
3908
|
+
}
|
|
3909
|
+
async _echoFailure(method, params, error, traced = false) {
|
|
3910
|
+
if (!['message.send', 'group.send'].includes(method) || !params || !this._isEchoPayload(params.payload) || !(error instanceof AUNError) || error instanceof AuthError || error._echoDeliveryAttempted)
|
|
3911
|
+
return;
|
|
3912
|
+
const payload = params.payload;
|
|
3913
|
+
if (String(payload.type ?? '').toLowerCase().startsWith('e2ee') || payload.ciphertext)
|
|
3914
|
+
return;
|
|
3915
|
+
const started = this._echoStarts.get(params) ?? performance.now();
|
|
3916
|
+
let sent;
|
|
3917
|
+
try {
|
|
3918
|
+
sent = traced ? payload : this._appendEchoTrace(payload, 'send', started);
|
|
3919
|
+
}
|
|
3920
|
+
catch {
|
|
3921
|
+
return;
|
|
3922
|
+
}
|
|
3923
|
+
const reason = error instanceof TimeoutError || error.code === -32011 ? 'timeout' : 'send_failed';
|
|
3924
|
+
const state = error instanceof ValidationError || error instanceof ConnectionError && /not connected|closing|closed/i.test(error.message) ? 'not_delivered' : 'unknown';
|
|
3925
|
+
const detail = { code: error.code, message: error.message, data: error.data };
|
|
3926
|
+
const result = { status: 'echo_return', message_id: params.message_id ?? `m-${crypto.randomUUID()}`, from: params.to ?? params.group_id, to: this._aid, payload: sent, reason, delivery_state: state, echo_error: detail };
|
|
3927
|
+
if (method === 'group.send')
|
|
3928
|
+
result.group_id = params.group_id;
|
|
3929
|
+
await this._publishEchoReturn(method, params, result);
|
|
3930
|
+
return result;
|
|
3931
|
+
}
|
|
3932
|
+
async _publishEchoReturn(method, params, result) {
|
|
3933
|
+
if (!this._isEchoPayload(result.payload))
|
|
3745
3934
|
return;
|
|
3746
|
-
const
|
|
3747
|
-
const
|
|
3748
|
-
|
|
3935
|
+
const payload = result.payload;
|
|
3936
|
+
const returnedPayload = this._appendEchoTrace(payload, 'receive', this._echoStarts.get(params) ?? performance.now(), !this._isEchoReturn(payload));
|
|
3937
|
+
if (result.echo_error)
|
|
3938
|
+
returnedPayload.text += ` reason=${result.reason} delivery_state=${result.delivery_state} error=${JSON.stringify(result.echo_error)}`;
|
|
3939
|
+
if (!String(returnedPayload.text).split('\n').some(line => line.trim() === '[AUN-Echo.return]'))
|
|
3940
|
+
returnedPayload.text += '\n[AUN-Echo.return]';
|
|
3941
|
+
result.payload = returnedPayload;
|
|
3942
|
+
if (this._state === 'closed')
|
|
3943
|
+
return;
|
|
3944
|
+
const groupId = String(result.group_id ?? params.group_id ?? params.group_aid ?? '');
|
|
3945
|
+
const sender = String(result.from ?? result.sender_aid ?? params.to ?? this._aid ?? '');
|
|
3946
|
+
try {
|
|
3947
|
+
await this._publishAppEvent(method === 'group.send' ? 'group.message_created' : 'message.received', {
|
|
3948
|
+
...result,
|
|
3949
|
+
message_id: String(result.message_id ?? params.message_id ?? ''),
|
|
3950
|
+
from: sender, sender_aid: sender, to: this._aid ?? '', encrypted: false,
|
|
3951
|
+
...(groupId ? { group_id: groupId } : {}), payload: returnedPayload,
|
|
3952
|
+
}, 'echo_return');
|
|
3953
|
+
}
|
|
3954
|
+
catch (error) {
|
|
3955
|
+
if (error instanceof Error)
|
|
3956
|
+
error._echoDeliveryAttempted = true;
|
|
3957
|
+
throw error;
|
|
3958
|
+
}
|
|
3749
3959
|
}
|
|
3750
3960
|
_debugJson(value) {
|
|
3751
3961
|
const seen = new WeakSet();
|
|
@@ -4637,6 +4847,10 @@ export class AUNClient {
|
|
|
4637
4847
|
: {};
|
|
4638
4848
|
const capabilities = isJsonObject(extraInfo._capabilities) ? { ...extraInfo._capabilities } : {};
|
|
4639
4849
|
delete capabilities.message_classes_v1;
|
|
4850
|
+
if (params.udp_standby !== false && params.connection_kind !== 'short')
|
|
4851
|
+
capabilities.udp_standby_v1 = true;
|
|
4852
|
+
else
|
|
4853
|
+
delete capabilities.udp_standby_v1;
|
|
4640
4854
|
if (typeof this._tokenStore.updateInstanceState === 'function') {
|
|
4641
4855
|
capabilities.message_classes_v2 = { p2p: ['normal', 'control', 'sdk_control'], group: ['normal', 'control'] };
|
|
4642
4856
|
capabilities.sdk_control_protocols = { 'aun.agent_md.changed': [1], 'aun.pubsub': [1] };
|
|
@@ -4659,12 +4873,21 @@ export class AUNClient {
|
|
|
4659
4873
|
const challenge = await this._transport.connect(gatewayUrl);
|
|
4660
4874
|
this._assertReconnectOwner(reconnectOwner);
|
|
4661
4875
|
this._lifecycle.assertConnectionAttemptOwner(connectionOwner);
|
|
4662
|
-
const authTransport =
|
|
4876
|
+
const authTransport = {
|
|
4663
4877
|
call: (...args) => {
|
|
4664
4878
|
this._assertReconnectOwner(reconnectOwner);
|
|
4879
|
+
this._lifecycle.assertConnectionAttemptOwner(connectionOwner);
|
|
4665
4880
|
return this._transport.call(...args);
|
|
4666
4881
|
},
|
|
4667
|
-
|
|
4882
|
+
connect: async (url) => {
|
|
4883
|
+
this._assertReconnectOwner(reconnectOwner);
|
|
4884
|
+
this._lifecycle.assertConnectionAttemptOwner(connectionOwner);
|
|
4885
|
+
const nextChallenge = await this._transport.connect(url);
|
|
4886
|
+
this._assertReconnectOwner(reconnectOwner);
|
|
4887
|
+
this._lifecycle.assertConnectionAttemptOwner(connectionOwner);
|
|
4888
|
+
return nextChallenge;
|
|
4889
|
+
},
|
|
4890
|
+
};
|
|
4668
4891
|
this._clientLog.debug(`WebSocket connection established: gateway=${gatewayUrl}`);
|
|
4669
4892
|
this._state = 'authenticating';
|
|
4670
4893
|
if (allowReauth) {
|
|
@@ -4697,6 +4920,7 @@ export class AUNClient {
|
|
|
4697
4920
|
this._bindGatewayRuntimeIdentity(auth.hello, params);
|
|
4698
4921
|
this._gatewayConnectionId = isJsonObject(auth.hello) && isJsonObject(auth.hello.connection) ? String(auth.hello.connection.id ?? '') : '';
|
|
4699
4922
|
this._negotiatedCapabilities = isJsonObject(auth.hello) && isJsonObject(auth.hello.negotiated_capabilities) ? auth.hello.negotiated_capabilities : {};
|
|
4923
|
+
this._udpStandbyPolicy = isJsonObject(auth.hello) && isJsonObject(auth.hello.udp_standby) ? auth.hello.udp_standby : undefined;
|
|
4700
4924
|
if (isJsonObject(auth.hello))
|
|
4701
4925
|
await this._applyRiskHello(auth.hello);
|
|
4702
4926
|
}
|
|
@@ -4719,6 +4943,7 @@ export class AUNClient {
|
|
|
4719
4943
|
this._bindGatewayRuntimeIdentity(hello, params);
|
|
4720
4944
|
this._gatewayConnectionId = isJsonObject(hello) && isJsonObject(hello.connection) ? String(hello.connection.id ?? '') : '';
|
|
4721
4945
|
this._negotiatedCapabilities = isJsonObject(hello) && isJsonObject(hello.negotiated_capabilities) ? hello.negotiated_capabilities : {};
|
|
4946
|
+
this._udpStandbyPolicy = isJsonObject(hello) && isJsonObject(hello.udp_standby) ? hello.udp_standby : undefined;
|
|
4722
4947
|
if (isJsonObject(hello))
|
|
4723
4948
|
await this._applyRiskHello(hello);
|
|
4724
4949
|
}
|
|
@@ -4789,19 +5014,16 @@ export class AUNClient {
|
|
|
4789
5014
|
this._agentMdChanges = new AgentMdChangeCoordinator(this);
|
|
4790
5015
|
// connect/reconnect 成功后自动触发一次 P2P message.v2.pull,补齐离线期间积压
|
|
4791
5016
|
// 群消息按惰性触发,不在此处主动 pull
|
|
4792
|
-
if (backgroundSyncEnabled) {
|
|
4793
|
-
void this._fillP2pGap().catch((exc) => {
|
|
4794
|
-
this._clientLog.warn(`schedule post-connect P2P gap fill failed: ${formatCaughtError(exc)}`);
|
|
4795
|
-
});
|
|
4796
|
-
}
|
|
4797
5017
|
this._entityOwnershipConnection++;
|
|
4798
5018
|
this._entityOwnershipChecks.clear();
|
|
4799
5019
|
if (!this._ownershipOperation)
|
|
4800
5020
|
this._safeAsync(this._checkIdentityAdmissionAfterConnect());
|
|
4801
|
-
|
|
5021
|
+
if (backgroundSyncEnabled)
|
|
5022
|
+
this._schedulePostConnectRecovery();
|
|
4802
5023
|
if (!isShortConnection && backgroundSyncEnabled && this._v2Session) {
|
|
4803
5024
|
this._safeAsync(this._v2AutoConfirmPendingProposals());
|
|
4804
5025
|
}
|
|
5026
|
+
this._activateUdpStandby();
|
|
4805
5027
|
this._clientLog.debug(`_connectOnce exit: elapsed=${Date.now() - tStart}ms gateway=${gatewayUrl}, aid=${this._aid ?? ''}`);
|
|
4806
5028
|
}
|
|
4807
5029
|
catch (err) {
|
|
@@ -5724,6 +5946,11 @@ export class AUNClient {
|
|
|
5724
5946
|
}
|
|
5725
5947
|
/** 停止所有后台任务 */
|
|
5726
5948
|
_stopBackgroundTasks() {
|
|
5949
|
+
this._postConnectRecoveryAbort?.abort();
|
|
5950
|
+
this._postConnectRecoveryAbort = null;
|
|
5951
|
+
if (this._postConnectRecoveryTimer !== null)
|
|
5952
|
+
clearTimeout(this._postConnectRecoveryTimer);
|
|
5953
|
+
this._postConnectRecoveryTimer = null;
|
|
5727
5954
|
this._syncChannelCoordinator?.reset();
|
|
5728
5955
|
this._stopCredentialRenewal();
|
|
5729
5956
|
this._auth.invalidateStrictPkiCache();
|
|
@@ -6118,6 +6345,8 @@ export class AUNClient {
|
|
|
6118
6345
|
}
|
|
6119
6346
|
/** 传输层断线回调 */
|
|
6120
6347
|
async _handleTransportDisconnect(error, closeCode, initialOptions) {
|
|
6348
|
+
if (this._udpStandby?.handlesDisconnect())
|
|
6349
|
+
return;
|
|
6121
6350
|
if (this._closing || this._disconnecting || this.state === ConnectionState.CLOSED)
|
|
6122
6351
|
return;
|
|
6123
6352
|
// 已在重连中则跳过,避免心跳超时和 transport 断线回调重复触发
|
|
@@ -6198,6 +6427,50 @@ export class AUNClient {
|
|
|
6198
6427
|
}
|
|
6199
6428
|
this._startReconnect(serverInitiated, initialOptions, reconnectAbort);
|
|
6200
6429
|
}
|
|
6430
|
+
_activateUdpStandby() {
|
|
6431
|
+
if (this._negotiatedCapabilities.udp_standby_v1 !== true || this._udpStandbyPolicy?.enabled !== true) {
|
|
6432
|
+
this._udpStandby?.deactivate();
|
|
6433
|
+
this._transport.setStandbyGate(null);
|
|
6434
|
+
return;
|
|
6435
|
+
}
|
|
6436
|
+
this._udpStandby ??= new UdpStandbyController({
|
|
6437
|
+
call: (method, params, timeout) => this._transport.call(method, params, timeout),
|
|
6438
|
+
canPrepare: async (timeout) => {
|
|
6439
|
+
if (!this._aid)
|
|
6440
|
+
return true;
|
|
6441
|
+
try {
|
|
6442
|
+
if (!this._aidStore) {
|
|
6443
|
+
const record = this._agentMdManager?._loadRecord?.(this._aid, true) ?? this._agentMdManager?.loadRecord(this._aid);
|
|
6444
|
+
const content = String(record?.local_draft_content ?? '') || String(record?.content ?? '');
|
|
6445
|
+
return !content || validateAgentMdReadDocument(content).frontmatter.type !== 'human';
|
|
6446
|
+
}
|
|
6447
|
+
const result = await this._aidStore.resolve(this._aid, { timeout });
|
|
6448
|
+
return !result.ok || result.data?.agent_md?.frontmatter.type !== 'human';
|
|
6449
|
+
}
|
|
6450
|
+
catch {
|
|
6451
|
+
return true;
|
|
6452
|
+
}
|
|
6453
|
+
},
|
|
6454
|
+
canStandby: () => this._state === 'ready' && !this._closing && !this._disconnecting && !this._reconnectActive
|
|
6455
|
+
&& this._sessionOptions?.background_sync !== false
|
|
6456
|
+
&& this._groupIdentityOperations.size === 0
|
|
6457
|
+
&& this._transport.isConnected() && this._transport.rpcBudget.counts.total === 0
|
|
6458
|
+
&& this._transport.foregroundWaiting === 0 && this._transport.rpcBudget.backgroundWaiting === 0
|
|
6459
|
+
&& this._dispatcher.isIdle && !this._delivery?.hasAnyPendingPull()
|
|
6460
|
+
&& (this._syncChannelCoordinator?.isIdle ?? true),
|
|
6461
|
+
prepareParams: () => ({ connection_id: this._gatewayConnectionId }),
|
|
6462
|
+
detach: () => this._transport.close(),
|
|
6463
|
+
reconnect: async () => {
|
|
6464
|
+
if (this._closing || this._disconnecting)
|
|
6465
|
+
throw new ConnectionError('client closed');
|
|
6466
|
+
this._state = 'standby';
|
|
6467
|
+
await this._lifecycle.connect(this._udpStandbyConnectOptions);
|
|
6468
|
+
},
|
|
6469
|
+
});
|
|
6470
|
+
this._udpStandby.activate(this._udpStandbyPolicy);
|
|
6471
|
+
const standby = this._udpStandby;
|
|
6472
|
+
this._transport.setStandbyGate((method, deadline, signal) => standby.acquire(method, deadline, signal), () => standby.touch(), params => standby.authenticationParams(params));
|
|
6473
|
+
}
|
|
6201
6474
|
/** 启动重连循环(默认无限重试 + 当前基数抖动,仅在不可重试错误、close() 或 max_attempts 耗尽时终止) */
|
|
6202
6475
|
_startReconnect(serverInitiated = false, initialOptions, existingAbort) {
|
|
6203
6476
|
if (existingAbort) {
|