@agentunion/fastaun 0.5.9 → 0.5.10

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 (38) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/_packed_docs/CHANGELOG.md +32 -0
  3. package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +6 -5
  4. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +43 -8
  5. package/_packed_docs/sdk/09-group-rpc-manual.md +18 -3
  6. package/_packed_docs/sdk/09-message-rpc-manual.md +34 -10
  7. package/dist/agent-md.js +5 -1
  8. package/dist/agent-md.js.map +1 -1
  9. package/dist/auth.d.ts +2 -1
  10. package/dist/auth.js +30 -44
  11. package/dist/auth.js.map +1 -1
  12. package/dist/client/delivery.d.ts +21 -5
  13. package/dist/client/delivery.js +236 -52
  14. package/dist/client/delivery.js.map +1 -1
  15. package/dist/client/group-state.js +6 -6
  16. package/dist/client/group-state.js.map +1 -1
  17. package/dist/client/lifecycle.js +5 -14
  18. package/dist/client/lifecycle.js.map +1 -1
  19. package/dist/client/rpc-pipeline.d.ts +4 -0
  20. package/dist/client/rpc-pipeline.js +33 -1
  21. package/dist/client/rpc-pipeline.js.map +1 -1
  22. package/dist/client/v2-e2ee.d.ts +1 -0
  23. package/dist/client/v2-e2ee.js +43 -17
  24. package/dist/client/v2-e2ee.js.map +1 -1
  25. package/dist/client.js +21 -27
  26. package/dist/client.js.map +1 -1
  27. package/dist/events.d.ts +23 -8
  28. package/dist/events.js +120 -26
  29. package/dist/events.js.map +1 -1
  30. package/dist/register-flow.js +15 -111
  31. package/dist/register-flow.js.map +1 -1
  32. package/dist/transport.d.ts +32 -3
  33. package/dist/transport.js +758 -33
  34. package/dist/transport.js.map +1 -1
  35. package/dist/version.d.ts +1 -1
  36. package/dist/version.js +1 -1
  37. package/dist/version.js.map +1 -1
  38. package/package.json +1 -1
package/dist/transport.js CHANGED
@@ -9,7 +9,8 @@
9
9
  */
10
10
  import WebSocket from 'ws';
11
11
  import * as crypto from 'node:crypto';
12
- import { ConnectionError, SerializationError, TimeoutError, ValidationError, mapRemoteError } from './errors.js';
12
+ import { Worker, isMainThread, parentPort, workerData } from 'node:worker_threads';
13
+ import { AUNError, AuthError, CertificateRevokedError, ClientSignatureError, ConnectionError, E2EEDecryptFailedError, E2EEDegradedError, E2EEError, GroupError, GroupNotFoundError, GroupStateError, IdentityConflictError, NotFoundError, PermissionError, RateLimitError, SerializationError, SessionError, StateError, TimeoutError, ValidationError, VersionConflictError, mapRemoteError, } from './errors.js';
13
14
  import { isJsonObject } from './types.js';
14
15
  const MAX_WS_PAYLOAD_SIZE = 1_000_000;
15
16
  const MAX_RPC_INFLIGHT = 16;
@@ -236,6 +237,226 @@ const EVENT_NAME_MAP = {
236
237
  'group.message_recalled': 'group.message_recalled',
237
238
  'storage.object_changed': 'storage.object_changed',
238
239
  };
240
+ function serializeWorkerError(error) {
241
+ if (error instanceof AUNError) {
242
+ const payload = {
243
+ name: error.name,
244
+ message: error.message,
245
+ code: error.code,
246
+ stringCode: error.stringCode,
247
+ data: error.data,
248
+ retryable: error.retryable,
249
+ traceId: error.traceId,
250
+ };
251
+ if (error instanceof E2EEError) {
252
+ payload.localCode = error.localCode;
253
+ payload.closeReason = error.closeReason;
254
+ }
255
+ return payload;
256
+ }
257
+ if (error instanceof Error) {
258
+ return { name: error.name, message: error.message };
259
+ }
260
+ return { name: 'Error', message: String(error) };
261
+ }
262
+ function deserializeWorkerError(payload) {
263
+ const message = String(payload?.message ?? 'transport worker error');
264
+ const options = {
265
+ code: payload?.code,
266
+ stringCode: payload?.stringCode,
267
+ data: payload?.data ?? null,
268
+ retryable: payload?.retryable,
269
+ traceId: payload?.traceId,
270
+ };
271
+ switch (payload?.name) {
272
+ case 'ConnectionError': return new ConnectionError(message, options);
273
+ case 'TimeoutError': return new TimeoutError(message, options);
274
+ case 'AuthError': return new AuthError(message, options);
275
+ case 'PermissionError': return new PermissionError(message, options);
276
+ case 'ValidationError': return new ValidationError(message, options);
277
+ case 'NotFoundError': return new NotFoundError(message, options);
278
+ case 'RateLimitError': return new RateLimitError(message, options);
279
+ case 'StateError': return new StateError(message, options);
280
+ case 'SerializationError': return new SerializationError(message, options);
281
+ case 'SessionError': return new SessionError(message, options);
282
+ case 'VersionConflictError': return new VersionConflictError(message, options);
283
+ case 'GroupError': return new GroupError(message, options);
284
+ case 'GroupNotFoundError': return new GroupNotFoundError(message, options);
285
+ case 'GroupStateError': return new GroupStateError(message, options);
286
+ case 'E2EEDecryptFailedError': return new E2EEDecryptFailedError(message, options);
287
+ case 'E2EEDegradedError': return new E2EEDegradedError(message, options);
288
+ case 'E2EEError': return new E2EEError(message, {
289
+ ...options,
290
+ localCode: payload?.localCode,
291
+ closeReason: payload?.closeReason,
292
+ });
293
+ case 'CertificateRevokedError': return new CertificateRevokedError(message, options);
294
+ case 'IdentityConflictError': return new IdentityConflictError(message, options);
295
+ case 'ClientSignatureError': return new ClientSignatureError(message, options);
296
+ case 'AUNError': return new AUNError(message, options);
297
+ case 'TypeError': return new TypeError(message);
298
+ case 'Error': return new Error(message);
299
+ default:
300
+ if (payload?.code !== undefined) {
301
+ return mapRemoteError({
302
+ code: payload.code,
303
+ message,
304
+ data: payload.data ?? null,
305
+ });
306
+ }
307
+ return new Error(message);
308
+ }
309
+ }
310
+ function executeShortRpcEnvelope(url, method, params, signal) {
311
+ return new Promise((resolve, reject) => {
312
+ if (signal.aborted) {
313
+ reject(new AuthError('short RPC cancelled'));
314
+ return;
315
+ }
316
+ const requestId = `pre-${method}`;
317
+ const payload = JSON.stringify({ jsonrpc: '2.0', id: requestId, method, params });
318
+ const ws = new WebSocket(url, { handshakeTimeout: 5_000, rejectUnauthorized: false });
319
+ let opened = false;
320
+ let challengeReceived = false;
321
+ let settled = false;
322
+ let timer = null;
323
+ const cleanup = () => {
324
+ if (timer !== null)
325
+ clearTimeout(timer);
326
+ signal.removeEventListener('abort', onAbort);
327
+ ws.off('open', onOpen);
328
+ ws.off('message', onMessage);
329
+ ws.off('error', onError);
330
+ ws.off('close', onClose);
331
+ };
332
+ const finish = (callback) => {
333
+ if (settled)
334
+ return;
335
+ settled = true;
336
+ cleanup();
337
+ try {
338
+ ws.close();
339
+ }
340
+ catch { /* ignore */ }
341
+ callback();
342
+ };
343
+ const fail = (error) => finish(() => reject(error));
344
+ const onOpen = () => {
345
+ opened = true;
346
+ timer = setTimeout(() => fail(new AuthError(`shortRpc timeout: ${method}`)), 15_000);
347
+ };
348
+ const onAbort = () => {
349
+ ws.once('error', () => { });
350
+ try {
351
+ ws.terminate();
352
+ }
353
+ catch { /* ignore */ }
354
+ fail(new AuthError('short RPC cancelled'));
355
+ };
356
+ const onError = (error) => fail(new AuthError(opened ? `websocket error: ${error.message}` : `websocket connect failed: ${error.message}`));
357
+ const onClose = () => fail(new AuthError(challengeReceived
358
+ ? `websocket closed before ${method} response`
359
+ : 'websocket closed before challenge'));
360
+ const onMessage = (data) => {
361
+ try {
362
+ const raw = Buffer.isBuffer(data)
363
+ ? data.toString('utf-8')
364
+ : data instanceof ArrayBuffer
365
+ ? Buffer.from(data).toString('utf-8')
366
+ : String(data);
367
+ const message = JSON.parse(raw);
368
+ if (!isJsonObject(message)) {
369
+ fail(new ValidationError(`invalid WebSocket frame before ${method}`));
370
+ return;
371
+ }
372
+ if (!challengeReceived) {
373
+ if (message.method !== 'challenge')
374
+ return;
375
+ challengeReceived = true;
376
+ ws.send(payload);
377
+ return;
378
+ }
379
+ if (message.id !== requestId)
380
+ return;
381
+ finish(() => resolve(message));
382
+ }
383
+ catch (error) {
384
+ fail(error instanceof Error ? error : new AuthError(String(error)));
385
+ }
386
+ };
387
+ ws.on('open', onOpen);
388
+ ws.on('message', onMessage);
389
+ ws.on('error', onError);
390
+ ws.on('close', onClose);
391
+ signal.addEventListener('abort', onAbort, { once: true });
392
+ if (signal.aborted)
393
+ onAbort();
394
+ });
395
+ }
396
+ export function shortRpcViaNetworkActor(url, method, params) {
397
+ if (!isMainThread || process.env.VITEST || process.env.AUN_TRANSPORT_LOCAL === '1') {
398
+ const controller = new AbortController();
399
+ return Object.assign(executeShortRpcEnvelope(url, method, params, controller.signal), {
400
+ cancel: () => controller.abort(),
401
+ });
402
+ }
403
+ const id = `short-rpc-${crypto.randomUUID()}`;
404
+ let worker = null;
405
+ let settled = false;
406
+ let resolveResult;
407
+ let rejectResult;
408
+ const promise = new Promise((resolve, reject) => {
409
+ resolveResult = resolve;
410
+ rejectResult = reject;
411
+ });
412
+ const cleanup = () => {
413
+ const current = worker;
414
+ worker = null;
415
+ if (!current)
416
+ return;
417
+ current.removeAllListeners();
418
+ void current.terminate().catch(() => { });
419
+ };
420
+ const resolveOnce = (value) => {
421
+ if (settled)
422
+ return;
423
+ settled = true;
424
+ cleanup();
425
+ resolveResult(value);
426
+ };
427
+ const rejectOnce = (error) => {
428
+ if (settled)
429
+ return;
430
+ settled = true;
431
+ cleanup();
432
+ rejectResult(error);
433
+ };
434
+ try {
435
+ worker = new Worker(new URL(import.meta.url), {
436
+ execArgv: process.execArgv.filter((arg) => !arg.startsWith('--input-type')),
437
+ workerData: { aunShortRpcWorker: true, id, url, method, params },
438
+ });
439
+ worker.on('message', (message) => {
440
+ if (message.type !== 'reply' || message.id !== id)
441
+ return;
442
+ if (message.ok && isJsonObject(message.result))
443
+ resolveOnce(message.result);
444
+ else
445
+ rejectOnce(deserializeWorkerError(message.error));
446
+ });
447
+ worker.on('error', (error) => rejectOnce(new AuthError(`short RPC worker error: ${error.message}`)));
448
+ worker.on('exit', (code) => {
449
+ if (!settled)
450
+ rejectOnce(new AuthError(`short RPC worker exited: ${code}`));
451
+ });
452
+ }
453
+ catch (error) {
454
+ rejectOnce(new AuthError(`short RPC worker unavailable: ${error instanceof Error ? error.message : String(error)}`));
455
+ }
456
+ return Object.assign(promise, {
457
+ cancel: () => rejectOnce(new AuthError('short RPC cancelled')),
458
+ });
459
+ }
239
460
  /**
240
461
  * WebSocket JSON-RPC 2.0 传输层
241
462
  */
@@ -273,6 +494,17 @@ export class RPCTransport {
273
494
  _traceMode = 'off';
274
495
  // Trace observer:observer(traceInfo) 在每次 RPC/事件携带 _trace 时调用
275
496
  _traceObserver = null;
497
+ // Network actor command bridge: transport state transitions are started in FIFO order.
498
+ _actorTail = Promise.resolve();
499
+ _actorBusy = false;
500
+ _worker = null;
501
+ _workerPending = new Map();
502
+ _workerSeq = 0;
503
+ _workerConnected = false;
504
+ _workerGeneration = null;
505
+ _workerConnecting = false;
506
+ _workerBufferedEvents = [];
507
+ _workerOptions;
276
508
  constructor(opts) {
277
509
  this._logger = opts.logger ?? _noopLogger;
278
510
  this._dispatcher = opts.eventDispatcher;
@@ -281,17 +513,262 @@ export class RPCTransport {
281
513
  this._onDisconnect = opts.onDisconnect ?? null;
282
514
  this._verifySsl = opts.verifySsl ?? true;
283
515
  this._dnsNet = opts.dnsNet ?? null;
516
+ this._workerOptions = { timeout: opts.timeout, verifySsl: opts.verifySsl };
517
+ this._dispatcher.subscribe('_transport.observer', (item) => this._dispatchObserver(item));
518
+ // 断线回调统一经过应用分发边界,避免 WebSocket close 处理器直接进入用户/客户端逻辑。
519
+ this._dispatcher.subscribe('_transport.disconnect', (item) => {
520
+ const event = item;
521
+ const callback = this._onDisconnect;
522
+ if (!callback || !event)
523
+ return;
524
+ try {
525
+ const result = callback(event.error ?? null, event.closeCode);
526
+ if (result && typeof result.then === 'function') {
527
+ return Promise.resolve(result).catch(() => {
528
+ this._logger.warn('disconnect callback error');
529
+ });
530
+ }
531
+ }
532
+ catch {
533
+ this._logger.warn('disconnect callback error');
534
+ }
535
+ });
536
+ if (isMainThread && !process.env.VITEST && process.env.AUN_TRANSPORT_LOCAL !== '1') {
537
+ this._startNetworkWorker(opts);
538
+ }
539
+ }
540
+ _startNetworkWorker(opts) {
541
+ try {
542
+ const worker = new Worker(new URL(import.meta.url), {
543
+ execArgv: process.execArgv.filter((arg) => !arg.startsWith('--input-type')),
544
+ workerData: {
545
+ aunTransportWorker: true,
546
+ timeout: opts.timeout ?? 10_000,
547
+ verifySsl: opts.verifySsl ?? true,
548
+ },
549
+ });
550
+ this._worker = worker;
551
+ worker.on('message', (message) => {
552
+ if (this._worker !== worker)
553
+ return;
554
+ if (message.type === 'event') {
555
+ this._handleWorkerEvent(message);
556
+ return;
557
+ }
558
+ if (!message.id)
559
+ return;
560
+ const pending = this._workerPending.get(message.id);
561
+ if (!pending)
562
+ return;
563
+ if (typeof message.generation === 'number'
564
+ && this._workerGeneration !== null
565
+ && pending.commandType !== 'connect'
566
+ && pending.commandType !== 'close'
567
+ && message.generation !== this._workerGeneration) {
568
+ this._workerPending.delete(message.id);
569
+ if (pending.timer !== null)
570
+ clearTimeout(pending.timer);
571
+ pending.reject(new ConnectionError('stale transport worker response'));
572
+ return;
573
+ }
574
+ this._workerPending.delete(message.id);
575
+ if (pending.timer !== null)
576
+ clearTimeout(pending.timer);
577
+ if (pending.commandType === 'connect' && message.ok && typeof message.generation === 'number') {
578
+ this._workerGeneration = message.generation;
579
+ }
580
+ if (message.ok)
581
+ pending.resolve(message.result);
582
+ else {
583
+ if (pending.commandType === 'connect') {
584
+ this._workerGeneration = null;
585
+ this._workerBufferedEvents = [];
586
+ }
587
+ pending.reject(deserializeWorkerError(message.error));
588
+ }
589
+ });
590
+ worker.on('error', (error) => {
591
+ if (this._worker !== worker)
592
+ return;
593
+ this._worker = null;
594
+ this._workerConnected = false;
595
+ this._workerConnecting = false;
596
+ this._workerGeneration = null;
597
+ this._workerBufferedEvents = [];
598
+ for (const pending of this._workerPending.values()) {
599
+ if (pending.timer !== null)
600
+ clearTimeout(pending.timer);
601
+ pending.reject(error);
602
+ }
603
+ this._workerPending.clear();
604
+ this._dispatcher.enqueue('connection.error', { error });
605
+ });
606
+ worker.on('exit', (code) => {
607
+ if (this._worker !== worker)
608
+ return;
609
+ this._worker = null;
610
+ this._workerConnected = false;
611
+ this._workerConnecting = false;
612
+ this._workerGeneration = null;
613
+ this._workerBufferedEvents = [];
614
+ const error = new ConnectionError(`transport worker exited: ${code}`);
615
+ for (const pending of this._workerPending.values()) {
616
+ if (pending.timer !== null)
617
+ clearTimeout(pending.timer);
618
+ pending.reject(error);
619
+ }
620
+ this._workerPending.clear();
621
+ if (code !== 0)
622
+ this._dispatcher.enqueue('connection.error', { error: new Error(`transport worker exited: ${code}`) });
623
+ });
624
+ }
625
+ catch (error) {
626
+ this._worker = null;
627
+ this._logger.warn(`transport worker unavailable; using local transport: ${error instanceof Error ? error.message : String(error)}`);
628
+ }
629
+ }
630
+ _ensureNetworkWorker() {
631
+ if (this._worker || !isMainThread || process.env.VITEST || process.env.AUN_TRANSPORT_LOCAL === '1')
632
+ return;
633
+ this._startNetworkWorker(this._workerOptions);
634
+ const worker = this._worker;
635
+ if (!worker)
636
+ return;
637
+ worker.postMessage({ type: 'set_timeout', value: this._timeout });
638
+ worker.postMessage({ type: 'set_connect_timeout', value: this._connectTimeout });
639
+ worker.postMessage({ type: 'set_trace_mode', value: this._traceMode });
640
+ }
641
+ _dnsFallbackSnapshot(url) {
642
+ if (!this._dnsNet)
643
+ return null;
644
+ try {
645
+ const hostname = new URL(url).hostname;
646
+ const cached = this._dnsNet.loadDnsCache(hostname);
647
+ return cached ? { hostname, ip: cached.ip, port: cached.port } : null;
648
+ }
649
+ catch {
650
+ return null;
651
+ }
652
+ }
653
+ _handleWorkerEvent(message) {
654
+ if (typeof message.generation !== 'number')
655
+ return;
656
+ if (this._workerConnecting) {
657
+ this._workerBufferedEvents.push(message);
658
+ return;
659
+ }
660
+ if (this._workerGeneration === null || message.generation !== this._workerGeneration)
661
+ return;
662
+ if (message.event === '_transport.observer') {
663
+ const item = message.payload;
664
+ const observer = item?.kind?.includes('trace') ? this._traceObserver : this._metaObserver;
665
+ if (observer && item?.payload)
666
+ this._dispatcher.enqueue('_transport.observer', {
667
+ kind: item.kind ?? 'observer', observer, payload: item.payload,
668
+ });
669
+ return;
670
+ }
671
+ if (message.event === '_transport.disconnect') {
672
+ this._workerConnected = false;
673
+ this._workerGeneration = null;
674
+ const payload = (message.payload && typeof message.payload === 'object')
675
+ ? { ...message.payload }
676
+ : {};
677
+ const workerError = payload.error;
678
+ if (workerError && typeof workerError === 'object') {
679
+ payload.error = new ConnectionError(String(workerError.message ?? 'transport disconnected'));
680
+ }
681
+ this._dispatcher.enqueue('_transport.disconnect', payload);
682
+ }
683
+ else if (message.event) {
684
+ this._dispatcher.enqueue(message.event, message.payload);
685
+ }
686
+ }
687
+ _flushWorkerEvents() {
688
+ const buffered = this._workerBufferedEvents;
689
+ this._workerBufferedEvents = [];
690
+ for (const message of buffered)
691
+ this._handleWorkerEvent(message);
692
+ }
693
+ _workerCommand(command) {
694
+ this._ensureNetworkWorker();
695
+ if (!this._worker)
696
+ return Promise.reject(new ConnectionError('transport worker unavailable'));
697
+ const worker = this._worker;
698
+ const id = `worker-${++this._workerSeq}`;
699
+ const timeoutMs = command.type === 'call'
700
+ ? null
701
+ : command.type === 'connect' ? this._connectTimeout + 1_000 : 30_000;
702
+ let posted = false;
703
+ const promise = new Promise((resolve, reject) => {
704
+ const timer = timeoutMs === null ? null : setTimeout(() => {
705
+ this._workerPending.delete(id);
706
+ if (posted) {
707
+ try {
708
+ worker.postMessage({ type: 'cancel', targetId: id });
709
+ }
710
+ catch { /* worker exit path */ }
711
+ }
712
+ reject(new TimeoutError(`rpc timeout: ${String(command.method ?? command.type)}`, { retryable: true }));
713
+ }, timeoutMs);
714
+ this._workerPending.set(id, { resolve: resolve, reject, timer, commandType: command.type });
715
+ const postCommand = () => {
716
+ if (!this._workerPending.has(id))
717
+ return;
718
+ try {
719
+ worker.postMessage({ ...command, id });
720
+ posted = true;
721
+ }
722
+ catch (error) {
723
+ this._workerPending.delete(id);
724
+ if (timer !== null)
725
+ clearTimeout(timer);
726
+ reject(error);
727
+ }
728
+ };
729
+ if (command.type === 'call')
730
+ queueMicrotask(postCommand);
731
+ else
732
+ postCommand();
733
+ });
734
+ return Object.assign(promise, {
735
+ cancel: () => {
736
+ const pending = this._workerPending.get(id);
737
+ if (!pending)
738
+ return;
739
+ this._workerPending.delete(id);
740
+ if (pending.timer !== null)
741
+ clearTimeout(pending.timer);
742
+ if (posted) {
743
+ try {
744
+ worker.postMessage({ type: 'cancel', targetId: id });
745
+ }
746
+ catch { /* worker exit path */ }
747
+ }
748
+ pending.reject(new TimeoutError(`rpc cancelled: ${String(command.method ?? command.type)}`, { retryable: true }));
749
+ },
750
+ });
284
751
  }
285
752
  /** 设置默认 RPC 超时(毫秒) */
286
753
  setTimeout(timeout) {
287
754
  this._timeout = timeout;
755
+ if (this._worker) {
756
+ this._worker.postMessage({ type: 'set_timeout', value: timeout });
757
+ return;
758
+ }
288
759
  }
289
760
  /** 设置 WebSocket 建连及 challenge 超时(毫秒)。 */
290
761
  setConnectTimeout(timeout) {
291
762
  this._connectTimeout = timeout;
763
+ if (this._worker) {
764
+ this._worker.postMessage({ type: 'set_connect_timeout', value: timeout });
765
+ return;
766
+ }
292
767
  }
293
768
  /** 当前是否仍有可用的 WebSocket。 */
294
769
  isConnected() {
770
+ if (this._worker)
771
+ return this._workerConnected;
295
772
  return !this._closed && this._ws !== null && this._ws.readyState === WebSocket.OPEN;
296
773
  }
297
774
  /**
@@ -309,11 +786,33 @@ export class RPCTransport {
309
786
  throw new ValidationError(`invalid trace mode: ${mode}, must be off/log/diag`);
310
787
  }
311
788
  this._traceMode = mode;
789
+ if (this._worker) {
790
+ this._worker.postMessage({ type: 'set_trace_mode', value: mode });
791
+ return;
792
+ }
312
793
  }
313
794
  /** 注册 trace observer;observer(traceInfo) 在每次 RPC/事件携带 _trace 时调用。 */
314
795
  setTraceObserver(observer) {
315
796
  this._traceObserver = observer;
316
797
  }
798
+ _queueObserver(kind, observer, payload) {
799
+ this._dispatcher.enqueue('_transport.observer', {
800
+ kind,
801
+ observer,
802
+ payload,
803
+ });
804
+ }
805
+ async _dispatchObserver(item) {
806
+ const observerEvent = item;
807
+ if (!observerEvent || typeof observerEvent.observer !== 'function')
808
+ return;
809
+ try {
810
+ await observerEvent.observer(observerEvent.payload);
811
+ }
812
+ catch (err) {
813
+ this._logger.debug(`${observerEvent.kind || 'observer'} raised: ${err instanceof Error ? err.message : String(err)}`);
814
+ }
815
+ }
317
816
  /** 获取上次连接的 challenge 消息 */
318
817
  get challenge() {
319
818
  return this._challenge;
@@ -334,11 +833,99 @@ export class RPCTransport {
334
833
  release();
335
834
  }
336
835
  }
836
+ _enqueueActor(operation) {
837
+ this._actorBusy = true;
838
+ const run = this._actorTail.then(async () => {
839
+ return await operation();
840
+ }, async () => {
841
+ return await operation();
842
+ });
843
+ const tail = run.then(() => undefined, () => undefined);
844
+ this._actorTail = tail;
845
+ void tail.then(() => {
846
+ if (this._actorTail === tail)
847
+ this._actorBusy = false;
848
+ });
849
+ return run;
850
+ }
851
+ /** 启动长生命周期 RPC,但不把 actor 队列占用到响应返回。 */
852
+ _enqueueActorStart(operation, cancellationError) {
853
+ let resolveResult;
854
+ let rejectResult;
855
+ const result = new Promise((resolve, reject) => {
856
+ resolveResult = resolve;
857
+ rejectResult = reject;
858
+ });
859
+ let cancel;
860
+ let cancelRequested = false;
861
+ const launch = () => {
862
+ if (cancelRequested)
863
+ return;
864
+ try {
865
+ const inner = operation();
866
+ cancel = inner.cancel;
867
+ inner.then(resolveResult, rejectResult);
868
+ }
869
+ catch (err) {
870
+ rejectResult(err);
871
+ }
872
+ };
873
+ // 调用命令本身在当前 turn 立即启动(若有 close 等独占命令则排队)。
874
+ // 网络响应仍异步完成,避免改变既有 call() 的入队/并发语义。
875
+ if (this._actorBusy) {
876
+ const gate = this._actorTail.then(launch, launch);
877
+ const tail = gate.then(() => undefined, () => undefined);
878
+ this._actorTail = tail;
879
+ void tail.then(() => {
880
+ if (this._actorTail === tail)
881
+ this._actorBusy = false;
882
+ });
883
+ }
884
+ else {
885
+ launch();
886
+ }
887
+ return Object.assign(result, {
888
+ cancel: () => {
889
+ if (cancelRequested)
890
+ return;
891
+ cancelRequested = true;
892
+ if (cancel) {
893
+ cancel();
894
+ }
895
+ else {
896
+ rejectResult(cancellationError?.() ?? new TimeoutError('rpc cancelled', { retryable: true }));
897
+ }
898
+ },
899
+ });
900
+ }
337
901
  /**
338
902
  * 连接到 Gateway WebSocket 端点。
339
903
  * 返回初始 challenge 消息(如果有)。
340
904
  */
341
905
  async connect(url) {
906
+ this._ensureNetworkWorker();
907
+ if (this._worker) {
908
+ this._workerConnecting = true;
909
+ try {
910
+ const result = await this._workerCommand({
911
+ type: 'connect', url, dnsFallback: this._dnsFallbackSnapshot(url),
912
+ });
913
+ this._workerConnected = true;
914
+ this._workerConnecting = false;
915
+ this._flushWorkerEvents();
916
+ return result;
917
+ }
918
+ catch (error) {
919
+ this._workerConnecting = false;
920
+ this._workerConnected = false;
921
+ this._workerGeneration = null;
922
+ this._workerBufferedEvents = [];
923
+ throw error;
924
+ }
925
+ }
926
+ return this._enqueueActorStart(() => this._connectLocal(url));
927
+ }
928
+ async _connectLocal(url) {
342
929
  const tStart = Date.now();
343
930
  this._logger.debug(`connect enter: url=${url}`);
344
931
  // 只串行化清理和监听器安装;后续 connect 可立即取消本次握手。
@@ -568,6 +1155,31 @@ export class RPCTransport {
568
1155
  }
569
1156
  /** 关闭连接 */
570
1157
  async close() {
1158
+ if (this._worker) {
1159
+ const worker = this._worker;
1160
+ try {
1161
+ await this._workerCommand({ type: 'close' });
1162
+ }
1163
+ finally {
1164
+ if (this._worker === worker)
1165
+ this._worker = null;
1166
+ this._workerConnected = false;
1167
+ this._workerConnecting = false;
1168
+ this._workerGeneration = null;
1169
+ this._workerBufferedEvents = [];
1170
+ for (const pending of this._workerPending.values()) {
1171
+ if (pending.timer !== null)
1172
+ clearTimeout(pending.timer);
1173
+ pending.reject(new ConnectionError('transport closed'));
1174
+ }
1175
+ this._workerPending.clear();
1176
+ await worker.terminate();
1177
+ }
1178
+ return;
1179
+ }
1180
+ await this._enqueueActor(() => this._closeLocal());
1181
+ }
1182
+ async _closeLocal() {
571
1183
  await this._withConnectionSetup(() => this._closeUnlocked());
572
1184
  }
573
1185
  /** 已持有连接建立串行权时关闭当前 WebSocket。 */
@@ -652,7 +1264,13 @@ export class RPCTransport {
652
1264
  /**
653
1265
  * 发送 JSON-RPC 2.0 请求并等待响应。
654
1266
  */
655
- async call(method, params, timeout, trace, background = false) {
1267
+ call(method, params, timeout, trace, background = false) {
1268
+ if (this._worker) {
1269
+ return this._workerCommand({ type: 'call', method, params, timeout, trace, background });
1270
+ }
1271
+ return this._enqueueActorStart(() => this._callLocal(method, params, timeout, trace, background), () => new TimeoutError(`rpc cancelled: ${method}`, { retryable: true }));
1272
+ }
1273
+ _callLocal(method, params, timeout, trace, background = false) {
656
1274
  if (this._closed || !this._ws) {
657
1275
  const suffix = this._lastCloseCode !== null ? `: close code ${this._lastCloseCode}` : '';
658
1276
  throw new ConnectionError(`transport not connected${suffix}`, {
@@ -721,12 +1339,7 @@ export class RPCTransport {
721
1339
  if (this._metaObserver !== null) {
722
1340
  const meta = response._meta;
723
1341
  if (meta !== null && typeof meta === 'object' && !Array.isArray(meta)) {
724
- try {
725
- this._metaObserver(meta);
726
- }
727
- catch (err) {
728
- this._logger.debug(`meta_observer raised: ${err instanceof Error ? err.message : String(err)}`);
729
- }
1342
+ this._queueObserver('meta_observer', this._metaObserver, meta);
730
1343
  }
731
1344
  }
732
1345
  // 处理 success 路径的 _trace
@@ -779,6 +1392,13 @@ export class RPCTransport {
779
1392
  }
780
1393
  /** 发送 JSON-RPC 2.0 Notification,不分配 id,也不等待响应。 */
781
1394
  async notify(method, params) {
1395
+ if (this._worker) {
1396
+ await this._workerCommand({ type: 'notify', method, params });
1397
+ return;
1398
+ }
1399
+ return this._enqueueActorStart(() => this._notifyLocal(method, params));
1400
+ }
1401
+ async _notifyLocal(method, params) {
782
1402
  if (this._closed || !this._ws) {
783
1403
  const suffix = this._lastCloseCode !== null ? `: close code ${this._lastCloseCode}` : '';
784
1404
  throw new ConnectionError(`transport not connected${suffix}`, {
@@ -965,7 +1585,9 @@ export class RPCTransport {
965
1585
  const enriched = { ...respTrace, spans };
966
1586
  this._logger.info(traceDisplay(method, status, elapsedMs, respTrace, spans));
967
1587
  if (this._traceObserver !== null) {
968
- this._traceObserver({ type: 'rpc', method, trace: enriched, status, duration_ms: elapsedMs });
1588
+ this._queueObserver('trace_observer', this._traceObserver, {
1589
+ type: 'rpc', method, trace: enriched, status, duration_ms: elapsedMs,
1590
+ });
969
1591
  }
970
1592
  }
971
1593
  catch (err) {
@@ -985,7 +1607,7 @@ export class RPCTransport {
985
1607
  }
986
1608
  catch (err) {
987
1609
  // 解析异常,发布错误事件
988
- this._dispatcher.publish('connection.error', {
1610
+ this._dispatcher.enqueue('connection.error', {
989
1611
  error: err instanceof Error ? err : String(err),
990
1612
  });
991
1613
  }
@@ -1018,8 +1640,10 @@ export class RPCTransport {
1018
1640
  }
1019
1641
  this._backgroundRpcQueue = [];
1020
1642
  if (!wasClosed && this._onDisconnect) {
1021
- const cb = this._onDisconnect;
1022
- Promise.resolve(cb(null, code)).catch(() => this._logger.warn('disconnect callback error'));
1643
+ this._dispatcher.enqueue('_transport.disconnect', {
1644
+ error: null,
1645
+ closeCode: code,
1646
+ });
1023
1647
  }
1024
1648
  });
1025
1649
  ws.on('error', (err) => {
@@ -1027,7 +1651,7 @@ export class RPCTransport {
1027
1651
  return;
1028
1652
  if (!this._closed) {
1029
1653
  this._logger.error(`WebSocket error: ${err.message}`);
1030
- this._dispatcher.publish('connection.error', { error: err });
1654
+ this._dispatcher.enqueue('connection.error', { error: err });
1031
1655
  }
1032
1656
  });
1033
1657
  this._startWebSocketHeartbeat(ws);
@@ -1136,7 +1760,7 @@ export class RPCTransport {
1136
1760
  if (method === 'challenge') {
1137
1761
  this._challenge = message;
1138
1762
  this._logger.debug('challenge received');
1139
- this._dispatcher.publish('connection.challenge', message.params ?? {});
1763
+ this._dispatcher.enqueue('connection.challenge', message.params ?? {});
1140
1764
  return;
1141
1765
  }
1142
1766
  // 事件消息(event/ 前缀)
@@ -1146,12 +1770,7 @@ export class RPCTransport {
1146
1770
  this._logger.debug(`event recv: event=${sdkEvent} ${summarizeDict(message.params, DIAG_RESULT_FIELDS)}`);
1147
1771
  const meta = message._meta;
1148
1772
  if (this._metaObserver !== null && meta !== null && typeof meta === 'object' && !Array.isArray(meta)) {
1149
- try {
1150
- this._metaObserver(meta);
1151
- }
1152
- catch (err) {
1153
- this._logger.debug(`event meta_observer raised: ${err instanceof Error ? err.message : String(err)}`);
1154
- }
1773
+ this._queueObserver('event meta_observer', this._metaObserver, meta);
1155
1774
  }
1156
1775
  // 提取事件中的 _trace 并回调 observer,然后从 params 中剥离
1157
1776
  const params = (message.params ?? {});
@@ -1160,10 +1779,9 @@ export class RPCTransport {
1160
1779
  delete params._trace;
1161
1780
  if (eventTrace && typeof eventTrace === 'object' && !Array.isArray(eventTrace)) {
1162
1781
  if (this._traceObserver !== null) {
1163
- try {
1164
- this._traceObserver({ type: 'event', event: sdkEvent, trace: eventTrace });
1165
- }
1166
- catch { /* observer 抛错被吞 */ }
1782
+ this._queueObserver('trace_observer', this._traceObserver, {
1783
+ type: 'event', event: sdkEvent, trace: eventTrace,
1784
+ });
1167
1785
  }
1168
1786
  const traceObj = eventTrace;
1169
1787
  this._logger.info(`[trace=${String(traceObj.trace_id ?? '')}] event_recv event=${sdkEvent}`);
@@ -1171,23 +1789,18 @@ export class RPCTransport {
1171
1789
  }
1172
1790
  // 发布为 _raw.{event},由 AUNClient 处理后再发布用户可见的事件
1173
1791
  if (sdkEvent.startsWith('app.')) {
1174
- this._dispatcher.publish(sdkEvent, params);
1792
+ this._dispatcher.enqueue(sdkEvent, params);
1175
1793
  return;
1176
1794
  }
1177
- this._dispatcher.publish(`_raw.${sdkEvent}`, params);
1795
+ this._dispatcher.enqueue(`_raw.${sdkEvent}`, params);
1178
1796
  return;
1179
1797
  }
1180
1798
  // 其他通知
1181
1799
  const meta = message._meta;
1182
1800
  if (this._metaObserver !== null && meta !== null && typeof meta === 'object' && !Array.isArray(meta)) {
1183
- try {
1184
- this._metaObserver(meta);
1185
- }
1186
- catch (err) {
1187
- this._logger.debug(`notification meta_observer raised: ${err instanceof Error ? err.message : String(err)}`);
1188
- }
1801
+ this._queueObserver('notification meta_observer', this._metaObserver, meta);
1189
1802
  }
1190
- this._dispatcher.publish('notification', message);
1803
+ this._dispatcher.enqueue('notification', message);
1191
1804
  }
1192
1805
  /** 解码 WebSocket 消息为 JSON 对象 */
1193
1806
  _decodeMessage(raw) {
@@ -1222,4 +1835,116 @@ export class RPCTransport {
1222
1835
  }
1223
1836
  }
1224
1837
  }
1838
+ if (!isMainThread && Boolean(workerData?.aunShortRpcWorker) && parentPort) {
1839
+ const command = workerData;
1840
+ const controller = new AbortController();
1841
+ void executeShortRpcEnvelope(command.url, command.method, command.params, controller.signal)
1842
+ .then((result) => parentPort.postMessage({
1843
+ type: 'reply', id: command.id, ok: true, result,
1844
+ }))
1845
+ .catch((error) => parentPort.postMessage({
1846
+ type: 'reply', id: command.id, ok: false, error: serializeWorkerError(error),
1847
+ }))
1848
+ .finally(() => parentPort.close());
1849
+ }
1850
+ if (!isMainThread && Boolean(workerData?.aunTransportWorker) && parentPort) {
1851
+ let workerActor = null;
1852
+ const bridgeGeneration = () => Number(workerActor?._connectionGeneration ?? 0);
1853
+ const workerDispatcher = {
1854
+ enqueue(event, payload) {
1855
+ if (event === '_transport.observer') {
1856
+ const item = payload;
1857
+ parentPort.postMessage({ type: 'event', generation: bridgeGeneration(), event, payload: {
1858
+ kind: item.kind ?? 'observer', payload: item.payload ?? {},
1859
+ } });
1860
+ return;
1861
+ }
1862
+ parentPort.postMessage({ type: 'event', generation: bridgeGeneration(), event, payload });
1863
+ },
1864
+ subscribe() { return { unsubscribe() { } }; },
1865
+ };
1866
+ const actor = new RPCTransport({
1867
+ eventDispatcher: workerDispatcher,
1868
+ timeout: Number(workerData.timeout ?? 10_000),
1869
+ verifySsl: Boolean(workerData.verifySsl ?? true),
1870
+ // Worker 内只让 close handler 经 workerDispatcher 转发一次,主线程负责执行真实回调。
1871
+ onDisconnect: () => { },
1872
+ });
1873
+ workerActor = actor;
1874
+ actor.setMetaObserver((meta) => workerDispatcher.enqueue('_transport.observer', {
1875
+ kind: 'meta_observer', payload: meta,
1876
+ }));
1877
+ actor.setTraceObserver((trace) => workerDispatcher.enqueue('_transport.observer', {
1878
+ kind: 'trace_observer', payload: trace,
1879
+ }));
1880
+ const active = new Map();
1881
+ let dnsFallback = null;
1882
+ actor._dnsNet = {
1883
+ loadDnsCache(hostname) {
1884
+ return dnsFallback?.hostname === hostname ? { ip: dnsFallback.ip, port: dnsFallback.port } : null;
1885
+ },
1886
+ };
1887
+ parentPort.on('message', async (command) => {
1888
+ const id = command.id;
1889
+ try {
1890
+ if (command.type === 'cancel') {
1891
+ const pending = active.get(String(command.targetId ?? ''));
1892
+ pending?.cancel?.();
1893
+ return;
1894
+ }
1895
+ let result;
1896
+ switch (command.type) {
1897
+ case 'connect': {
1898
+ dnsFallback = command.dnsFallback ?? null;
1899
+ const request = actor.connect(String(command.url ?? ''));
1900
+ if (id)
1901
+ active.set(id, { cancel: request.cancel ?? (() => { void actor.close().catch(() => { }); }) });
1902
+ result = await request;
1903
+ if (id)
1904
+ active.delete(id);
1905
+ break;
1906
+ }
1907
+ case 'call': {
1908
+ const request = actor.call(String(command.method ?? ''), command.params ?? undefined, command.timeout, command.trace, Boolean(command.background));
1909
+ if (id)
1910
+ active.set(id, request);
1911
+ result = await request;
1912
+ if (id)
1913
+ active.delete(id);
1914
+ break;
1915
+ }
1916
+ case 'notify':
1917
+ result = await actor.notify(String(command.method ?? ''), command.params);
1918
+ break;
1919
+ case 'close':
1920
+ result = await actor.close();
1921
+ break;
1922
+ case 'set_timeout':
1923
+ actor.setTimeout(Number(command.value));
1924
+ return;
1925
+ case 'set_connect_timeout':
1926
+ actor.setConnectTimeout(Number(command.value));
1927
+ return;
1928
+ case 'set_trace_mode':
1929
+ actor.setTraceMode(String(command.value ?? 'off'));
1930
+ return;
1931
+ default: return;
1932
+ }
1933
+ if (id)
1934
+ parentPort.postMessage({ type: 'reply', id, generation: bridgeGeneration(), ok: true, result });
1935
+ }
1936
+ catch (error) {
1937
+ if (id)
1938
+ active.delete(id);
1939
+ if (id)
1940
+ parentPort.postMessage({
1941
+ type: 'reply',
1942
+ id,
1943
+ generation: bridgeGeneration(),
1944
+ ok: false,
1945
+ error: serializeWorkerError(error),
1946
+ });
1947
+ }
1948
+ });
1949
+ }
1225
1950
  //# sourceMappingURL=transport.js.map