@agentunion/fastaun 0.5.33 → 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.
Files changed (113) hide show
  1. package/CHANGELOG.md +59 -2
  2. package/README.md +28 -26
  3. package/_packed_docs/CHANGELOG.md +59 -2
  4. package/_packed_docs/INDEX.md +26 -12
  5. package/_packed_docs/KITE_DOCS_GUIDE.md +17 -11
  6. package/_packed_docs/agent.md/SCHEMA.md +1 -1
  7. 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
  8. package/_packed_docs/protocol/03-Gateway-/350/277/236/346/216/245/346/250/241/345/274/217.md +58 -8
  9. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2112.1.md +17 -17
  10. package/_packed_docs/protocol/echo-probe-gateway.md +99 -0
  11. package/_packed_docs/protocol/echo-probe-sdk.md +91 -0
  12. package/_packed_docs/sdk/0.5.33-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +464 -316
  13. package/_packed_docs/sdk/0.5.33-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +499 -304
  14. package/_packed_docs/sdk/0.5.34-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +133 -0
  15. package/_packed_docs/sdk/0.5.34-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +107 -0
  16. 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
  17. 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
  18. package/_packed_docs/sdk/01-/345/277/253/351/200/237/345/274/200/345/247/213.md +16 -13
  19. package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +13 -1
  20. package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +19 -9
  21. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +87 -20
  22. package/_packed_docs/sdk/09-collab-rpc-manual.md +48 -30
  23. package/_packed_docs/sdk/09-custody-api-manual.md +3 -3
  24. package/_packed_docs/sdk/09-group-rpc-manual.md +60 -13
  25. package/_packed_docs/sdk/09-message-rpc-manual.md +22 -0
  26. package/_packed_docs/sdk/09-meta-rpc-manual.md +2 -1
  27. package/_packed_docs/sdk/09-payload-reference.md +9 -8
  28. package/_packed_docs/sdk/09-proxy-rpc-manual.md +20 -14
  29. package/_packed_docs/sdk/09-storage-rpc-manual.md +1698 -1659
  30. package/_packed_docs/sdk/09-stream-rpc-manual.md +33 -20
  31. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +19 -17
  32. 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 +125 -30
  33. 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 +109 -33
  34. package/_packed_docs/sdk/INDEX.md +30 -18
  35. package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/2272.1.md +1 -1
  36. 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
  37. 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
  38. package/_packed_docs/sdk/Ownership2.1/350/257/246/347/273/206/350/256/276/350/256/241.md +9 -9
  39. package/_packed_docs/sdk/README.md +11 -6
  40. 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
  41. 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
  42. package/dist/auth.js +39 -45
  43. package/dist/auth.js.map +1 -1
  44. package/dist/client/control-messages.js +2 -2
  45. package/dist/client/control-messages.js.map +1 -1
  46. package/dist/client/delivery.d.ts +12 -1
  47. package/dist/client/delivery.js +281 -46
  48. package/dist/client/delivery.js.map +1 -1
  49. package/dist/client/lifecycle.js +13 -1
  50. package/dist/client/lifecycle.js.map +1 -1
  51. package/dist/client/rpc-budget.d.ts +21 -0
  52. package/dist/client/rpc-budget.js +82 -0
  53. package/dist/client/rpc-budget.js.map +1 -0
  54. package/dist/client/rpc-pipeline.d.ts +5 -0
  55. package/dist/client/rpc-pipeline.js +227 -63
  56. package/dist/client/rpc-pipeline.js.map +1 -1
  57. package/dist/client/sync-channels.d.ts +90 -0
  58. package/dist/client/sync-channels.js +508 -0
  59. package/dist/client/sync-channels.js.map +1 -0
  60. package/dist/client/v2-e2ee.d.ts +5 -1
  61. package/dist/client/v2-e2ee.js +420 -303
  62. package/dist/client/v2-e2ee.js.map +1 -1
  63. package/dist/client.d.ts +17 -0
  64. package/dist/client.js +373 -50
  65. package/dist/client.js.map +1 -1
  66. package/dist/events.d.ts +1 -0
  67. package/dist/events.js +5 -0
  68. package/dist/events.js.map +1 -1
  69. package/dist/index.d.ts +3 -1
  70. package/dist/index.js +1 -0
  71. package/dist/index.js.map +1 -1
  72. package/dist/mail-client.d.ts +101 -0
  73. package/dist/mail-client.js +369 -0
  74. package/dist/mail-client.js.map +1 -0
  75. package/dist/pubsub/binding.js +23 -3
  76. package/dist/pubsub/binding.js.map +1 -1
  77. package/dist/pubsub/channel.d.ts +15 -3
  78. package/dist/pubsub/channel.js +114 -7
  79. package/dist/pubsub/channel.js.map +1 -1
  80. package/dist/pubsub/client.d.ts +17 -2
  81. package/dist/pubsub/client.js +205 -40
  82. package/dist/pubsub/client.js.map +1 -1
  83. package/dist/pubsub/credentials.d.ts +3 -0
  84. package/dist/pubsub/credentials.js +57 -20
  85. package/dist/pubsub/credentials.js.map +1 -1
  86. package/dist/pubsub/index.d.ts +5 -2
  87. package/dist/pubsub/index.js +70 -54
  88. package/dist/pubsub/index.js.map +1 -1
  89. package/dist/pubsub/service.d.ts +9 -0
  90. package/dist/pubsub/service.js +266 -20
  91. package/dist/pubsub/service.js.map +1 -1
  92. package/dist/pubsub/types.d.ts +13 -1
  93. package/dist/storage/lowlevel.d.ts +3 -1
  94. package/dist/storage/lowlevel.js +13 -0
  95. package/dist/storage/lowlevel.js.map +1 -1
  96. package/dist/storage/types.d.ts +10 -0
  97. package/dist/storage/types.js.map +1 -1
  98. package/dist/storage/vfs.d.ts +3 -1
  99. package/dist/storage/vfs.js +2 -2
  100. package/dist/storage/vfs.js.map +1 -1
  101. package/dist/tools/cross-sdk-agent.d.ts +55 -5
  102. package/dist/tools/cross-sdk-agent.js +88 -21
  103. package/dist/tools/cross-sdk-agent.js.map +1 -1
  104. package/dist/transport.d.ts +16 -0
  105. package/dist/transport.js +129 -7
  106. package/dist/transport.js.map +1 -1
  107. package/dist/udp-standby.d.ts +89 -0
  108. package/dist/udp-standby.js +487 -0
  109. package/dist/udp-standby.js.map +1 -0
  110. package/dist/version.d.ts +1 -1
  111. package/dist/version.js +1 -1
  112. package/package.json +5 -1
  113. package/scripts/patch-imapflow-external.mjs +69 -0
package/dist/client.js CHANGED
@@ -26,6 +26,8 @@ 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';
30
+ import { SyncChannels } from './client/sync-channels.js';
29
31
  import { AuthFlow } from './auth.js';
30
32
  import { AgentMdManager, buildDefaultAgentMd, updateOwnershipAgentMd } from './agent-md.js';
31
33
  import { AgentMdChangeCoordinator } from './agent-md-changes.js';
@@ -908,6 +910,9 @@ export class AUNClient {
908
910
  _discovery;
909
911
  /** 传输层 */
910
912
  _transport;
913
+ _udpStandby = null;
914
+ _udpStandbyPolicy;
915
+ _udpStandbyConnectOptions;
911
916
  /** 认证流程 */
912
917
  _auth;
913
918
  resolveCredentialService;
@@ -931,6 +936,8 @@ export class AUNClient {
931
936
  _groupIdentityOperations = new Map();
932
937
  _entityOwnershipConnection = 0;
933
938
  _entityOwnershipChecks = new Map();
939
+ _postConnectRecoveryTimer = null;
940
+ _postConnectRecoveryAbort = null;
934
941
  _groupIndexMetaCache = new GroupIndexMetaCache();
935
942
  /** 消息序列号跟踪器(群消息 + P2P 空洞检测) */
936
943
  _p2pPeerPullEnabled = true;
@@ -1050,6 +1057,7 @@ export class AUNClient {
1050
1057
  _peerDirectory;
1051
1058
  _lifecycle;
1052
1059
  _rpcPipeline;
1060
+ _syncChannelCoordinator = null;
1053
1061
  _delivery;
1054
1062
  _v2E2EE;
1055
1063
  _groupState;
@@ -1161,6 +1169,7 @@ export class AUNClient {
1161
1169
  this._installRiskHooks();
1162
1170
  this._transport.setClassMessageHandler((event, payload) => this._controlMessages().onPush(payload, event.includes('group.')));
1163
1171
  this._transport.setProtocolMetaObserver((meta) => this._observeRpcMeta(meta));
1172
+ this._transport.setSyncControlObserver((meta, method, params) => this._historyChannels().observe(meta, method, params));
1164
1173
  this._transport.setServiceEventVerifier((event, payload) => this._verifyIncomingServiceEvent(event, payload), () => this._identity?.aid ?? '', () => this._serviceEventIdentityVersion);
1165
1174
  this._transport.setProtocolDisconnectCallback((err, closeCode) => this._handleTransportDisconnect(err, closeCode));
1166
1175
  this._transport._onForegroundDrained = () => this._delivery.onForegroundPullsDrained();
@@ -1351,6 +1360,11 @@ export class AUNClient {
1351
1360
  const client = new AUNClient(AID._create({ aid: aid.aid, aunPath: aid.aunPath, certPem: aid.certPem, privateKeyPem: aid.privateKeyPem,
1352
1361
  certValid: aid.isCertValid(), privateKeyValid: aid.isPrivateKeyValid(), deviceId: this._deviceId, slotId,
1353
1362
  verifySsl: aid.verifySsl, rootCaPath: aid.rootCaPath, debug: aid.debug, aidStore: aid._aidStore }));
1363
+ const identity = this._auth.loadIdentityOrNone(), token = String(identity?.access_token ?? ''), expiresAt = identity?.access_token_expires_at;
1364
+ if (identity?.aid === aid.aid && token.startsWith('eyJ') && token.split('.').length === 3
1365
+ && typeof expiresAt === 'number' && expiresAt > Date.now() / 1000 + 30) {
1366
+ Object.assign(client._identity, { access_token: token, access_token_expires_at: expiresAt });
1367
+ }
1354
1368
  client._ownershipOperation = true;
1355
1369
  client._ownershipType = this.getType();
1356
1370
  return client;
@@ -1590,6 +1604,10 @@ export class AUNClient {
1590
1604
  }
1591
1605
  _applyAidRuntimeContext(aid) {
1592
1606
  this._aidStore = aid._aidStore;
1607
+ this._udpStandby?.close();
1608
+ this._udpStandby = null;
1609
+ this._udpStandbyPolicy = undefined;
1610
+ this._udpStandbyConnectOptions = undefined;
1593
1611
  const oldTransport = this._transport;
1594
1612
  try {
1595
1613
  const closeResult = oldTransport?.close?.();
@@ -1693,6 +1711,7 @@ export class AUNClient {
1693
1711
  this._installRiskHooks();
1694
1712
  this._transport.setClassMessageHandler((event, payload) => this._controlMessages().onPush(payload, event.includes('group.')));
1695
1713
  this._transport.setProtocolMetaObserver((meta) => this._observeRpcMeta(meta));
1714
+ this._transport.setSyncControlObserver((meta, method, params) => this._historyChannels().observe(meta, method, params));
1696
1715
  this._transport.setServiceEventVerifier((event, payload) => this._verifyIncomingServiceEvent(event, payload), () => this._identity?.aid ?? '', () => this._serviceEventIdentityVersion);
1697
1716
  this._transport.setProtocolDisconnectCallback((err, closeCode) => this._handleTransportDisconnect(err, closeCode));
1698
1717
  this._transport._onForegroundDrained = () => this._delivery.onForegroundPullsDrained();
@@ -1748,6 +1767,56 @@ export class AUNClient {
1748
1767
  return this._peerDirectory.peers();
1749
1768
  }
1750
1769
  /** transport 的 meta observer:吸收 gateway 注入的 _meta 字段。失败不影响业务。 */
1770
+ _historyChannels() {
1771
+ return this._syncChannelCoordinator ??= new SyncChannels({
1772
+ main: () => this._transport,
1773
+ current: () => this._state === 'ready' && !this._closing && this._transport.isConnected(),
1774
+ wake: () => this._delivery?.requestBackgroundPullRecovery(),
1775
+ owner: () => String(this._aid ?? ''),
1776
+ namespace: (method, params) => this._rpcPipeline.pullGateKeyForCall(method, params).split('|')[0],
1777
+ pendingNamespaces: () => [...this._delivery.pendingHistoryNamespaces(), ...this._rpcPipeline.pendingHistoryNamespaces()],
1778
+ fail: (method, params, error) => this._dispatcher.enqueue('sync.error', { namespace: params.sender_aid ? this._delivery.peerNamespace(String(params.sender_aid)) : `group:${String(params.group_id ?? params.group_aid ?? '')}`, method, code: 'OVERSIZED_MESSAGE', error: error instanceof Error ? error.message : String(error) }),
1779
+ create: async (parent, timeout = 35000, signal) => {
1780
+ const deadline = Date.now() + timeout;
1781
+ const dispatcher = new EventDispatcher(this._logger.for('aun_core.events'));
1782
+ const transport = new RPCTransport({ eventDispatcher: dispatcher, rpcBudget: this._transport.rpcBudget, syncAuxiliary: true,
1783
+ timeout, verifySsl: this._configModel.verifySsl, logger: this._logger.for('aun_core.transport') });
1784
+ transport.setSyncControlObserver((meta, method, params) => this._historyChannels().observe(meta, method, params));
1785
+ transport.setRequestAdmission(this._transport._requestAdmission);
1786
+ transport.setRequestCheck(this._transport._requestCheck);
1787
+ transport.setProtocolDisconnectCallback(() => this._historyChannels().auxiliaryDisconnected(transport));
1788
+ const abort = () => { void transport.close(); };
1789
+ signal?.addEventListener('abort', abort, { once: true });
1790
+ try {
1791
+ if (signal?.aborted)
1792
+ throw new TimeoutError('history sync cancelled', { retryable: true });
1793
+ const challenge = await transport.connect(this._gatewayUrl);
1794
+ if (Date.now() >= deadline || signal?.aborted)
1795
+ throw new TimeoutError('history sync deadline exceeded', { retryable: true });
1796
+ transport.setTimeout(Math.max(1, deadline - Date.now()));
1797
+ const token = AuthFlow._getCachedAccessToken(this._identity ?? {})
1798
+ || AuthFlow._getCachedAccessToken(this._sessionParams ?? {});
1799
+ if (!token)
1800
+ throw new ConnectionError('auxiliary sync connection has no valid access token');
1801
+ const hello = await this._auth.initializeWithToken(transport, challenge, token, { deviceId: this._deviceId,
1802
+ slotId: this._slotId, connectionKind: 'short', shortTtlMs: 0, extraInfo: { _sync_parent: parent } });
1803
+ if (!isJsonObject(hello.connection) || !hello.connection.id)
1804
+ throw new ConnectionError('auxiliary sync connection has no identity');
1805
+ if (!isJsonObject(hello.negotiated_capabilities) || hello.negotiated_capabilities.elastic_connections_v1 !== true || hello.negotiated_capabilities.sync_channels_v1 !== true)
1806
+ throw new ConnectionError('auxiliary sync capabilities were not negotiated');
1807
+ return { id: String(hello.connection.id), transport, dispose: () => dispatcher.close() };
1808
+ }
1809
+ catch (error) {
1810
+ await transport.close();
1811
+ await dispatcher.close();
1812
+ throw error;
1813
+ }
1814
+ finally {
1815
+ signal?.removeEventListener('abort', abort);
1816
+ }
1817
+ },
1818
+ });
1819
+ }
1751
1820
  _observeRpcMeta(meta) {
1752
1821
  this._agentMdManager.observeRpcMeta(meta, this._aid);
1753
1822
  this._groupIndexMetaCache.observeRpcMeta(meta, { localAid: this._aid ?? '' });
@@ -1791,10 +1860,14 @@ export class AUNClient {
1791
1860
  }
1792
1861
  /** 连接到 Gateway;身份来自构造函数或 loadIdentity(aid),认证由 SDK 内部自动完成。 */
1793
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;
1794
1866
  await this._lifecycle.connect(opts);
1795
1867
  }
1796
1868
  /** 关闭连接 */
1797
1869
  async close() {
1870
+ this._udpStandby?.close();
1798
1871
  this._risk.close();
1799
1872
  this._peerGatewayCache.clear();
1800
1873
  return this._lifecycle.close();
@@ -1804,6 +1877,7 @@ export class AUNClient {
1804
1877
  * disconnect 是可恢复的:停止心跳、关闭 WebSocket,但不清理 keystore 等状态。
1805
1878
  */
1806
1879
  async disconnect() {
1880
+ this._udpStandby?.close();
1807
1881
  return this._lifecycle.disconnect();
1808
1882
  }
1809
1883
  get riskState() { return Object.freeze(structuredClone(this._risk.state)); }
@@ -1961,27 +2035,47 @@ export class AUNClient {
1961
2035
  * 自动处理内部方法限制、E2EE 加解密、客户端签名等。
1962
2036
  */
1963
2037
  async call(method, params, options) {
1964
- const assertCurrent = ['group.transfer_owner', 'org.lifecycle.transfer', 'group.dissolve', 'org.lifecycle.dissolve'].includes(method) ? this._entityOwnershipGuard() : undefined;
1965
- const recoverOwnership = ['group.transfer_owner', 'org.lifecycle.transfer', 'group.dissolve', 'org.lifecycle.dissolve'].includes(method) ? await this._prepareEntityOwnershipLifecycle(method, params ?? {}) : undefined;
1966
- await this._checkEntityOwnershipOnAccess(method, params ?? {});
1967
- const readIdentity = ['message.read', 'message.read_state', 'group.read', 'group.read_state'].includes(method)
1968
- ? this._ensureReadStateIdentity() : null;
1969
- try {
1970
- const result = await (options === undefined ? this._rpcPipeline.call(method, params, assertCurrent) : this._rpcPipeline.call(method, params, assertCurrent, options));
1971
- await this._safeAsync(this._getCredentialRenewal().observe(method, params ?? {}, result));
1972
- if (readIdentity !== null && readIdentity === this._ensureReadStateIdentity())
1973
- this._observeReadStateResult(method, result, params ?? {});
1974
- return result;
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 };
1975
2049
  }
1976
- catch (err) {
1977
- if (recoverOwnership) {
1978
- try {
1979
- await recoverOwnership(err);
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 { }
1980
2069
  }
1981
- catch { }
2070
+ await this._maybeHandleHalfOpenRpcFailure(err);
2071
+ const returned = await this._echoFailure(method, params, err);
2072
+ if (returned)
2073
+ return returned;
2074
+ throw err;
1982
2075
  }
1983
- await this._maybeHandleHalfOpenRpcFailure(err);
1984
- throw err;
2076
+ }
2077
+ finally {
2078
+ standbyRelease?.();
1985
2079
  }
1986
2080
  }
1987
2081
  _entityOwnershipGuard() {
@@ -2081,7 +2175,7 @@ export class AUNClient {
2081
2175
  return await existing;
2082
2176
  this._entityOwnershipChecks.set(`${identity}:${aid}`, task);
2083
2177
  }
2084
- const consensus = await this.checkOwnershipConsensus(aid);
2178
+ const consensus = await this.checkOwnershipConsensus(aid, true);
2085
2179
  if (consensus.status === 'consensus' && consensus.owner_aid === this._aid)
2086
2180
  return true;
2087
2181
  await this._completeEntityOwnership(aid, type, undefined, () => {
@@ -2209,9 +2303,27 @@ export class AUNClient {
2209
2303
  const groupId = String(params.group_aid ?? params.groupAid ?? params.group_id ?? params.groupId ?? message.slice(prefix.length)).trim();
2210
2304
  const target = normalizeGroupId(groupId.toLowerCase(), { localIssuer: this._aid?.split('.').slice(1).join('.') });
2211
2305
  const key = `${this._aid}:${this._serviceEventIdentityVersion}:${this._entityOwnershipConnection}:${target}`;
2212
- if (!this._entityOwnershipChecks.has(key))
2213
- await this._checkEntityOwnershipOnAccess('group.get_info', { group_id: groupId });
2214
- return await this._entityOwnershipChecks.get(key) ?? false;
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
+ }
2215
2327
  }
2216
2328
  }
2217
2329
  const agentPrefix = 'valid agent.md is required for ';
@@ -2260,17 +2372,14 @@ export class AUNClient {
2260
2372
  catch (exc) {
2261
2373
  this._clientLog.warn(`post-connect agent.md check failed: ${formatCaughtError(exc)}`);
2262
2374
  }
2263
- const store = this._aidStore;
2264
- if (!store)
2265
- return;
2266
- await this._resumeOrgIdentityAfterConnect(store);
2267
2375
  }
2268
2376
  async _runGroupIdentityOperation(groupId, operation, context = '') {
2269
2377
  const aid = String(this._aid ?? '').trim();
2270
2378
  const dot = aid.indexOf('.');
2271
- const key = normalizeGroupId(groupId, { localIssuer: dot >= 0 ? aid.slice(dot + 1) : '' }).toLowerCase();
2272
- if (!key)
2379
+ const target = normalizeGroupId(groupId, { localIssuer: dot >= 0 ? aid.slice(dot + 1) : '' }).toLowerCase();
2380
+ if (!target)
2273
2381
  return operation();
2382
+ const key = `${this._aid}:${this._serviceEventIdentityVersion}:${this._entityOwnershipConnection}:${target}`;
2274
2383
  const pending = this._groupIdentityOperations.get(key);
2275
2384
  if (pending && pending.context !== context)
2276
2385
  throw ownershipFailure('conflict', 'pending Org creation context changed');
@@ -2354,12 +2463,42 @@ export class AUNClient {
2354
2463
  tokens.saveMetadata(orgAid, { org_identity_creation: null });
2355
2464
  return checkpoint.result;
2356
2465
  }
2357
- async _resumeOrgIdentityAfterConnect(store) {
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;
2358
2494
  const keyStore = store._keystore;
2359
2495
  const tokens = store._tokenStore;
2360
2496
  if (typeof keyStore?.listIdentities !== 'function' || typeof tokens?.loadMetadata !== 'function')
2361
2497
  return;
2362
2498
  for (const aid of keyStore.listIdentities()) {
2499
+ await new Promise((resolve) => setTimeout(resolve, 0));
2500
+ if (!current())
2501
+ return;
2363
2502
  const metadata = typeof tokens._loadMetadataStrict === 'function' ? tokens._loadMetadataStrict(aid) : tokens.loadMetadata(aid);
2364
2503
  const creation = metadata?.org_identity_creation;
2365
2504
  const transfer = metadata?.org_identity_transfer;
@@ -3534,6 +3673,8 @@ export class AUNClient {
3534
3673
  // ── 事件自动解密管线 ──────────────────────────────────────
3535
3674
  /** 处理 transport 层推送的原始 P2P 消息 */
3536
3675
  async _onRawMessageReceived(data) {
3676
+ if (isJsonObject(data))
3677
+ this._echoStarts.set(data, performance.now());
3537
3678
  if (this._controlMessages().onPush(data))
3538
3679
  return;
3539
3680
  return this._delivery.onRawMessageReceived(data);
@@ -3543,6 +3684,8 @@ export class AUNClient {
3543
3684
  }
3544
3685
  /** 处理群组消息推送:自动解密后 re-publish */
3545
3686
  async _onRawGroupMessageCreated(data) {
3687
+ if (isJsonObject(data))
3688
+ this._echoStarts.set(data, performance.now());
3546
3689
  if (this._controlMessages().onPush(data, true))
3547
3690
  return;
3548
3691
  return this._delivery.onRawGroupMessageCreated(data);
@@ -3657,21 +3800,70 @@ export class AUNClient {
3657
3800
  const ms = String(now.getMilliseconds()).padStart(3, '0');
3658
3801
  return `${hh}:${mm}:${ss}.${ms}`;
3659
3802
  }
3803
+ _echoStarts = new WeakMap();
3660
3804
  _isEchoPayload(payload) {
3661
3805
  if (!payload || typeof payload !== 'object' || Array.isArray(payload))
3662
3806
  return false;
3663
3807
  const text = payload.text;
3664
- if (typeof text !== 'string' || text.length > 4096)
3808
+ if (typeof text !== 'string')
3665
3809
  return false;
3666
- return text.split('\n', 1)[0].toLowerCase().includes('echo');
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;
3667
3844
  }
3668
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];
3669
3850
  const payload = params.payload;
3670
- if (!this._isEchoPayload(payload))
3671
- return;
3672
- const uptime = this._connectedAt ? Math.floor((Date.now() - this._connectedAt) / 1000) : 0;
3673
- const trace = `${this._echoTimestamp()} [AUN-SDK.send] aid=${this._aid ?? '-'} conn_uptime=${uptime}s`;
3674
- params.payload = { ...payload, text: payload.text + '\n' + trace };
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;
3675
3867
  }
3676
3868
  _shouldSkipEventSignature(event) {
3677
3869
  if (event.encrypted || event.encrypt)
@@ -3679,14 +3871,79 @@ export class AUNClient {
3679
3871
  return this._isEchoPayload(event.payload);
3680
3872
  }
3681
3873
  _maybeAppendEchoTraceReceive(msg) {
3682
- if (msg.encrypted)
3683
- return;
3874
+ if (msg.encrypted || msg.encrypt)
3875
+ return false;
3684
3876
  const payload = msg.payload;
3685
- if (!this._isEchoPayload(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) {
3886
+ const payload = msg.payload;
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)
3686
3902
  return;
3687
- const uptime = this._connectedAt ? Math.floor((Date.now() - this._connectedAt) / 1000) : 0;
3688
- const trace = `${this._echoTimestamp()} [AUN-SDK.receive] aid=${this._aid ?? '-'} conn_uptime=${uptime}s`;
3689
- msg.payload = { ...payload, text: payload.text + '\n' + trace };
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 {
3909
+ return;
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
+ }
3690
3947
  }
3691
3948
  _debugJson(value) {
3692
3949
  const seen = new WeakSet();
@@ -4560,6 +4817,7 @@ export class AUNClient {
4560
4817
  // ── 内部:连接 ────────────────────────────────────────────
4561
4818
  /** 执行一次连接流程 */
4562
4819
  async _connectOnce(params, allowReauth, reconnectOwner, connectionOwner) {
4820
+ this._syncChannelCoordinator?.reset();
4563
4821
  await this._restoreRiskState();
4564
4822
  this._assertRiskReconnect();
4565
4823
  this._riskNegotiated = false;
@@ -4577,6 +4835,10 @@ export class AUNClient {
4577
4835
  : {};
4578
4836
  const capabilities = isJsonObject(extraInfo._capabilities) ? { ...extraInfo._capabilities } : {};
4579
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;
4580
4842
  if (typeof this._tokenStore.updateInstanceState === 'function') {
4581
4843
  capabilities.message_classes_v2 = { p2p: ['normal', 'control', 'sdk_control'], group: ['normal', 'control'] };
4582
4844
  capabilities.sdk_control_protocols = { 'aun.agent_md.changed': [1], 'aun.pubsub': [1] };
@@ -4599,12 +4861,21 @@ export class AUNClient {
4599
4861
  const challenge = await this._transport.connect(gatewayUrl);
4600
4862
  this._assertReconnectOwner(reconnectOwner);
4601
4863
  this._lifecycle.assertConnectionAttemptOwner(connectionOwner);
4602
- const authTransport = reconnectOwner ? {
4864
+ const authTransport = {
4603
4865
  call: (...args) => {
4604
4866
  this._assertReconnectOwner(reconnectOwner);
4867
+ this._lifecycle.assertConnectionAttemptOwner(connectionOwner);
4605
4868
  return this._transport.call(...args);
4606
4869
  },
4607
- } : this._transport;
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
+ };
4608
4879
  this._clientLog.debug(`WebSocket connection established: gateway=${gatewayUrl}`);
4609
4880
  this._state = 'authenticating';
4610
4881
  if (allowReauth) {
@@ -4637,6 +4908,7 @@ export class AUNClient {
4637
4908
  this._bindGatewayRuntimeIdentity(auth.hello, params);
4638
4909
  this._gatewayConnectionId = isJsonObject(auth.hello) && isJsonObject(auth.hello.connection) ? String(auth.hello.connection.id ?? '') : '';
4639
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;
4640
4912
  if (isJsonObject(auth.hello))
4641
4913
  await this._applyRiskHello(auth.hello);
4642
4914
  }
@@ -4659,6 +4931,7 @@ export class AUNClient {
4659
4931
  this._bindGatewayRuntimeIdentity(hello, params);
4660
4932
  this._gatewayConnectionId = isJsonObject(hello) && isJsonObject(hello.connection) ? String(hello.connection.id ?? '') : '';
4661
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;
4662
4935
  if (isJsonObject(hello))
4663
4936
  await this._applyRiskHello(hello);
4664
4937
  }
@@ -4671,6 +4944,7 @@ export class AUNClient {
4671
4944
  this._migrationPending = false;
4672
4945
  this._lastDisconnectInfo = null;
4673
4946
  this._state = 'ready';
4947
+ this._historyChannels().bind(this._gatewayConnectionId, this._negotiatedCapabilities.elastic_connections_v1 === true && this._negotiatedCapabilities.sync_channels_v1 === true && params.connection_kind !== 'short');
4674
4948
  this._connectedAt = Date.now();
4675
4949
  if (Array.isArray(params.gateways)) {
4676
4950
  this._gatewayCandidates = [...new Set(params.gateways
@@ -4728,19 +5002,16 @@ export class AUNClient {
4728
5002
  this._agentMdChanges = new AgentMdChangeCoordinator(this);
4729
5003
  // connect/reconnect 成功后自动触发一次 P2P message.v2.pull,补齐离线期间积压
4730
5004
  // 群消息按惰性触发,不在此处主动 pull
4731
- if (backgroundSyncEnabled) {
4732
- void this._fillP2pGap().catch((exc) => {
4733
- this._clientLog.warn(`schedule post-connect P2P gap fill failed: ${formatCaughtError(exc)}`);
4734
- });
4735
- }
4736
5005
  this._entityOwnershipConnection++;
4737
5006
  this._entityOwnershipChecks.clear();
4738
5007
  if (!this._ownershipOperation)
4739
5008
  this._safeAsync(this._checkIdentityAdmissionAfterConnect());
4740
- this._safeAsync(this._delivery.restoreMessageClassPulls());
5009
+ if (backgroundSyncEnabled)
5010
+ this._schedulePostConnectRecovery();
4741
5011
  if (!isShortConnection && backgroundSyncEnabled && this._v2Session) {
4742
5012
  this._safeAsync(this._v2AutoConfirmPendingProposals());
4743
5013
  }
5014
+ this._activateUdpStandby();
4744
5015
  this._clientLog.debug(`_connectOnce exit: elapsed=${Date.now() - tStart}ms gateway=${gatewayUrl}, aid=${this._aid ?? ''}`);
4745
5016
  }
4746
5017
  catch (err) {
@@ -5663,6 +5934,12 @@ export class AUNClient {
5663
5934
  }
5664
5935
  /** 停止所有后台任务 */
5665
5936
  _stopBackgroundTasks() {
5937
+ this._postConnectRecoveryAbort?.abort();
5938
+ this._postConnectRecoveryAbort = null;
5939
+ if (this._postConnectRecoveryTimer !== null)
5940
+ clearTimeout(this._postConnectRecoveryTimer);
5941
+ this._postConnectRecoveryTimer = null;
5942
+ this._syncChannelCoordinator?.reset();
5666
5943
  this._stopCredentialRenewal();
5667
5944
  this._auth.invalidateStrictPkiCache();
5668
5945
  this._certCache.clear();
@@ -6056,6 +6333,8 @@ export class AUNClient {
6056
6333
  }
6057
6334
  /** 传输层断线回调 */
6058
6335
  async _handleTransportDisconnect(error, closeCode, initialOptions) {
6336
+ if (this._udpStandby?.handlesDisconnect())
6337
+ return;
6059
6338
  if (this._closing || this._disconnecting || this.state === ConnectionState.CLOSED)
6060
6339
  return;
6061
6340
  // 已在重连中则跳过,避免心跳超时和 transport 断线回调重复触发
@@ -6136,6 +6415,50 @@ export class AUNClient {
6136
6415
  }
6137
6416
  this._startReconnect(serverInitiated, initialOptions, reconnectAbort);
6138
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
+ }
6139
6462
  /** 启动重连循环(默认无限重试 + 当前基数抖动,仅在不可重试错误、close() 或 max_attempts 耗尽时终止) */
6140
6463
  _startReconnect(serverInitiated = false, initialOptions, existingAbort) {
6141
6464
  if (existingAbort) {