@agentunion/fastaun 0.5.6 → 0.5.9

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 (92) hide show
  1. package/CHANGELOG.md +106 -0
  2. package/_packed_docs/CHANGELOG.md +106 -0
  3. package/_packed_docs/INDEX.md +12 -11
  4. package/_packed_docs/KITE_DOCS_GUIDE.md +4 -3
  5. package/_packed_docs/agent.md/SCHEMA.md +83 -58
  6. package/_packed_docs/agent.md/examples/codeagent-claudecode.md +1 -1
  7. package/_packed_docs/agent.md/examples/openclaw-lobster.md +1 -1
  8. package/_packed_docs/agent.md/examples/signed-openclaw-lobster.md +1 -1
  9. package/_packed_docs/agent.md/examples//347/276/244/347/273/204-/345/274/200/345/217/221/345/233/242/351/230/237.md +21 -0
  10. package/_packed_docs/audit/AUN/346/234/215/345/212/241Go/345/214/226/351/207/215/346/236/204/347/262/276/347/273/206/345/214/226/346/272/220/347/240/201/345/256/241/346/237/245-20260718.md +3 -3
  11. package/_packed_docs/aun/345/205/254/347/275/221/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +7 -0
  12. package/_packed_docs/aun/345/210/206/345/270/203/345/274/217/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +422 -331
  13. package/_packed_docs/aun/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +598 -398
  14. package/_packed_docs/group-message-rpc-alignment-gaps.md +741 -0
  15. package/_packed_docs/message-online-push-alignment.md +572 -0
  16. package/_packed_docs/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +1 -1
  17. package/_packed_docs/sdk/02-WebSocket/345/215/217/350/256/256.md +10 -10
  18. package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +2 -2
  19. package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +30 -10
  20. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +23 -20
  21. package/_packed_docs/sdk/07-/351/224/231/350/257/257/345/244/204/347/220/206.md +3 -3
  22. package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +5 -6
  23. package/_packed_docs/sdk/09-group-rpc-manual.md +11 -7
  24. package/_packed_docs/sdk/09-message-rpc-manual.md +5 -3
  25. package/_packed_docs/sdk/09-storage-rpc-manual.md +14 -3
  26. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +19 -17
  27. package/_packed_docs/sdk/CHANGELOG-0.5.6.md +73 -0
  28. package/_packed_docs/sdk/INDEX.md +22 -22
  29. package/_packed_docs/sdk/README.md +6 -5
  30. package/dist/agent-md-schema.d.ts +16 -0
  31. package/dist/agent-md-schema.js +109 -0
  32. package/dist/agent-md-schema.js.map +1 -0
  33. package/dist/agent-md.d.ts +7 -0
  34. package/dist/agent-md.js +91 -9
  35. package/dist/agent-md.js.map +1 -1
  36. package/dist/aid-store.d.ts +5 -1
  37. package/dist/aid-store.js +25 -6
  38. package/dist/aid-store.js.map +1 -1
  39. package/dist/aid.d.ts +2 -0
  40. package/dist/aid.js +32 -12
  41. package/dist/aid.js.map +1 -1
  42. package/dist/auth.d.ts +3 -1
  43. package/dist/auth.js +65 -13
  44. package/dist/auth.js.map +1 -1
  45. package/dist/cert-utils.d.ts +2 -1
  46. package/dist/cert-utils.js +32 -16
  47. package/dist/cert-utils.js.map +1 -1
  48. package/dist/client/delivery.d.ts +36 -7
  49. package/dist/client/delivery.js +701 -216
  50. package/dist/client/delivery.js.map +1 -1
  51. package/dist/client/group-state.js +2 -2
  52. package/dist/client/group-state.js.map +1 -1
  53. package/dist/client/lifecycle.d.ts +15 -1
  54. package/dist/client/lifecycle.js +475 -152
  55. package/dist/client/lifecycle.js.map +1 -1
  56. package/dist/client/rpc-pipeline.d.ts +24 -6
  57. package/dist/client/rpc-pipeline.js +372 -131
  58. package/dist/client/rpc-pipeline.js.map +1 -1
  59. package/dist/client/runtime.d.ts +1 -3
  60. package/dist/client/runtime.js +4 -7
  61. package/dist/client/runtime.js.map +1 -1
  62. package/dist/client/v2-e2ee.d.ts +20 -3
  63. package/dist/client/v2-e2ee.js +376 -93
  64. package/dist/client/v2-e2ee.js.map +1 -1
  65. package/dist/client.d.ts +23 -10
  66. package/dist/client.js +515 -150
  67. package/dist/client.js.map +1 -1
  68. package/dist/discovery.d.ts +1 -0
  69. package/dist/discovery.js +3 -0
  70. package/dist/discovery.js.map +1 -1
  71. package/dist/facades.d.ts +23 -1
  72. package/dist/facades.js +59 -7
  73. package/dist/facades.js.map +1 -1
  74. package/dist/group-id.js +2 -17
  75. package/dist/group-id.js.map +1 -1
  76. package/dist/index.d.ts +4 -3
  77. package/dist/index.js +2 -1
  78. package/dist/index.js.map +1 -1
  79. package/dist/net.d.ts +3 -0
  80. package/dist/net.js +13 -0
  81. package/dist/net.js.map +1 -1
  82. package/dist/seq-tracker.js +6 -1
  83. package/dist/seq-tracker.js.map +1 -1
  84. package/dist/tools/cross-sdk-agent.js +29 -3
  85. package/dist/tools/cross-sdk-agent.js.map +1 -1
  86. package/dist/transport.js +8 -1
  87. package/dist/transport.js.map +1 -1
  88. package/dist/validators.js +8 -14
  89. package/dist/validators.js.map +1 -1
  90. package/dist/version.d.ts +1 -1
  91. package/dist/version.js +1 -1
  92. package/package.json +4 -3
@@ -1,5 +1,25 @@
1
- import { StateError, ValidationError } from '../errors.js';
2
1
  import { ConnectionState } from '../types.js';
2
+ import { AUNError, AuthError, ConnectionError, PermissionError, StateError, TimeoutError, ValidationError, } from '../errors.js';
3
+ function shouldRetryInitialConnect(error) {
4
+ if (error instanceof AuthError) {
5
+ const message = String(error.message ?? '').toLowerCase();
6
+ return message.includes('long_connection_already_exists')
7
+ || message.includes('long connection already exists')
8
+ || message.includes('aid_login1_failed')
9
+ || message.includes('aid_login2_failed');
10
+ }
11
+ if (error instanceof PermissionError || error instanceof ValidationError || error instanceof StateError) {
12
+ return false;
13
+ }
14
+ if (error instanceof ConnectionError || error instanceof TimeoutError) {
15
+ return true;
16
+ }
17
+ if (error instanceof AUNError) {
18
+ return error.retryable;
19
+ }
20
+ // Node/WebSocket 底层异常不一定继承 SDK 错误类型,默认视为瞬时错误。
21
+ return true;
22
+ }
3
23
  const PUBLIC_CONNECTION_OPTION_KEYS = new Set([
4
24
  'auto_reconnect',
5
25
  'connect_timeout',
@@ -73,9 +93,194 @@ export class LifecycleController {
73
93
  constructor(runtime) {
74
94
  this.runtime = runtime;
75
95
  }
96
+ beginConnectionAttempt() {
97
+ const client = this.runtime.client;
98
+ if (client._closing || client.state === ConnectionState.CLOSED) {
99
+ throw new StateError('connect not allowed on a closed client');
100
+ }
101
+ if (client._disconnecting) {
102
+ throw new StateError('connect not allowed while disconnect is in progress');
103
+ }
104
+ if (client._connectDone) {
105
+ throw new StateError('connect already in progress');
106
+ }
107
+ const owner = new AbortController();
108
+ let resolveDone;
109
+ const done = new Promise((resolve) => { resolveDone = resolve; });
110
+ client._connectAbort = owner;
111
+ client._connectDoneOwner = owner;
112
+ client._connectDone = done;
113
+ client._connectDoneResolve = resolveDone;
114
+ client._connectEventDispatchDepth = Number(client._connectEventDispatchDepth ?? 0);
115
+ return owner;
116
+ }
117
+ finishConnectionAttempt(owner) {
118
+ const client = this.runtime.client;
119
+ if (client._connectDoneOwner !== owner)
120
+ return;
121
+ const resolveDone = client._connectDoneResolve;
122
+ if (client._connectAbort === owner)
123
+ client._connectAbort = null;
124
+ client._connectDoneOwner = null;
125
+ client._connectDone = null;
126
+ client._connectDoneResolve = null;
127
+ resolveDone?.();
128
+ }
129
+ ownsConnectionAttempt(owner) {
130
+ if (!owner)
131
+ return true;
132
+ const client = this.runtime.client;
133
+ return client._connectAbort === owner
134
+ && !owner.signal.aborted
135
+ && !client._closing
136
+ && !client._disconnecting;
137
+ }
138
+ assertConnectionAttemptOwner(owner) {
139
+ if (owner && !this.ownsConnectionAttempt(owner)) {
140
+ throw new StateError('connection attempt superseded');
141
+ }
142
+ }
143
+ assertAttemptOwners(connectionOwner, reconnectOwner) {
144
+ this.assertConnectionAttemptOwner(connectionOwner);
145
+ this.runtime.client._assertReconnectOwner?.(reconnectOwner);
146
+ }
147
+ async waitForAttempt(operation, connectionOwner, reconnectOwner) {
148
+ const owner = connectionOwner ?? reconnectOwner;
149
+ if (!owner)
150
+ return operation;
151
+ this.assertAttemptOwners(connectionOwner, reconnectOwner);
152
+ return new Promise((resolve, reject) => {
153
+ let settled = false;
154
+ const finish = (callback) => {
155
+ if (settled)
156
+ return;
157
+ settled = true;
158
+ owner.signal.removeEventListener('abort', onAbort);
159
+ callback();
160
+ };
161
+ const onAbort = () => finish(() => reject(connectionOwner
162
+ ? new StateError('connection attempt superseded')
163
+ : new ConnectionError('reconnect attempt superseded')));
164
+ owner.signal.addEventListener('abort', onAbort, { once: true });
165
+ if (owner.signal.aborted) {
166
+ onAbort();
167
+ return;
168
+ }
169
+ operation.then((value) => finish(() => {
170
+ try {
171
+ this.assertAttemptOwners(connectionOwner, reconnectOwner);
172
+ resolve(value);
173
+ }
174
+ catch (err) {
175
+ reject(err);
176
+ }
177
+ }), (err) => finish(() => reject(err)));
178
+ });
179
+ }
180
+ async publishConnectionEvent(owner, event, payload) {
181
+ const client = this.runtime.client;
182
+ this.assertConnectionAttemptOwner(owner);
183
+ client._connectEventDispatchDepth = Number(client._connectEventDispatchDepth ?? 0) + 1;
184
+ try {
185
+ await client._dispatcher.publish(event, payload);
186
+ }
187
+ finally {
188
+ client._connectEventDispatchDepth -= 1;
189
+ }
190
+ return this.ownsConnectionAttempt(owner);
191
+ }
192
+ async cancelConnectionAttemptAndWait() {
193
+ const client = this.runtime.client;
194
+ const owner = client._connectDoneOwner;
195
+ const done = client._connectDone;
196
+ if (!owner || !done)
197
+ return;
198
+ if (client._connectAbort === owner)
199
+ client._connectAbort = null;
200
+ owner.abort();
201
+ client._discovery?._cancelPending?.();
202
+ client._auth?._cancelPending?.();
203
+ // READY 事件按顺序同步分发;handler 内等待所属 connect 会自锁。
204
+ if (Number(client._connectEventDispatchDepth ?? 0) > 0)
205
+ return;
206
+ let timeout = null;
207
+ try {
208
+ await Promise.race([
209
+ done,
210
+ new Promise((_, reject) => {
211
+ timeout = setTimeout(() => reject(new StateError('timed out waiting for connect attempt to stop')), 1_000);
212
+ }),
213
+ ]);
214
+ }
215
+ finally {
216
+ if (timeout !== null)
217
+ clearTimeout(timeout);
218
+ }
219
+ }
220
+ lifecycleStopEventIsReentrant() {
221
+ const client = this.runtime.client;
222
+ return Boolean(client._lifecycleStopActive)
223
+ && Number(client._lifecycleStopEventDispatchDepth ?? 0) > 0;
224
+ }
225
+ scheduleReentrantClose() {
226
+ const client = this.runtime.client;
227
+ const tail = client._lifecycleStopTail;
228
+ if (!tail)
229
+ return;
230
+ void tail.then(() => this.close()).catch((err) => {
231
+ client._clientLog.warn(`reentrant close failed: ${formatCaughtError(err)}`);
232
+ });
233
+ }
234
+ async publishLifecycleStopEvent(payload) {
235
+ const client = this.runtime.client;
236
+ client._lifecycleStopEventDispatchDepth = Number(client._lifecycleStopEventDispatchDepth ?? 0) + 1;
237
+ try {
238
+ await client._dispatcher.publish('state_change', payload);
239
+ }
240
+ finally {
241
+ client._lifecycleStopEventDispatchDepth -= 1;
242
+ }
243
+ }
244
+ async withLifecycleStop(operation, kind) {
245
+ const client = this.runtime.client;
246
+ const previous = client._lifecycleStopTail;
247
+ let release;
248
+ const gate = new Promise((resolve) => { release = resolve; });
249
+ // 首个 stop 操作要在当前调用栈内进入清理;只有已有 stop 时才排队等待。
250
+ // 这样既保持 stop 之间的串行化,也不让调用方观察到无意义的额外微任务延迟。
251
+ const tail = previous ? previous.catch(() => { }).then(() => gate) : gate;
252
+ client._lifecycleStopTail = tail;
253
+ if (previous)
254
+ await previous.catch(() => { });
255
+ client._lifecycleStopActive = true;
256
+ client._lifecycleStopKind = kind;
257
+ try {
258
+ return await operation();
259
+ }
260
+ finally {
261
+ client._lifecycleStopActive = false;
262
+ client._lifecycleStopKind = null;
263
+ release();
264
+ if (client._lifecycleStopTail === tail)
265
+ client._lifecycleStopTail = null;
266
+ }
267
+ }
268
+ /** 首连后台重试使用的完整尝试;不在失败时再次创建重连任务。 */
269
+ async retryInitialConnect(opts, reconnectOwner) {
270
+ const client = this.runtime.client;
271
+ client._assertReconnectOwner?.(reconnectOwner);
272
+ this.runtime.lifecycle.setState('standby');
273
+ client._gatewayUrl = null;
274
+ client._gatewayCandidates = [];
275
+ return this.connect(opts, true, reconnectOwner);
276
+ }
76
277
  async authenticate(options = {}) {
77
278
  const client = this.runtime.client;
78
279
  const tStart = Date.now();
280
+ const inheritedAttempt = client._activeAuthenticateAttempt;
281
+ let connectionOwner = inheritedAttempt?.connectionOwner;
282
+ const reconnectOwner = inheritedAttempt?.reconnectOwner;
283
+ let ownsPublicAttempt = false;
79
284
  const target = client._currentAid?.aid ?? client._aid ?? '';
80
285
  if (!target || !client._currentAid?.isPrivateKeyValid()) {
81
286
  throw new StateError('authenticate requires a loaded AID with a valid private key');
@@ -90,11 +295,24 @@ export class LifecycleController {
90
295
  if ('aid' in options || 'access_token' in options || 'token' in options || 'kite_token' in options) {
91
296
  throw new ValidationError('authenticate options must not include aid or token fields; load an AID object first');
92
297
  }
298
+ if (!connectionOwner && !reconnectOwner) {
299
+ connectionOwner = this.beginConnectionAttempt();
300
+ ownsPublicAttempt = true;
301
+ }
302
+ this.assertAttemptOwners(connectionOwner, reconnectOwner);
93
303
  this.runtime.lifecycle.setState('connecting');
94
304
  try {
95
- const discovered = typeof client._resolveGatewayCandidatesForAid === 'function'
96
- ? await client._resolveGatewayCandidatesForAid(target)
97
- : [String(client._gatewayUrl ?? await client._resolveGatewayForAid(target)).trim()];
305
+ let discovered;
306
+ if (typeof client._resolveGatewayCandidatesForAid === 'function') {
307
+ discovered = await this.waitForAttempt(Promise.resolve(client._resolveGatewayCandidatesForAid(target)), connectionOwner, reconnectOwner);
308
+ }
309
+ else if (client._gatewayUrl) {
310
+ discovered = [String(client._gatewayUrl).trim()];
311
+ }
312
+ else {
313
+ const gateway = await this.waitForAttempt(Promise.resolve(client._resolveGatewayForAid(target)), connectionOwner, reconnectOwner);
314
+ discovered = [String(gateway).trim()];
315
+ }
98
316
  const gateways = [...new Set(discovered
99
317
  .map((item) => String(item ?? '').trim()).filter(Boolean))];
100
318
  if (gateways.length === 0)
@@ -104,7 +322,7 @@ export class LifecycleController {
104
322
  let lastError = null;
105
323
  for (const candidate of gateways) {
106
324
  try {
107
- result = await client._auth.authenticate(candidate, { aid: target });
325
+ result = await this.waitForAttempt(Promise.resolve(client._auth.authenticate(candidate, { aid: target })), connectionOwner, reconnectOwner);
108
326
  gateway = candidate;
109
327
  break;
110
328
  }
@@ -117,6 +335,7 @@ export class LifecycleController {
117
335
  }
118
336
  if (result === null)
119
337
  throw (lastError instanceof Error ? lastError : new Error(String(lastError)));
338
+ this.assertAttemptOwners(connectionOwner, reconnectOwner);
120
339
  client._gatewayCandidates = gateways;
121
340
  this.runtime.lifecycle.setGatewayUrl(gatewayFromAuthResult(result, gateway));
122
341
  let loadedIdentity = null;
@@ -133,13 +352,19 @@ export class LifecycleController {
133
352
  return result;
134
353
  }
135
354
  catch (err) {
355
+ this.assertAttemptOwners(connectionOwner, reconnectOwner);
136
356
  this.runtime.lifecycle.setState('standby');
137
357
  this.runtime.lifecycle.setError(err instanceof Error ? err : new Error(String(err)), 'AUTHENTICATE_FAILED');
138
358
  client._clientLog.debug(`authenticate exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
139
359
  throw err;
140
360
  }
361
+ finally {
362
+ if (ownsPublicAttempt && connectionOwner) {
363
+ this.finishConnectionAttempt(connectionOwner);
364
+ }
365
+ }
141
366
  }
142
- async connect(opts) {
367
+ async connect(opts, fromInitialReconnect = false, reconnectOwner) {
143
368
  const client = this.runtime.client;
144
369
  const tStart = Date.now();
145
370
  if (opts !== undefined && opts !== null && typeof opts === 'object') {
@@ -153,183 +378,281 @@ export class LifecycleController {
153
378
  if (!target || !client._currentAid?.isPrivateKeyValid()) {
154
379
  throw new StateError('connect requires a loaded AID with a valid private key');
155
380
  }
156
- const options = {};
157
- if (opts?.auto_reconnect !== undefined)
158
- options.auto_reconnect = opts.auto_reconnect;
159
- if (opts?.heartbeat_interval !== undefined)
160
- options.heartbeat_interval = opts.heartbeat_interval;
161
- if (opts?.connect_timeout !== undefined || opts?.call_timeout !== undefined) {
162
- options.timeouts = {
163
- ...(opts.connect_timeout !== undefined ? { connect: opts.connect_timeout } : {}),
164
- ...(opts.call_timeout !== undefined ? { call: opts.call_timeout } : {}),
165
- };
166
- }
167
- if (opts?.retry !== undefined)
168
- options.retry = opts.retry;
169
- if (opts?.retry_initial_delay !== undefined || opts?.retry_max_delay !== undefined || opts?.retry_max_attempts !== undefined) {
170
- const baseRetry = isRecord(options.retry)
171
- ? { ...options.retry }
172
- : { initial_delay: 1, max_delay: 64, max_attempts: 0 };
173
- options.retry = {
174
- ...baseRetry,
175
- ...(opts.retry_initial_delay !== undefined ? { initial_delay: opts.retry_initial_delay } : {}),
176
- ...(opts.retry_max_delay !== undefined ? { max_delay: opts.retry_max_delay } : {}),
177
- ...(opts.retry_max_attempts !== undefined ? { max_attempts: opts.retry_max_attempts } : {}),
178
- };
179
- }
180
- if (opts?.connection_kind !== undefined)
181
- options.connection_kind = opts.connection_kind;
182
- if (opts?.short_ttl_ms !== undefined)
183
- options.short_ttl_ms = opts.short_ttl_ms;
184
- if (opts?.delivery_mode !== undefined)
185
- options.delivery_mode = opts.delivery_mode;
186
- if (opts?.extra_info !== undefined)
187
- options.extra_info = opts.extra_info;
188
- if (opts?.background_sync !== undefined)
189
- options.background_sync = opts.background_sync;
190
- const publicState = client.state;
191
- const allowed = new Set([
192
- ConnectionState.STANDBY,
193
- ConnectionState.AUTHENTICATED,
194
- ConnectionState.RETRY_BACKOFF,
195
- ConnectionState.CONNECTION_FAILED,
196
- ]);
197
- if (!allowed.has(publicState)) {
198
- throw new StateError(`connect not allowed in state ${publicState}`);
199
- }
200
- if (publicState === ConnectionState.RETRY_BACKOFF) {
201
- client._stopReconnect();
202
- }
203
- if (publicState === ConnectionState.CONNECTION_FAILED) {
204
- this.runtime.lifecycle.setRetryAttempt(0);
205
- this.runtime.lifecycle.setError(null, null);
206
- }
207
- this.runtime.lifecycle.setNextRetryAt(null);
208
- let authResult = null;
209
- if (!client._gatewayUrl) {
210
- authResult = await client.authenticate();
211
- }
212
- this.runtime.lifecycle.setState('connecting');
213
- const gateway = String(client._gatewayUrl ?? '').trim();
214
- const accessToken = accessTokenFromAuthResult(authResult) || cachedAccessToken(client);
215
- const params = {
216
- ...options,
217
- gateway,
218
- ...(Array.isArray(client._gatewayCandidates) && client._gatewayCandidates.length > 0
219
- ? { gateways: [...client._gatewayCandidates] }
220
- : {}),
221
- ...(accessToken ? { access_token: accessToken } : {}),
222
- };
223
- const normalized = client._normalizeConnectParams(params);
224
- client._captureCapabilitiesFromConnect(normalized);
225
- this.runtime.lifecycle.setSession(normalized, client._buildSessionOptions(normalized));
226
- const connectTimeoutSec = client._sessionOptions.timeouts.connect;
227
- client._transport.setConnectTimeout(connectTimeoutSec != null ? connectTimeoutSec * 1000 : 5_000);
228
- const callTimeoutSec = client._sessionOptions.timeouts.call;
229
- client._transport.setTimeout(callTimeoutSec != null ? callTimeoutSec * 1000 : 35_000);
230
- this.runtime.lifecycle.setClosing(false);
231
- client._clientLog.debug(`connect enter: gateway=${String(normalized.gateway ?? '')}, device_id=${client._deviceId}`);
232
- const gateways = client._resolveGateways(normalized);
233
- let lastErr = null;
234
- for (const gw of gateways) {
235
- try {
236
- const gwParams = { ...normalized, gateway: gw };
237
- await client._connectOnce(gwParams, true);
381
+ const connectionOwner = fromInitialReconnect ? undefined : this.beginConnectionAttempt();
382
+ try {
383
+ const options = {};
384
+ if (opts?.auto_reconnect !== undefined)
385
+ options.auto_reconnect = opts.auto_reconnect;
386
+ if (opts?.heartbeat_interval !== undefined)
387
+ options.heartbeat_interval = opts.heartbeat_interval;
388
+ if (opts?.connect_timeout !== undefined || opts?.call_timeout !== undefined) {
389
+ options.timeouts = {
390
+ ...(opts.connect_timeout !== undefined ? { connect: opts.connect_timeout } : {}),
391
+ ...(opts.call_timeout !== undefined ? { call: opts.call_timeout } : {}),
392
+ };
393
+ }
394
+ if (opts?.retry !== undefined)
395
+ options.retry = opts.retry;
396
+ if (opts?.retry_initial_delay !== undefined || opts?.retry_max_delay !== undefined || opts?.retry_max_attempts !== undefined) {
397
+ const baseRetry = isRecord(options.retry)
398
+ ? { ...options.retry }
399
+ : { initial_delay: 1, max_delay: 64, max_attempts: 0 };
400
+ options.retry = {
401
+ ...baseRetry,
402
+ ...(opts.retry_initial_delay !== undefined ? { initial_delay: opts.retry_initial_delay } : {}),
403
+ ...(opts.retry_max_delay !== undefined ? { max_delay: opts.retry_max_delay } : {}),
404
+ ...(opts.retry_max_attempts !== undefined ? { max_attempts: opts.retry_max_attempts } : {}),
405
+ };
406
+ }
407
+ if (opts?.connection_kind !== undefined)
408
+ options.connection_kind = opts.connection_kind;
409
+ if (opts?.short_ttl_ms !== undefined)
410
+ options.short_ttl_ms = opts.short_ttl_ms;
411
+ if (opts?.delivery_mode !== undefined)
412
+ options.delivery_mode = opts.delivery_mode;
413
+ if (opts?.extra_info !== undefined)
414
+ options.extra_info = opts.extra_info;
415
+ if (opts?.background_sync !== undefined)
416
+ options.background_sync = opts.background_sync;
417
+ const publicState = client.state;
418
+ const allowed = new Set([
419
+ ConnectionState.STANDBY,
420
+ ConnectionState.AUTHENTICATED,
421
+ ConnectionState.RETRY_BACKOFF,
422
+ ConnectionState.CONNECTION_FAILED,
423
+ ]);
424
+ const hasReconnectController = Boolean(client._reconnectAbort || client._reconnectActive || client._reconnectTask);
425
+ if (!allowed.has(publicState) && !(hasReconnectController && !fromInitialReconnect)) {
426
+ throw new StateError(`connect not allowed in state ${publicState}`);
427
+ }
428
+ // 显式 connect 开启新的连接意图,必须取消 standby/retry_backoff 窗口中
429
+ // 尚未完成的旧重连代;内部首连重试不能取消自己的控制器。
430
+ if (!fromInitialReconnect && hasReconnectController) {
431
+ await client._cancelReconnectAndWait();
432
+ }
433
+ else if (fromInitialReconnect) {
434
+ client._assertReconnectOwner?.(reconnectOwner);
435
+ }
436
+ if (publicState === ConnectionState.CONNECTION_FAILED) {
437
+ this.runtime.lifecycle.setRetryAttempt(0);
238
438
  this.runtime.lifecycle.setError(null, null);
239
- client._clientLog.debug(`connect exit: elapsed=${Date.now() - tStart}ms aid=${client._aid ?? ''}, state=${client._state}`);
240
- return;
241
439
  }
242
- catch (err) {
243
- lastErr = err;
440
+ this.runtime.lifecycle.setNextRetryAt(null);
441
+ // 认证/发现发生在 session 参数可归一化之前,先保存会话选项,
442
+ // 这样首连失败也能沿用同一套退避与 auto_reconnect 配置。
443
+ this.runtime.lifecycle.setSession(null, client._buildSessionOptions({
444
+ ...options,
445
+ connection_kind: opts?.connection_kind ?? 'long',
446
+ }));
447
+ let authResult = null;
448
+ if (!client._gatewayUrl) {
449
+ const previousAttempt = client._activeAuthenticateAttempt;
450
+ client._activeAuthenticateAttempt = { connectionOwner, reconnectOwner };
244
451
  try {
245
- await client._transport.close();
452
+ authResult = await this.waitForAttempt(Promise.resolve(client.authenticate()), connectionOwner, reconnectOwner);
453
+ this.assertAttemptOwners(connectionOwner, reconnectOwner);
246
454
  }
247
- catch (closeErr) {
248
- client._clientLog.debug(`connect gateway cleanup skipped: ${formatCaughtError(closeErr)}`);
455
+ catch (err) {
456
+ this.assertAttemptOwners(connectionOwner, reconnectOwner);
457
+ const connectError = err instanceof Error ? err : new Error(String(err));
458
+ this.runtime.lifecycle.setError(connectError, 'CONNECT_FAILED');
459
+ if (!fromInitialReconnect && client._sessionOptions?.auto_reconnect && shouldRetryInitialConnect(connectError)
460
+ && typeof client._handleTransportDisconnect === 'function') {
461
+ try {
462
+ await client._handleTransportDisconnect(connectError, undefined, opts ?? {});
463
+ }
464
+ catch (reconnectErr) {
465
+ client._clientLog.warn(`initial reconnect scheduling failed: ${reconnectErr instanceof Error ? reconnectErr.message : String(reconnectErr)}`);
466
+ }
467
+ }
468
+ throw err;
249
469
  }
250
- if (gateways.length > 1) {
251
- client._clientLog.warn(`connect: gateway ${gw} failed, trying next: ${formatCaughtError(err)}`);
470
+ finally {
471
+ if (client._activeAuthenticateAttempt?.connectionOwner === connectionOwner
472
+ && client._activeAuthenticateAttempt?.reconnectOwner === reconnectOwner) {
473
+ client._activeAuthenticateAttempt = previousAttempt ?? null;
474
+ }
252
475
  }
253
- if (client._state !== 'closed')
254
- this.runtime.lifecycle.setState('connecting');
255
476
  }
477
+ this.assertAttemptOwners(connectionOwner, reconnectOwner);
478
+ this.runtime.lifecycle.setState('connecting');
479
+ const gateway = String(client._gatewayUrl ?? '').trim();
480
+ const accessToken = accessTokenFromAuthResult(authResult) || cachedAccessToken(client);
481
+ const params = {
482
+ ...options,
483
+ gateway,
484
+ ...(Array.isArray(client._gatewayCandidates) && client._gatewayCandidates.length > 0
485
+ ? { gateways: [...client._gatewayCandidates] }
486
+ : {}),
487
+ ...(accessToken ? { access_token: accessToken } : {}),
488
+ };
489
+ const normalized = client._normalizeConnectParams(params);
490
+ client._captureCapabilitiesFromConnect(normalized);
491
+ this.runtime.lifecycle.setSession(normalized, client._buildSessionOptions(normalized));
492
+ const connectTimeoutSec = client._sessionOptions.timeouts.connect;
493
+ client._transport.setConnectTimeout(connectTimeoutSec != null ? connectTimeoutSec * 1000 : 5_000);
494
+ const callTimeoutSec = client._sessionOptions.timeouts.call;
495
+ client._transport.setTimeout(callTimeoutSec != null ? callTimeoutSec * 1000 : 35_000);
496
+ this.assertAttemptOwners(connectionOwner, reconnectOwner);
497
+ this.runtime.lifecycle.setClosing(false);
498
+ client._clientLog.debug(`connect enter: gateway=${String(normalized.gateway ?? '')}, device_id=${client._deviceId}`);
499
+ const gateways = client._resolveGateways(normalized);
500
+ let lastErr = null;
501
+ for (const gw of gateways) {
502
+ try {
503
+ const gwParams = { ...normalized, gateway: gw };
504
+ await this.waitForAttempt(Promise.resolve(client._connectOnce(gwParams, true, reconnectOwner)), connectionOwner, reconnectOwner);
505
+ this.assertAttemptOwners(connectionOwner, reconnectOwner);
506
+ this.runtime.lifecycle.setError(null, null);
507
+ client._clientLog.debug(`connect exit: elapsed=${Date.now() - tStart}ms aid=${client._aid ?? ''}, state=${client._state}`);
508
+ return;
509
+ }
510
+ catch (err) {
511
+ lastErr = err;
512
+ this.assertAttemptOwners(connectionOwner, reconnectOwner);
513
+ if (reconnectOwner && !client._ownsReconnect?.(reconnectOwner))
514
+ throw err;
515
+ try {
516
+ await client._transport.close();
517
+ }
518
+ catch (closeErr) {
519
+ client._clientLog.debug(`connect gateway cleanup skipped: ${formatCaughtError(closeErr)}`);
520
+ }
521
+ if (gateways.length > 1) {
522
+ client._clientLog.warn(`connect: gateway ${gw} failed, trying next: ${formatCaughtError(err)}`);
523
+ }
524
+ if (client._state !== 'closed')
525
+ this.runtime.lifecycle.setState('connecting');
526
+ }
527
+ }
528
+ if (client._state === 'connecting' || client._state === 'authenticating') {
529
+ this.runtime.lifecycle.setState(client._currentAid || client._aid ? 'standby' : 'no_identity');
530
+ }
531
+ const connectError = lastErr instanceof Error ? lastErr : new Error(String(lastErr));
532
+ this.runtime.lifecycle.setError(connectError, 'CONNECT_FAILED');
533
+ if (!fromInitialReconnect && client._sessionOptions?.auto_reconnect && shouldRetryInitialConnect(connectError)
534
+ && typeof client._handleTransportDisconnect === 'function') {
535
+ try {
536
+ // 首连与已建立连接的断线共享同一重连入口,保证退避、并发闸门和取消语义一致。
537
+ await client._handleTransportDisconnect(connectError, undefined, opts ?? {});
538
+ }
539
+ catch (reconnectErr) {
540
+ client._clientLog.warn(`initial reconnect scheduling failed: ${formatCaughtError(reconnectErr)}`);
541
+ }
542
+ }
543
+ client._clientLog.error(`connect failed: ${formatCaughtError(lastErr)}`, lastErr instanceof Error ? lastErr : undefined);
544
+ client._clientLog.debug(`connect exit (error): elapsed=${Date.now() - tStart}ms err=${lastErr instanceof Error ? lastErr.message : String(lastErr)}`);
545
+ throw lastErr;
256
546
  }
257
- if (client._state === 'connecting' || client._state === 'authenticating') {
258
- this.runtime.lifecycle.setState(client._currentAid || client._aid ? 'standby' : 'no_identity');
547
+ finally {
548
+ if (connectionOwner)
549
+ this.finishConnectionAttempt(connectionOwner);
259
550
  }
260
- this.runtime.lifecycle.setError(lastErr instanceof Error ? lastErr : new Error(String(lastErr)), 'CONNECT_FAILED');
261
- client._clientLog.error(`connect failed: ${formatCaughtError(lastErr)}`, lastErr instanceof Error ? lastErr : undefined);
262
- client._clientLog.debug(`connect exit (error): elapsed=${Date.now() - tStart}ms err=${lastErr instanceof Error ? lastErr.message : String(lastErr)}`);
263
- throw lastErr;
264
551
  }
265
552
  async close() {
266
553
  const client = this.runtime.client;
267
554
  const tStart = Date.now();
268
555
  client._clientLog.debug(`close enter: state=${client._state}, aid=${client._aid ?? ''}`);
269
- try {
270
- this.runtime.lifecycle.setClosing(true);
271
- // 不等待 transport.close,先隔离已在途的 inline Push 回调。
272
- client._delivery.resetInlineAckState();
273
- client._saveSeqTrackerState();
274
- client._stopBackgroundTasks();
275
- client._stopReconnect();
276
- if (client.state === ConnectionState.NO_IDENTITY || client.state === ConnectionState.CLOSED) {
556
+ // 关闭意图必须在第一个 await 前生效,阻止迟到的连接/重连结果复活客户端。
557
+ this.runtime.lifecycle.setClosing(true);
558
+ client._delivery.resetInlineAckState();
559
+ if (this.lifecycleStopEventIsReentrant()) {
560
+ // 当前 stop 操作正在等待自己的 state_change handler;直接等待闸门会自锁。
561
+ // 先返回让 handler 完成,再由闸门尾部补做真正的 close。
562
+ if (client._lifecycleStopKind !== 'close')
563
+ this.scheduleReentrantClose();
564
+ return;
565
+ }
566
+ return this.withLifecycleStop(async () => {
567
+ try {
568
+ const pullInvalidation = client._rpcPipeline?.invalidatePulls?.();
569
+ client._rpcPipeline?.stopPullGateWatchdogs?.();
570
+ const reconnectCancellation = Promise.resolve(client._cancelReconnectAndWait());
571
+ const connectionCancellation = client._connectDone
572
+ ? this.cancelConnectionAttemptAndWait()
573
+ : Promise.resolve();
574
+ client._stopBackgroundTasks();
575
+ if (client.state === ConnectionState.NO_IDENTITY || client.state === ConnectionState.CLOSED) {
576
+ await Promise.all([pullInvalidation, reconnectCancellation, connectionCancellation]);
577
+ await client._saveSeqTrackerState();
578
+ const closableStore = client._tokenStore;
579
+ closableStore.close?.();
580
+ this.runtime.lifecycle.setState('closed');
581
+ client._logger.close();
582
+ client._resetSeqTrackingState();
583
+ client._clientLog.debug(`close exit: elapsed=${Date.now() - tStart}ms (was no_identity/closed)`);
584
+ return;
585
+ }
586
+ await client._transport.close();
587
+ await Promise.all([pullInvalidation, reconnectCancellation, connectionCancellation]);
588
+ await client._saveSeqTrackerState();
277
589
  const closableStore = client._tokenStore;
278
590
  closableStore.close?.();
279
591
  this.runtime.lifecycle.setState('closed');
280
592
  client._logger.close();
593
+ await this.publishLifecycleStopEvent({ state: client._publicState(client._state) });
281
594
  client._resetSeqTrackingState();
282
- client._clientLog.debug(`close exit: elapsed=${Date.now() - tStart}ms (was no_identity/closed)`);
283
- return;
595
+ client._clientLog.debug(`close exit: elapsed=${Date.now() - tStart}ms`);
284
596
  }
285
- await client._transport.close();
286
- const closableStore = client._tokenStore;
287
- closableStore.close?.();
288
- this.runtime.lifecycle.setState('closed');
289
- client._logger.close();
290
- await client._dispatcher.publish('state_change', { state: client._publicState(client._state) });
291
- client._resetSeqTrackingState();
292
- client._clientLog.debug(`close exit: elapsed=${Date.now() - tStart}ms`);
293
- }
294
- catch (err) {
295
- client._clientLog.debug(`close exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
296
- throw err;
297
- }
597
+ catch (err) {
598
+ client._clientLog.debug(`close exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
599
+ throw err;
600
+ }
601
+ }, 'close');
298
602
  }
299
603
  async disconnect() {
300
604
  const client = this.runtime.client;
301
605
  const tStart = Date.now();
302
606
  client._clientLog.debug(`disconnect enter: state=${client._state}, aid=${client._aid ?? ''}, closing=${client._closing}`);
607
+ if (client._closing || client._disconnecting || this.lifecycleStopEventIsReentrant())
608
+ return;
609
+ client._disconnecting = true;
303
610
  try {
304
- if (client._closing) {
305
- client._clientLog.debug(`disconnect exit: elapsed=${Date.now() - tStart}ms (closing)`);
306
- return;
307
- }
308
- if (![
309
- ConnectionState.AUTHENTICATED,
310
- ConnectionState.CONNECTING,
311
- ConnectionState.READY,
312
- ConnectionState.RETRY_BACKOFF,
313
- ConnectionState.RECONNECTING,
314
- ConnectionState.CONNECTION_FAILED,
315
- ].includes(client.state)) {
316
- client._clientLog.debug(`disconnect exit: elapsed=${Date.now() - tStart}ms (state=${client._state})`);
317
- return;
318
- }
319
- // 不等待 transport.close,先隔离已在途的 inline Push 回调。
320
- client._delivery.resetInlineAckState();
321
- client._saveSeqTrackerState();
322
- client._stopBackgroundTasks();
323
- client._stopReconnect();
324
- await client._transport.close();
325
- this.runtime.lifecycle.resetForDisconnect('standby');
326
- await client._dispatcher.publish('state_change', { state: client._publicState(client._state) });
327
- client._clientLog.debug(`disconnect exit: elapsed=${Date.now() - tStart}ms`);
611
+ return await this.withLifecycleStop(async () => {
612
+ if (client._closing) {
613
+ client._clientLog.debug(`disconnect exit: elapsed=${Date.now() - tStart}ms (closing)`);
614
+ return;
615
+ }
616
+ const hasInitialReconnect = Boolean(client._reconnectActive || client._reconnectAbort || client._reconnectTask);
617
+ const hasConnect = Boolean(client._connectDone);
618
+ if (!([
619
+ ConnectionState.AUTHENTICATED,
620
+ ConnectionState.CONNECTING,
621
+ ConnectionState.READY,
622
+ ConnectionState.RETRY_BACKOFF,
623
+ ConnectionState.RECONNECTING,
624
+ ConnectionState.CONNECTION_FAILED,
625
+ ].includes(client.state)) && !hasInitialReconnect && !hasConnect) {
626
+ client._clientLog.debug(`disconnect exit: elapsed=${Date.now() - tStart}ms (state=${client._state})`);
627
+ return;
628
+ }
629
+ // 不等待 transport.close,先隔离已在途的 inline Push 回调。
630
+ client._delivery.resetInlineAckState();
631
+ const pullInvalidation = client._rpcPipeline?.invalidatePulls?.();
632
+ client._rpcPipeline?.stopPullGateWatchdogs?.();
633
+ const reconnectCancellation = Promise.resolve(client._cancelReconnectAndWait());
634
+ const connectionCancellation = client._connectDone
635
+ ? this.cancelConnectionAttemptAndWait()
636
+ : Promise.resolve();
637
+ client._stopBackgroundTasks();
638
+ await client._transport.close();
639
+ await Promise.all([pullInvalidation, reconnectCancellation, connectionCancellation]);
640
+ await client._saveSeqTrackerState();
641
+ // close() 可能在 transport.close 等待期间抢先声明关闭意图;此时不得回写 standby。
642
+ if (client._closing)
643
+ return;
644
+ this.runtime.lifecycle.resetForDisconnect('standby');
645
+ await this.publishLifecycleStopEvent({ state: client._publicState(client._state) });
646
+ client._clientLog.debug(`disconnect exit: elapsed=${Date.now() - tStart}ms`);
647
+ }, 'disconnect');
328
648
  }
329
649
  catch (err) {
330
650
  client._clientLog.debug(`disconnect exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
331
651
  throw err;
332
652
  }
653
+ finally {
654
+ client._disconnecting = false;
655
+ }
333
656
  }
334
657
  }
335
658
  //# sourceMappingURL=lifecycle.js.map