@agentunion/fastaun 0.5.34 → 0.5.35
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 +30 -1
- package/_packed_docs/CHANGELOG.md +30 -1
- package/_packed_docs/INDEX.md +11 -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/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 +18 -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/auth.js +31 -43
- package/dist/auth.js.map +1 -1
- package/dist/client/delivery.d.ts +7 -1
- package/dist/client/delivery.js +137 -16
- 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 +2 -0
- package/dist/client/rpc-pipeline.js +65 -7
- 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 +19 -5
- package/dist/client/v2-e2ee.js.map +1 -1
- package/dist/client.d.ts +15 -0
- package/dist/client.js +310 -49
- 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/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 +88 -7
- package/dist/pubsub/channel.js.map +1 -1
- package/dist/pubsub/client.d.ts +8 -2
- package/dist/pubsub/client.js +59 -17
- 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 +100 -9
- 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 +89 -0
- package/dist/udp-standby.js +487 -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.d.ts
CHANGED
|
@@ -32,6 +32,7 @@ import type { SemanticPersistentState } from './semantic-facade.js';
|
|
|
32
32
|
*/
|
|
33
33
|
export declare function stableStringify(obj: JsonValue | object | undefined): string;
|
|
34
34
|
export interface ConnectionOptions {
|
|
35
|
+
udp_standby?: boolean;
|
|
35
36
|
auto_reconnect?: boolean;
|
|
36
37
|
connect_timeout?: number;
|
|
37
38
|
retry?: JsonObject;
|
|
@@ -143,6 +144,9 @@ export declare class AUNClient {
|
|
|
143
144
|
private _discovery;
|
|
144
145
|
/** 传输层 */
|
|
145
146
|
private _transport;
|
|
147
|
+
private _udpStandby;
|
|
148
|
+
private _udpStandbyPolicy;
|
|
149
|
+
private _udpStandbyConnectOptions;
|
|
146
150
|
/** 认证流程 */
|
|
147
151
|
private _auth;
|
|
148
152
|
resolveCredentialService?: (cid: string) => string | null | undefined | Promise<string | null | undefined>;
|
|
@@ -166,6 +170,8 @@ export declare class AUNClient {
|
|
|
166
170
|
private _groupIdentityOperations;
|
|
167
171
|
private _entityOwnershipConnection;
|
|
168
172
|
private _entityOwnershipChecks;
|
|
173
|
+
private _postConnectRecoveryTimer;
|
|
174
|
+
private _postConnectRecoveryAbort;
|
|
169
175
|
private _groupIndexMetaCache;
|
|
170
176
|
/** 消息序列号跟踪器(群消息 + P2P 空洞检测) */
|
|
171
177
|
private _p2pPeerPullEnabled;
|
|
@@ -423,6 +429,7 @@ export declare class AUNClient {
|
|
|
423
429
|
private _resumeOrgRequest;
|
|
424
430
|
createOrg(params?: RpcParams, options?: OrgIdentityOptions): Promise<RpcResult>;
|
|
425
431
|
private _createOrg;
|
|
432
|
+
private _schedulePostConnectRecovery;
|
|
426
433
|
private _resumeOrgIdentityAfterConnect;
|
|
427
434
|
private _verifyOrgTransferMaterials;
|
|
428
435
|
completeOrgTransfer(params?: RpcParams, options?: OrgIdentityOptions): Promise<RpcResult>;
|
|
@@ -483,10 +490,17 @@ export declare class AUNClient {
|
|
|
483
490
|
private _markPublishedSeq;
|
|
484
491
|
private _publishAppEvent;
|
|
485
492
|
private _echoTimestamp;
|
|
493
|
+
private _echoStarts;
|
|
486
494
|
private _isEchoPayload;
|
|
495
|
+
private _echoHop;
|
|
496
|
+
private _isEchoReturn;
|
|
497
|
+
private _appendEchoTrace;
|
|
487
498
|
private _maybeAppendEchoTraceSend;
|
|
488
499
|
private _shouldSkipEventSignature;
|
|
489
500
|
private _maybeAppendEchoTraceReceive;
|
|
501
|
+
private _returnReceivedEcho;
|
|
502
|
+
private _echoFailure;
|
|
503
|
+
private _publishEchoReturn;
|
|
490
504
|
private _debugJson;
|
|
491
505
|
private _messageEnvelopeFieldsForDebug;
|
|
492
506
|
private _isMessageDebugEnabled;
|
|
@@ -680,6 +694,7 @@ export declare class AUNClient {
|
|
|
680
694
|
private _onGatewayDisconnect;
|
|
681
695
|
/** 传输层断线回调 */
|
|
682
696
|
private _handleTransportDisconnect;
|
|
697
|
+
private _activateUdpStandby;
|
|
683
698
|
/** 启动重连循环(默认无限重试 + 当前基数抖动,仅在不可重试错误、close() 或 max_attempts 耗尽时终止) */
|
|
684
699
|
private _startReconnect;
|
|
685
700
|
private _ownsReconnect;
|
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;
|
|
@@ -1598,6 +1604,10 @@ export class AUNClient {
|
|
|
1598
1604
|
}
|
|
1599
1605
|
_applyAidRuntimeContext(aid) {
|
|
1600
1606
|
this._aidStore = aid._aidStore;
|
|
1607
|
+
this._udpStandby?.close();
|
|
1608
|
+
this._udpStandby = null;
|
|
1609
|
+
this._udpStandbyPolicy = undefined;
|
|
1610
|
+
this._udpStandbyConnectOptions = undefined;
|
|
1601
1611
|
const oldTransport = this._transport;
|
|
1602
1612
|
try {
|
|
1603
1613
|
const closeResult = oldTransport?.close?.();
|
|
@@ -1850,10 +1860,14 @@ export class AUNClient {
|
|
|
1850
1860
|
}
|
|
1851
1861
|
/** 连接到 Gateway;身份来自构造函数或 loadIdentity(aid),认证由 SDK 内部自动完成。 */
|
|
1852
1862
|
async connect(opts) {
|
|
1863
|
+
if (this._udpStandby && ['standby', 'preparing', 'waking'].includes(this._udpStandby.state))
|
|
1864
|
+
return this._udpStandby.ensureActive();
|
|
1865
|
+
this._udpStandbyConnectOptions = opts ? { ...opts } : undefined;
|
|
1853
1866
|
await this._lifecycle.connect(opts);
|
|
1854
1867
|
}
|
|
1855
1868
|
/** 关闭连接 */
|
|
1856
1869
|
async close() {
|
|
1870
|
+
this._udpStandby?.close();
|
|
1857
1871
|
this._risk.close();
|
|
1858
1872
|
this._peerGatewayCache.clear();
|
|
1859
1873
|
return this._lifecycle.close();
|
|
@@ -1863,6 +1877,7 @@ export class AUNClient {
|
|
|
1863
1877
|
* disconnect 是可恢复的:停止心跳、关闭 WebSocket,但不清理 keystore 等状态。
|
|
1864
1878
|
*/
|
|
1865
1879
|
async disconnect() {
|
|
1880
|
+
this._udpStandby?.close();
|
|
1866
1881
|
return this._lifecycle.disconnect();
|
|
1867
1882
|
}
|
|
1868
1883
|
get riskState() { return Object.freeze(structuredClone(this._risk.state)); }
|
|
@@ -2020,27 +2035,47 @@ export class AUNClient {
|
|
|
2020
2035
|
* 自动处理内部方法限制、E2EE 加解密、客户端签名等。
|
|
2021
2036
|
*/
|
|
2022
2037
|
async call(method, params, options) {
|
|
2023
|
-
const
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2038
|
+
const echoStarted = performance.now();
|
|
2039
|
+
if ((method === 'message.send' || method === 'group.send') && params && this._isEchoPayload(params.payload ?? params.content)) {
|
|
2040
|
+
params = { ...params };
|
|
2041
|
+
this._echoStarts.set(params, echoStarted);
|
|
2042
|
+
}
|
|
2043
|
+
const lifecycle = ['group.transfer_owner', 'org.lifecycle.transfer', 'group.dissolve', 'org.lifecycle.dissolve'].includes(method);
|
|
2044
|
+
let standbyRelease;
|
|
2045
|
+
if (lifecycle && this._udpStandby) {
|
|
2046
|
+
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);
|
|
2047
|
+
standbyRelease = await this._udpStandby.acquire(method, deadline);
|
|
2048
|
+
options = { ...options, deadline };
|
|
2034
2049
|
}
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2050
|
+
try {
|
|
2051
|
+
const assertCurrent = lifecycle ? this._entityOwnershipGuard() : undefined;
|
|
2052
|
+
const recoverOwnership = lifecycle ? await this._prepareEntityOwnershipLifecycle(method, params ?? {}) : undefined;
|
|
2053
|
+
await this._checkEntityOwnershipOnAccess(method, params ?? {});
|
|
2054
|
+
const readIdentity = ['message.read', 'message.read_state', 'group.read', 'group.read_state'].includes(method)
|
|
2055
|
+
? this._ensureReadStateIdentity() : null;
|
|
2056
|
+
try {
|
|
2057
|
+
const result = await (options === undefined ? this._rpcPipeline.call(method, params, assertCurrent) : this._rpcPipeline.call(method, params, assertCurrent, options));
|
|
2058
|
+
await this._safeAsync(this._getCredentialRenewal().observe(method, params ?? {}, result));
|
|
2059
|
+
if (readIdentity !== null && readIdentity === this._ensureReadStateIdentity())
|
|
2060
|
+
this._observeReadStateResult(method, result, params ?? {});
|
|
2061
|
+
return result;
|
|
2062
|
+
}
|
|
2063
|
+
catch (err) {
|
|
2064
|
+
if (recoverOwnership) {
|
|
2065
|
+
try {
|
|
2066
|
+
await recoverOwnership(err);
|
|
2067
|
+
}
|
|
2068
|
+
catch { }
|
|
2039
2069
|
}
|
|
2040
|
-
|
|
2070
|
+
await this._maybeHandleHalfOpenRpcFailure(err);
|
|
2071
|
+
const returned = await this._echoFailure(method, params, err);
|
|
2072
|
+
if (returned)
|
|
2073
|
+
return returned;
|
|
2074
|
+
throw err;
|
|
2041
2075
|
}
|
|
2042
|
-
|
|
2043
|
-
|
|
2076
|
+
}
|
|
2077
|
+
finally {
|
|
2078
|
+
standbyRelease?.();
|
|
2044
2079
|
}
|
|
2045
2080
|
}
|
|
2046
2081
|
_entityOwnershipGuard() {
|
|
@@ -2268,9 +2303,27 @@ export class AUNClient {
|
|
|
2268
2303
|
const groupId = String(params.group_aid ?? params.groupAid ?? params.group_id ?? params.groupId ?? message.slice(prefix.length)).trim();
|
|
2269
2304
|
const target = normalizeGroupId(groupId.toLowerCase(), { localIssuer: this._aid?.split('.').slice(1).join('.') });
|
|
2270
2305
|
const key = `${this._aid}:${this._serviceEventIdentityVersion}:${this._entityOwnershipConnection}:${target}`;
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2306
|
+
const assertCurrent = this._entityOwnershipGuard();
|
|
2307
|
+
try {
|
|
2308
|
+
assertCurrent();
|
|
2309
|
+
const pending = this._groupIdentityOperations.get(key);
|
|
2310
|
+
if (pending && !['group.create', 'group.bind_group_aid'].includes(_method)) {
|
|
2311
|
+
const local = this._aidStore?.load(target);
|
|
2312
|
+
if (!local?.ok || !local.data.aid.isPrivateKeyValid()) {
|
|
2313
|
+
await pending.task;
|
|
2314
|
+
assertCurrent();
|
|
2315
|
+
return true;
|
|
2316
|
+
}
|
|
2317
|
+
}
|
|
2318
|
+
if (!this._entityOwnershipChecks.has(key))
|
|
2319
|
+
await this._checkEntityOwnershipOnAccess('group.get_info', { group_id: groupId });
|
|
2320
|
+
const recovered = await this._entityOwnershipChecks.get(key) ?? false;
|
|
2321
|
+
assertCurrent();
|
|
2322
|
+
return recovered;
|
|
2323
|
+
}
|
|
2324
|
+
catch {
|
|
2325
|
+
return false;
|
|
2326
|
+
}
|
|
2274
2327
|
}
|
|
2275
2328
|
}
|
|
2276
2329
|
const agentPrefix = 'valid agent.md is required for ';
|
|
@@ -2319,17 +2372,14 @@ export class AUNClient {
|
|
|
2319
2372
|
catch (exc) {
|
|
2320
2373
|
this._clientLog.warn(`post-connect agent.md check failed: ${formatCaughtError(exc)}`);
|
|
2321
2374
|
}
|
|
2322
|
-
const store = this._aidStore;
|
|
2323
|
-
if (!store)
|
|
2324
|
-
return;
|
|
2325
|
-
await this._resumeOrgIdentityAfterConnect(store);
|
|
2326
2375
|
}
|
|
2327
2376
|
async _runGroupIdentityOperation(groupId, operation, context = '') {
|
|
2328
2377
|
const aid = String(this._aid ?? '').trim();
|
|
2329
2378
|
const dot = aid.indexOf('.');
|
|
2330
|
-
const
|
|
2331
|
-
if (!
|
|
2379
|
+
const target = normalizeGroupId(groupId, { localIssuer: dot >= 0 ? aid.slice(dot + 1) : '' }).toLowerCase();
|
|
2380
|
+
if (!target)
|
|
2332
2381
|
return operation();
|
|
2382
|
+
const key = `${this._aid}:${this._serviceEventIdentityVersion}:${this._entityOwnershipConnection}:${target}`;
|
|
2333
2383
|
const pending = this._groupIdentityOperations.get(key);
|
|
2334
2384
|
if (pending && pending.context !== context)
|
|
2335
2385
|
throw ownershipFailure('conflict', 'pending Org creation context changed');
|
|
@@ -2413,12 +2463,42 @@ export class AUNClient {
|
|
|
2413
2463
|
tokens.saveMetadata(orgAid, { org_identity_creation: null });
|
|
2414
2464
|
return checkpoint.result;
|
|
2415
2465
|
}
|
|
2416
|
-
|
|
2466
|
+
_schedulePostConnectRecovery() {
|
|
2467
|
+
this._postConnectRecoveryAbort?.abort();
|
|
2468
|
+
if (this._postConnectRecoveryTimer !== null)
|
|
2469
|
+
clearTimeout(this._postConnectRecoveryTimer);
|
|
2470
|
+
this._postConnectRecoveryTimer = null;
|
|
2471
|
+
const store = this._aidStore;
|
|
2472
|
+
if (this._sessionOptions.background_sync === false)
|
|
2473
|
+
return;
|
|
2474
|
+
const abort = new AbortController();
|
|
2475
|
+
this._postConnectRecoveryAbort = abort;
|
|
2476
|
+
const aid = this._aid, version = this._serviceEventIdentityVersion, connection = this._entityOwnershipConnection;
|
|
2477
|
+
const current = () => !abort.signal.aborted && aid === this._aid && version === this._serviceEventIdentityVersion && connection === this._entityOwnershipConnection && ['ready', 'connected'].includes(this._state) && !this._closing && !this._disconnecting;
|
|
2478
|
+
this._postConnectRecoveryTimer = setTimeout(() => {
|
|
2479
|
+
this._postConnectRecoveryTimer = null;
|
|
2480
|
+
if (!current())
|
|
2481
|
+
return;
|
|
2482
|
+
this._safeAsync(this._delivery.restoreMessageClassPulls());
|
|
2483
|
+
void this._fillP2pGap().catch((exc) => {
|
|
2484
|
+
this._clientLog.warn(`schedule post-connect P2P gap fill failed: ${formatCaughtError(exc)}`);
|
|
2485
|
+
});
|
|
2486
|
+
if (store && !this._ownershipOperation)
|
|
2487
|
+
this._safeAsync(this._resumeOrgIdentityAfterConnect(store, current));
|
|
2488
|
+
}, 30_000 + Math.random() * 30_000);
|
|
2489
|
+
this._postConnectRecoveryTimer.unref?.();
|
|
2490
|
+
}
|
|
2491
|
+
async _resumeOrgIdentityAfterConnect(store, current = () => true) {
|
|
2492
|
+
if (!current())
|
|
2493
|
+
return;
|
|
2417
2494
|
const keyStore = store._keystore;
|
|
2418
2495
|
const tokens = store._tokenStore;
|
|
2419
2496
|
if (typeof keyStore?.listIdentities !== 'function' || typeof tokens?.loadMetadata !== 'function')
|
|
2420
2497
|
return;
|
|
2421
2498
|
for (const aid of keyStore.listIdentities()) {
|
|
2499
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
2500
|
+
if (!current())
|
|
2501
|
+
return;
|
|
2422
2502
|
const metadata = typeof tokens._loadMetadataStrict === 'function' ? tokens._loadMetadataStrict(aid) : tokens.loadMetadata(aid);
|
|
2423
2503
|
const creation = metadata?.org_identity_creation;
|
|
2424
2504
|
const transfer = metadata?.org_identity_transfer;
|
|
@@ -3593,6 +3673,8 @@ export class AUNClient {
|
|
|
3593
3673
|
// ── 事件自动解密管线 ──────────────────────────────────────
|
|
3594
3674
|
/** 处理 transport 层推送的原始 P2P 消息 */
|
|
3595
3675
|
async _onRawMessageReceived(data) {
|
|
3676
|
+
if (isJsonObject(data))
|
|
3677
|
+
this._echoStarts.set(data, performance.now());
|
|
3596
3678
|
if (this._controlMessages().onPush(data))
|
|
3597
3679
|
return;
|
|
3598
3680
|
return this._delivery.onRawMessageReceived(data);
|
|
@@ -3602,6 +3684,8 @@ export class AUNClient {
|
|
|
3602
3684
|
}
|
|
3603
3685
|
/** 处理群组消息推送:自动解密后 re-publish */
|
|
3604
3686
|
async _onRawGroupMessageCreated(data) {
|
|
3687
|
+
if (isJsonObject(data))
|
|
3688
|
+
this._echoStarts.set(data, performance.now());
|
|
3605
3689
|
if (this._controlMessages().onPush(data, true))
|
|
3606
3690
|
return;
|
|
3607
3691
|
return this._delivery.onRawGroupMessageCreated(data);
|
|
@@ -3716,21 +3800,70 @@ export class AUNClient {
|
|
|
3716
3800
|
const ms = String(now.getMilliseconds()).padStart(3, '0');
|
|
3717
3801
|
return `${hh}:${mm}:${ss}.${ms}`;
|
|
3718
3802
|
}
|
|
3803
|
+
_echoStarts = new WeakMap();
|
|
3719
3804
|
_isEchoPayload(payload) {
|
|
3720
3805
|
if (!payload || typeof payload !== 'object' || Array.isArray(payload))
|
|
3721
3806
|
return false;
|
|
3722
3807
|
const text = payload.text;
|
|
3723
|
-
if (typeof text !== 'string'
|
|
3808
|
+
if (typeof text !== 'string')
|
|
3724
3809
|
return false;
|
|
3725
|
-
|
|
3810
|
+
const first = text.split('\n', 1)[0].trimStart();
|
|
3811
|
+
return /^(?:echo\.(?:ecdiag|ask)(?:\s|$)|echo(?:\s|\[|$))/i.test(first);
|
|
3812
|
+
}
|
|
3813
|
+
_echoHop(payload) {
|
|
3814
|
+
if (Number.isInteger(payload.hop)) {
|
|
3815
|
+
if (!Number.isSafeInteger(payload.hop))
|
|
3816
|
+
throw new ValidationError('echo hop must be a JSON safe integer');
|
|
3817
|
+
return Number(payload.hop);
|
|
3818
|
+
}
|
|
3819
|
+
const limit = /(?:^|\s)hop=([+-]?\d+)(?=\s|$)/i.exec(payload.text.split('\n', 1)[0]);
|
|
3820
|
+
const hop = limit ? Number(limit[1]) : 100;
|
|
3821
|
+
if (!Number.isSafeInteger(hop))
|
|
3822
|
+
throw new ValidationError('echo hop must be a JSON safe integer');
|
|
3823
|
+
return hop;
|
|
3824
|
+
}
|
|
3825
|
+
_isEchoReturn(payload) {
|
|
3826
|
+
return this._echoHop(payload) < 0 || payload.dir === 'ret' || payload.dir !== 'fwd' && payload.text.split('\n').some(line => line.trim() === '[AUN-Echo.return]');
|
|
3827
|
+
}
|
|
3828
|
+
_appendEchoTrace(payload, stage, started, endpoint = false) {
|
|
3829
|
+
const previousHop = this._echoHop(payload);
|
|
3830
|
+
const returning = endpoint || this._isEchoReturn(payload);
|
|
3831
|
+
const hop = returning ? previousHop - 1 : Math.max(previousHop - 1, 0);
|
|
3832
|
+
if (!Number.isSafeInteger(hop))
|
|
3833
|
+
throw new ValidationError('echo hop decrement exceeds the JSON safe integer range');
|
|
3834
|
+
const now = new Date();
|
|
3835
|
+
const offset = -now.getTimezoneOffset();
|
|
3836
|
+
const tz = `${offset < 0 ? '-' : '+'}${String(Math.floor(Math.abs(offset) / 60)).padStart(2, '0')}:${String(Math.abs(offset) % 60).padStart(2, '0')}`;
|
|
3837
|
+
const uptime = this._connectedAt ? Math.floor((now.getTime() - this._connectedAt) / 1000) : 0;
|
|
3838
|
+
const duration = Math.max(0, Math.round((performance.now() - started) * 1000) / 1000);
|
|
3839
|
+
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`;
|
|
3840
|
+
const result = { ...payload, type: 'text', hop, dir: returning ? 'ret' : 'fwd', text: payload.text.trimStart() + '\n' + trace };
|
|
3841
|
+
delete result.max_hop;
|
|
3842
|
+
delete result.rhop;
|
|
3843
|
+
return result;
|
|
3726
3844
|
}
|
|
3727
3845
|
_maybeAppendEchoTraceSend(params) {
|
|
3846
|
+
const reflected = params._echo_reflection === true;
|
|
3847
|
+
delete params._echo_reflection;
|
|
3848
|
+
for (const key of ['hop', 'max_hop', 'rhop', 'dir'])
|
|
3849
|
+
delete params[key];
|
|
3728
3850
|
const payload = params.payload;
|
|
3729
|
-
if (!this._isEchoPayload(payload))
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3851
|
+
if (!this._isEchoPayload(payload)) {
|
|
3852
|
+
if (payload && typeof payload === 'object' && !Array.isArray(payload)) {
|
|
3853
|
+
const clean = { ...payload };
|
|
3854
|
+
for (const key of ['hop', 'max_hop', 'rhop', 'dir'])
|
|
3855
|
+
delete clean[key];
|
|
3856
|
+
params.payload = clean;
|
|
3857
|
+
}
|
|
3858
|
+
return false;
|
|
3859
|
+
}
|
|
3860
|
+
if (reflected)
|
|
3861
|
+
return false;
|
|
3862
|
+
const started = this._echoStarts.get(params) ?? performance.now();
|
|
3863
|
+
this._echoStarts.set(params, started);
|
|
3864
|
+
const next = this._appendEchoTrace(payload, 'send', started);
|
|
3865
|
+
params.payload = next;
|
|
3866
|
+
return next.dir === 'fwd' && next.hop === 0;
|
|
3734
3867
|
}
|
|
3735
3868
|
_shouldSkipEventSignature(event) {
|
|
3736
3869
|
if (event.encrypted || event.encrypt)
|
|
@@ -3738,14 +3871,79 @@ export class AUNClient {
|
|
|
3738
3871
|
return this._isEchoPayload(event.payload);
|
|
3739
3872
|
}
|
|
3740
3873
|
_maybeAppendEchoTraceReceive(msg) {
|
|
3741
|
-
if (msg.encrypted)
|
|
3742
|
-
return;
|
|
3874
|
+
if (msg.encrypted || msg.encrypt)
|
|
3875
|
+
return false;
|
|
3876
|
+
const payload = msg.payload;
|
|
3877
|
+
if (!this._isEchoPayload(payload) || String(payload.type ?? '').startsWith('e2ee.') || payload.ciphertext)
|
|
3878
|
+
return false;
|
|
3879
|
+
const started = this._echoStarts.get(msg) ?? performance.now();
|
|
3880
|
+
this._echoStarts.set(msg, started);
|
|
3881
|
+
const next = this._appendEchoTrace(payload, 'receive', started);
|
|
3882
|
+
msg.payload = next;
|
|
3883
|
+
return next.dir === 'fwd' && next.hop === 0;
|
|
3884
|
+
}
|
|
3885
|
+
async _returnReceivedEcho(msg) {
|
|
3743
3886
|
const payload = msg.payload;
|
|
3744
|
-
|
|
3887
|
+
const sender = String(msg.from ?? msg.sender_aid ?? msg.from_aid ?? '');
|
|
3888
|
+
const returned = this._appendEchoTrace(payload, 'receive', this._echoStarts.get(msg) ?? performance.now(), true);
|
|
3889
|
+
returned.text = String(returned.text) + '\n[AUN-Echo.return]';
|
|
3890
|
+
const groupId = String(msg.group_id ?? '');
|
|
3891
|
+
await this._rpcPipeline.call('message.send', {
|
|
3892
|
+
to: sender, encrypt: false, _echo_reflection: true,
|
|
3893
|
+
...(groupId ? { group_id: groupId } : {}),
|
|
3894
|
+
payload: returned,
|
|
3895
|
+
});
|
|
3896
|
+
}
|
|
3897
|
+
async _echoFailure(method, params, error, traced = false) {
|
|
3898
|
+
if (!['message.send', 'group.send'].includes(method) || !params || !this._isEchoPayload(params.payload) || !(error instanceof AUNError) || error instanceof AuthError || error._echoDeliveryAttempted)
|
|
3899
|
+
return;
|
|
3900
|
+
const payload = params.payload;
|
|
3901
|
+
if (String(payload.type ?? '').toLowerCase().startsWith('e2ee') || payload.ciphertext)
|
|
3902
|
+
return;
|
|
3903
|
+
const started = this._echoStarts.get(params) ?? performance.now();
|
|
3904
|
+
let sent;
|
|
3905
|
+
try {
|
|
3906
|
+
sent = traced ? payload : this._appendEchoTrace(payload, 'send', started);
|
|
3907
|
+
}
|
|
3908
|
+
catch {
|
|
3745
3909
|
return;
|
|
3746
|
-
|
|
3747
|
-
const
|
|
3748
|
-
|
|
3910
|
+
}
|
|
3911
|
+
const reason = error instanceof TimeoutError || error.code === -32011 ? 'timeout' : 'send_failed';
|
|
3912
|
+
const state = error instanceof ValidationError || error instanceof ConnectionError && /not connected|closing|closed/i.test(error.message) ? 'not_delivered' : 'unknown';
|
|
3913
|
+
const detail = { code: error.code, message: error.message, data: error.data };
|
|
3914
|
+
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 };
|
|
3915
|
+
if (method === 'group.send')
|
|
3916
|
+
result.group_id = params.group_id;
|
|
3917
|
+
await this._publishEchoReturn(method, params, result);
|
|
3918
|
+
return result;
|
|
3919
|
+
}
|
|
3920
|
+
async _publishEchoReturn(method, params, result) {
|
|
3921
|
+
if (!this._isEchoPayload(result.payload))
|
|
3922
|
+
return;
|
|
3923
|
+
const payload = result.payload;
|
|
3924
|
+
const returnedPayload = this._appendEchoTrace(payload, 'receive', this._echoStarts.get(params) ?? performance.now(), !this._isEchoReturn(payload));
|
|
3925
|
+
if (result.echo_error)
|
|
3926
|
+
returnedPayload.text += ` reason=${result.reason} delivery_state=${result.delivery_state} error=${JSON.stringify(result.echo_error)}`;
|
|
3927
|
+
if (!String(returnedPayload.text).split('\n').some(line => line.trim() === '[AUN-Echo.return]'))
|
|
3928
|
+
returnedPayload.text += '\n[AUN-Echo.return]';
|
|
3929
|
+
result.payload = returnedPayload;
|
|
3930
|
+
if (this._state === 'closed')
|
|
3931
|
+
return;
|
|
3932
|
+
const groupId = String(result.group_id ?? params.group_id ?? params.group_aid ?? '');
|
|
3933
|
+
const sender = String(result.from ?? result.sender_aid ?? params.to ?? this._aid ?? '');
|
|
3934
|
+
try {
|
|
3935
|
+
await this._publishAppEvent(method === 'group.send' ? 'group.message_created' : 'message.received', {
|
|
3936
|
+
...result,
|
|
3937
|
+
message_id: String(result.message_id ?? params.message_id ?? ''),
|
|
3938
|
+
from: sender, sender_aid: sender, to: this._aid ?? '', encrypted: false,
|
|
3939
|
+
...(groupId ? { group_id: groupId } : {}), payload: returnedPayload,
|
|
3940
|
+
}, 'echo_return');
|
|
3941
|
+
}
|
|
3942
|
+
catch (error) {
|
|
3943
|
+
if (error instanceof Error)
|
|
3944
|
+
error._echoDeliveryAttempted = true;
|
|
3945
|
+
throw error;
|
|
3946
|
+
}
|
|
3749
3947
|
}
|
|
3750
3948
|
_debugJson(value) {
|
|
3751
3949
|
const seen = new WeakSet();
|
|
@@ -4637,6 +4835,10 @@ export class AUNClient {
|
|
|
4637
4835
|
: {};
|
|
4638
4836
|
const capabilities = isJsonObject(extraInfo._capabilities) ? { ...extraInfo._capabilities } : {};
|
|
4639
4837
|
delete capabilities.message_classes_v1;
|
|
4838
|
+
if (params.udp_standby !== false && params.connection_kind !== 'short')
|
|
4839
|
+
capabilities.udp_standby_v1 = true;
|
|
4840
|
+
else
|
|
4841
|
+
delete capabilities.udp_standby_v1;
|
|
4640
4842
|
if (typeof this._tokenStore.updateInstanceState === 'function') {
|
|
4641
4843
|
capabilities.message_classes_v2 = { p2p: ['normal', 'control', 'sdk_control'], group: ['normal', 'control'] };
|
|
4642
4844
|
capabilities.sdk_control_protocols = { 'aun.agent_md.changed': [1], 'aun.pubsub': [1] };
|
|
@@ -4659,12 +4861,21 @@ export class AUNClient {
|
|
|
4659
4861
|
const challenge = await this._transport.connect(gatewayUrl);
|
|
4660
4862
|
this._assertReconnectOwner(reconnectOwner);
|
|
4661
4863
|
this._lifecycle.assertConnectionAttemptOwner(connectionOwner);
|
|
4662
|
-
const authTransport =
|
|
4864
|
+
const authTransport = {
|
|
4663
4865
|
call: (...args) => {
|
|
4664
4866
|
this._assertReconnectOwner(reconnectOwner);
|
|
4867
|
+
this._lifecycle.assertConnectionAttemptOwner(connectionOwner);
|
|
4665
4868
|
return this._transport.call(...args);
|
|
4666
4869
|
},
|
|
4667
|
-
|
|
4870
|
+
connect: async (url) => {
|
|
4871
|
+
this._assertReconnectOwner(reconnectOwner);
|
|
4872
|
+
this._lifecycle.assertConnectionAttemptOwner(connectionOwner);
|
|
4873
|
+
const nextChallenge = await this._transport.connect(url);
|
|
4874
|
+
this._assertReconnectOwner(reconnectOwner);
|
|
4875
|
+
this._lifecycle.assertConnectionAttemptOwner(connectionOwner);
|
|
4876
|
+
return nextChallenge;
|
|
4877
|
+
},
|
|
4878
|
+
};
|
|
4668
4879
|
this._clientLog.debug(`WebSocket connection established: gateway=${gatewayUrl}`);
|
|
4669
4880
|
this._state = 'authenticating';
|
|
4670
4881
|
if (allowReauth) {
|
|
@@ -4697,6 +4908,7 @@ export class AUNClient {
|
|
|
4697
4908
|
this._bindGatewayRuntimeIdentity(auth.hello, params);
|
|
4698
4909
|
this._gatewayConnectionId = isJsonObject(auth.hello) && isJsonObject(auth.hello.connection) ? String(auth.hello.connection.id ?? '') : '';
|
|
4699
4910
|
this._negotiatedCapabilities = isJsonObject(auth.hello) && isJsonObject(auth.hello.negotiated_capabilities) ? auth.hello.negotiated_capabilities : {};
|
|
4911
|
+
this._udpStandbyPolicy = isJsonObject(auth.hello) && isJsonObject(auth.hello.udp_standby) ? auth.hello.udp_standby : undefined;
|
|
4700
4912
|
if (isJsonObject(auth.hello))
|
|
4701
4913
|
await this._applyRiskHello(auth.hello);
|
|
4702
4914
|
}
|
|
@@ -4719,6 +4931,7 @@ export class AUNClient {
|
|
|
4719
4931
|
this._bindGatewayRuntimeIdentity(hello, params);
|
|
4720
4932
|
this._gatewayConnectionId = isJsonObject(hello) && isJsonObject(hello.connection) ? String(hello.connection.id ?? '') : '';
|
|
4721
4933
|
this._negotiatedCapabilities = isJsonObject(hello) && isJsonObject(hello.negotiated_capabilities) ? hello.negotiated_capabilities : {};
|
|
4934
|
+
this._udpStandbyPolicy = isJsonObject(hello) && isJsonObject(hello.udp_standby) ? hello.udp_standby : undefined;
|
|
4722
4935
|
if (isJsonObject(hello))
|
|
4723
4936
|
await this._applyRiskHello(hello);
|
|
4724
4937
|
}
|
|
@@ -4789,19 +5002,16 @@ export class AUNClient {
|
|
|
4789
5002
|
this._agentMdChanges = new AgentMdChangeCoordinator(this);
|
|
4790
5003
|
// connect/reconnect 成功后自动触发一次 P2P message.v2.pull,补齐离线期间积压
|
|
4791
5004
|
// 群消息按惰性触发,不在此处主动 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
5005
|
this._entityOwnershipConnection++;
|
|
4798
5006
|
this._entityOwnershipChecks.clear();
|
|
4799
5007
|
if (!this._ownershipOperation)
|
|
4800
5008
|
this._safeAsync(this._checkIdentityAdmissionAfterConnect());
|
|
4801
|
-
|
|
5009
|
+
if (backgroundSyncEnabled)
|
|
5010
|
+
this._schedulePostConnectRecovery();
|
|
4802
5011
|
if (!isShortConnection && backgroundSyncEnabled && this._v2Session) {
|
|
4803
5012
|
this._safeAsync(this._v2AutoConfirmPendingProposals());
|
|
4804
5013
|
}
|
|
5014
|
+
this._activateUdpStandby();
|
|
4805
5015
|
this._clientLog.debug(`_connectOnce exit: elapsed=${Date.now() - tStart}ms gateway=${gatewayUrl}, aid=${this._aid ?? ''}`);
|
|
4806
5016
|
}
|
|
4807
5017
|
catch (err) {
|
|
@@ -5724,6 +5934,11 @@ export class AUNClient {
|
|
|
5724
5934
|
}
|
|
5725
5935
|
/** 停止所有后台任务 */
|
|
5726
5936
|
_stopBackgroundTasks() {
|
|
5937
|
+
this._postConnectRecoveryAbort?.abort();
|
|
5938
|
+
this._postConnectRecoveryAbort = null;
|
|
5939
|
+
if (this._postConnectRecoveryTimer !== null)
|
|
5940
|
+
clearTimeout(this._postConnectRecoveryTimer);
|
|
5941
|
+
this._postConnectRecoveryTimer = null;
|
|
5727
5942
|
this._syncChannelCoordinator?.reset();
|
|
5728
5943
|
this._stopCredentialRenewal();
|
|
5729
5944
|
this._auth.invalidateStrictPkiCache();
|
|
@@ -6118,6 +6333,8 @@ export class AUNClient {
|
|
|
6118
6333
|
}
|
|
6119
6334
|
/** 传输层断线回调 */
|
|
6120
6335
|
async _handleTransportDisconnect(error, closeCode, initialOptions) {
|
|
6336
|
+
if (this._udpStandby?.handlesDisconnect())
|
|
6337
|
+
return;
|
|
6121
6338
|
if (this._closing || this._disconnecting || this.state === ConnectionState.CLOSED)
|
|
6122
6339
|
return;
|
|
6123
6340
|
// 已在重连中则跳过,避免心跳超时和 transport 断线回调重复触发
|
|
@@ -6198,6 +6415,50 @@ export class AUNClient {
|
|
|
6198
6415
|
}
|
|
6199
6416
|
this._startReconnect(serverInitiated, initialOptions, reconnectAbort);
|
|
6200
6417
|
}
|
|
6418
|
+
_activateUdpStandby() {
|
|
6419
|
+
if (this._negotiatedCapabilities.udp_standby_v1 !== true || this._udpStandbyPolicy?.enabled !== true) {
|
|
6420
|
+
this._udpStandby?.deactivate();
|
|
6421
|
+
this._transport.setStandbyGate(null);
|
|
6422
|
+
return;
|
|
6423
|
+
}
|
|
6424
|
+
this._udpStandby ??= new UdpStandbyController({
|
|
6425
|
+
call: (method, params, timeout) => this._transport.call(method, params, timeout),
|
|
6426
|
+
canPrepare: async (timeout) => {
|
|
6427
|
+
if (!this._aid)
|
|
6428
|
+
return true;
|
|
6429
|
+
try {
|
|
6430
|
+
if (!this._aidStore) {
|
|
6431
|
+
const record = this._agentMdManager?._loadRecord?.(this._aid, true) ?? this._agentMdManager?.loadRecord(this._aid);
|
|
6432
|
+
const content = String(record?.local_draft_content ?? '') || String(record?.content ?? '');
|
|
6433
|
+
return !content || validateAgentMdReadDocument(content).frontmatter.type !== 'human';
|
|
6434
|
+
}
|
|
6435
|
+
const result = await this._aidStore.resolve(this._aid, { timeout });
|
|
6436
|
+
return !result.ok || result.data?.agent_md?.frontmatter.type !== 'human';
|
|
6437
|
+
}
|
|
6438
|
+
catch {
|
|
6439
|
+
return true;
|
|
6440
|
+
}
|
|
6441
|
+
},
|
|
6442
|
+
canStandby: () => this._state === 'ready' && !this._closing && !this._disconnecting && !this._reconnectActive
|
|
6443
|
+
&& this._sessionOptions?.background_sync !== false
|
|
6444
|
+
&& this._groupIdentityOperations.size === 0
|
|
6445
|
+
&& this._transport.isConnected() && this._transport.rpcBudget.counts.total === 0
|
|
6446
|
+
&& this._transport.foregroundWaiting === 0 && this._transport.rpcBudget.backgroundWaiting === 0
|
|
6447
|
+
&& this._dispatcher.isIdle && !this._delivery?.hasAnyPendingPull()
|
|
6448
|
+
&& (this._syncChannelCoordinator?.isIdle ?? true),
|
|
6449
|
+
prepareParams: () => ({ connection_id: this._gatewayConnectionId }),
|
|
6450
|
+
detach: () => this._transport.close(),
|
|
6451
|
+
reconnect: async () => {
|
|
6452
|
+
if (this._closing || this._disconnecting)
|
|
6453
|
+
throw new ConnectionError('client closed');
|
|
6454
|
+
this._state = 'standby';
|
|
6455
|
+
await this._lifecycle.connect(this._udpStandbyConnectOptions);
|
|
6456
|
+
},
|
|
6457
|
+
});
|
|
6458
|
+
this._udpStandby.activate(this._udpStandbyPolicy);
|
|
6459
|
+
const standby = this._udpStandby;
|
|
6460
|
+
this._transport.setStandbyGate((method, deadline, signal) => standby.acquire(method, deadline, signal), () => standby.touch(), params => standby.authenticationParams(params));
|
|
6461
|
+
}
|
|
6201
6462
|
/** 启动重连循环(默认无限重试 + 当前基数抖动,仅在不可重试错误、close() 或 max_attempts 耗尽时终止) */
|
|
6202
6463
|
_startReconnect(serverInitiated = false, initialOptions, existingAbort) {
|
|
6203
6464
|
if (existingAbort) {
|