@agentunion/fastaun 0.5.9 → 0.5.11

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 (43) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/_packed_docs/CHANGELOG.md +64 -0
  3. package/_packed_docs/INDEX.md +3 -3
  4. package/_packed_docs/KITE_DOCS_GUIDE.md +1 -1
  5. package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +6 -5
  6. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +102 -8
  7. package/_packed_docs/sdk/09-group-rpc-manual.md +18 -3
  8. package/_packed_docs/sdk/09-message-rpc-manual.md +34 -10
  9. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +3 -2
  10. package/_packed_docs/sdk/INDEX.md +2 -1
  11. package/dist/agent-md.js +5 -1
  12. package/dist/agent-md.js.map +1 -1
  13. package/dist/auth.d.ts +2 -1
  14. package/dist/auth.js +30 -44
  15. package/dist/auth.js.map +1 -1
  16. package/dist/client/delivery.d.ts +39 -9
  17. package/dist/client/delivery.js +407 -73
  18. package/dist/client/delivery.js.map +1 -1
  19. package/dist/client/group-state.js +6 -6
  20. package/dist/client/group-state.js.map +1 -1
  21. package/dist/client/lifecycle.js +5 -14
  22. package/dist/client/lifecycle.js.map +1 -1
  23. package/dist/client/rpc-pipeline.d.ts +4 -0
  24. package/dist/client/rpc-pipeline.js +51 -5
  25. package/dist/client/rpc-pipeline.js.map +1 -1
  26. package/dist/client/v2-e2ee.d.ts +3 -0
  27. package/dist/client/v2-e2ee.js +236 -59
  28. package/dist/client/v2-e2ee.js.map +1 -1
  29. package/dist/client.d.ts +1 -0
  30. package/dist/client.js +84 -30
  31. package/dist/client.js.map +1 -1
  32. package/dist/events.d.ts +23 -8
  33. package/dist/events.js +120 -26
  34. package/dist/events.js.map +1 -1
  35. package/dist/register-flow.js +15 -111
  36. package/dist/register-flow.js.map +1 -1
  37. package/dist/transport.d.ts +36 -3
  38. package/dist/transport.js +855 -33
  39. package/dist/transport.js.map +1 -1
  40. package/dist/version.d.ts +1 -1
  41. package/dist/version.js +1 -1
  42. package/dist/version.js.map +1 -1
  43. package/package.json +1 -1
package/dist/transport.js CHANGED
@@ -9,11 +9,14 @@
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;
16
17
  const MAX_BACKGROUND_RPC_INFLIGHT = 8;
18
+ const WORKER_START_TIMEOUT_MS = 2_000;
19
+ const SHORT_RPC_WORKER_START_TIMEOUT_MS = 2_000;
17
20
  const _noopLogger = {
18
21
  error: () => { },
19
22
  warn: () => { },
@@ -236,6 +239,264 @@ const EVENT_NAME_MAP = {
236
239
  'group.message_recalled': 'group.message_recalled',
237
240
  'storage.object_changed': 'storage.object_changed',
238
241
  };
242
+ function serializeWorkerError(error) {
243
+ if (error instanceof AUNError) {
244
+ const payload = {
245
+ name: error.name,
246
+ message: error.message,
247
+ code: error.code,
248
+ stringCode: error.stringCode,
249
+ data: error.data,
250
+ retryable: error.retryable,
251
+ traceId: error.traceId,
252
+ };
253
+ if (error instanceof E2EEError) {
254
+ payload.localCode = error.localCode;
255
+ payload.closeReason = error.closeReason;
256
+ }
257
+ return payload;
258
+ }
259
+ if (error instanceof Error) {
260
+ return { name: error.name, message: error.message };
261
+ }
262
+ return { name: 'Error', message: String(error) };
263
+ }
264
+ function deserializeWorkerError(payload) {
265
+ const message = String(payload?.message ?? 'transport worker error');
266
+ const options = {
267
+ code: payload?.code,
268
+ stringCode: payload?.stringCode,
269
+ data: payload?.data ?? null,
270
+ retryable: payload?.retryable,
271
+ traceId: payload?.traceId,
272
+ };
273
+ switch (payload?.name) {
274
+ case 'ConnectionError': return new ConnectionError(message, options);
275
+ case 'TimeoutError': return new TimeoutError(message, options);
276
+ case 'AuthError': return new AuthError(message, options);
277
+ case 'PermissionError': return new PermissionError(message, options);
278
+ case 'ValidationError': return new ValidationError(message, options);
279
+ case 'NotFoundError': return new NotFoundError(message, options);
280
+ case 'RateLimitError': return new RateLimitError(message, options);
281
+ case 'StateError': return new StateError(message, options);
282
+ case 'SerializationError': return new SerializationError(message, options);
283
+ case 'SessionError': return new SessionError(message, options);
284
+ case 'VersionConflictError': return new VersionConflictError(message, options);
285
+ case 'GroupError': return new GroupError(message, options);
286
+ case 'GroupNotFoundError': return new GroupNotFoundError(message, options);
287
+ case 'GroupStateError': return new GroupStateError(message, options);
288
+ case 'E2EEDecryptFailedError': return new E2EEDecryptFailedError(message, options);
289
+ case 'E2EEDegradedError': return new E2EEDegradedError(message, options);
290
+ case 'E2EEError': return new E2EEError(message, {
291
+ ...options,
292
+ localCode: payload?.localCode,
293
+ closeReason: payload?.closeReason,
294
+ });
295
+ case 'CertificateRevokedError': return new CertificateRevokedError(message, options);
296
+ case 'IdentityConflictError': return new IdentityConflictError(message, options);
297
+ case 'ClientSignatureError': return new ClientSignatureError(message, options);
298
+ case 'AUNError': return new AUNError(message, options);
299
+ case 'TypeError': return new TypeError(message);
300
+ case 'Error': return new Error(message);
301
+ default:
302
+ if (payload?.code !== undefined) {
303
+ return mapRemoteError({
304
+ code: payload.code,
305
+ message,
306
+ data: payload.data ?? null,
307
+ });
308
+ }
309
+ return new Error(message);
310
+ }
311
+ }
312
+ function executeShortRpcEnvelope(url, method, params, signal) {
313
+ return new Promise((resolve, reject) => {
314
+ if (signal.aborted) {
315
+ reject(new AuthError('short RPC cancelled'));
316
+ return;
317
+ }
318
+ const requestId = `pre-${method}`;
319
+ const payload = JSON.stringify({ jsonrpc: '2.0', id: requestId, method, params });
320
+ const ws = new WebSocket(url, { handshakeTimeout: 5_000, rejectUnauthorized: false });
321
+ let opened = false;
322
+ let challengeReceived = false;
323
+ let settled = false;
324
+ let timer = null;
325
+ const cleanup = () => {
326
+ if (timer !== null)
327
+ clearTimeout(timer);
328
+ signal.removeEventListener('abort', onAbort);
329
+ ws.off('open', onOpen);
330
+ ws.off('message', onMessage);
331
+ ws.off('error', onError);
332
+ ws.off('close', onClose);
333
+ };
334
+ const finish = (callback) => {
335
+ if (settled)
336
+ return;
337
+ settled = true;
338
+ cleanup();
339
+ try {
340
+ ws.close();
341
+ }
342
+ catch { /* ignore */ }
343
+ callback();
344
+ };
345
+ const fail = (error) => finish(() => reject(error));
346
+ const onOpen = () => {
347
+ opened = true;
348
+ timer = setTimeout(() => fail(new AuthError(`shortRpc timeout: ${method}`)), 15_000);
349
+ };
350
+ const onAbort = () => {
351
+ ws.once('error', () => { });
352
+ try {
353
+ ws.terminate();
354
+ }
355
+ catch { /* ignore */ }
356
+ fail(new AuthError('short RPC cancelled'));
357
+ };
358
+ const onError = (error) => fail(new AuthError(opened ? `websocket error: ${error.message}` : `websocket connect failed: ${error.message}`));
359
+ const onClose = () => fail(new AuthError(challengeReceived
360
+ ? `websocket closed before ${method} response`
361
+ : 'websocket closed before challenge'));
362
+ const onMessage = (data) => {
363
+ try {
364
+ const raw = Buffer.isBuffer(data)
365
+ ? data.toString('utf-8')
366
+ : data instanceof ArrayBuffer
367
+ ? Buffer.from(data).toString('utf-8')
368
+ : String(data);
369
+ const message = JSON.parse(raw);
370
+ if (!isJsonObject(message)) {
371
+ fail(new ValidationError(`invalid WebSocket frame before ${method}`));
372
+ return;
373
+ }
374
+ if (!challengeReceived) {
375
+ if (message.method !== 'challenge')
376
+ return;
377
+ challengeReceived = true;
378
+ ws.send(payload);
379
+ return;
380
+ }
381
+ if (message.id !== requestId)
382
+ return;
383
+ finish(() => resolve(message));
384
+ }
385
+ catch (error) {
386
+ fail(error instanceof Error ? error : new AuthError(String(error)));
387
+ }
388
+ };
389
+ ws.on('open', onOpen);
390
+ ws.on('message', onMessage);
391
+ ws.on('error', onError);
392
+ ws.on('close', onClose);
393
+ signal.addEventListener('abort', onAbort, { once: true });
394
+ if (signal.aborted)
395
+ onAbort();
396
+ });
397
+ }
398
+ export function shortRpcViaNetworkActor(url, method, params) {
399
+ if (!isMainThread || process.env.VITEST || process.env.AUN_TRANSPORT_LOCAL === '1') {
400
+ const controller = new AbortController();
401
+ return Object.assign(executeShortRpcEnvelope(url, method, params, controller.signal), {
402
+ cancel: () => controller.abort(),
403
+ });
404
+ }
405
+ const id = `short-rpc-${crypto.randomUUID()}`;
406
+ let worker = null;
407
+ let workerReady = false;
408
+ let workerReadyTimer = null;
409
+ let localController = null;
410
+ let settled = false;
411
+ let resolveResult;
412
+ let rejectResult;
413
+ const promise = new Promise((resolve, reject) => {
414
+ resolveResult = resolve;
415
+ rejectResult = reject;
416
+ });
417
+ const cleanup = () => {
418
+ if (workerReadyTimer !== null) {
419
+ clearTimeout(workerReadyTimer);
420
+ workerReadyTimer = null;
421
+ }
422
+ const current = worker;
423
+ worker = null;
424
+ if (!current)
425
+ return;
426
+ current.removeAllListeners();
427
+ void current.terminate().catch(() => { });
428
+ };
429
+ const resolveOnce = (value) => {
430
+ if (settled)
431
+ return;
432
+ settled = true;
433
+ cleanup();
434
+ resolveResult(value);
435
+ };
436
+ const rejectOnce = (error) => {
437
+ if (settled)
438
+ return;
439
+ settled = true;
440
+ cleanup();
441
+ rejectResult(error);
442
+ };
443
+ const fallbackToLocal = () => {
444
+ if (settled || localController)
445
+ return;
446
+ cleanup();
447
+ localController = new AbortController();
448
+ void executeShortRpcEnvelope(url, method, params, localController.signal).then(resolveOnce, rejectOnce);
449
+ };
450
+ try {
451
+ worker = new Worker(new URL(import.meta.url), {
452
+ execArgv: process.execArgv.filter((arg) => !arg.startsWith('--input-type')),
453
+ workerData: { aunShortRpcWorker: true, id, url, method, params },
454
+ });
455
+ workerReadyTimer = setTimeout(() => {
456
+ if (!workerReady && !settled)
457
+ fallbackToLocal();
458
+ }, SHORT_RPC_WORKER_START_TIMEOUT_MS);
459
+ worker.on('message', (message) => {
460
+ if (message.type === 'ready' && message.id === id) {
461
+ workerReady = true;
462
+ if (workerReadyTimer !== null) {
463
+ clearTimeout(workerReadyTimer);
464
+ workerReadyTimer = null;
465
+ }
466
+ return;
467
+ }
468
+ if (message.type !== 'reply' || message.id !== id)
469
+ return;
470
+ if (message.ok && isJsonObject(message.result))
471
+ resolveOnce(message.result);
472
+ else
473
+ rejectOnce(deserializeWorkerError(message.error));
474
+ });
475
+ worker.on('error', (error) => {
476
+ if (!workerReady)
477
+ fallbackToLocal();
478
+ else
479
+ rejectOnce(new AuthError(`short RPC worker error: ${error.message}`));
480
+ });
481
+ worker.on('exit', (code) => {
482
+ if (settled)
483
+ return;
484
+ if (!workerReady)
485
+ fallbackToLocal();
486
+ else
487
+ rejectOnce(new AuthError(`short RPC worker exited: ${code}`));
488
+ });
489
+ }
490
+ catch (error) {
491
+ fallbackToLocal();
492
+ }
493
+ return Object.assign(promise, {
494
+ cancel: () => {
495
+ localController?.abort();
496
+ rejectOnce(new AuthError('short RPC cancelled'));
497
+ },
498
+ });
499
+ }
239
500
  /**
240
501
  * WebSocket JSON-RPC 2.0 传输层
241
502
  */
@@ -273,6 +534,20 @@ export class RPCTransport {
273
534
  _traceMode = 'off';
274
535
  // Trace observer:observer(traceInfo) 在每次 RPC/事件携带 _trace 时调用
275
536
  _traceObserver = null;
537
+ // Network actor command bridge: transport state transitions are started in FIFO order.
538
+ _actorTail = Promise.resolve();
539
+ _actorBusy = false;
540
+ _worker = null;
541
+ _workerReady = false;
542
+ _workerReadyTimer = null;
543
+ _workerDisabled = false;
544
+ _workerPending = new Map();
545
+ _workerSeq = 0;
546
+ _workerConnected = false;
547
+ _workerGeneration = null;
548
+ _workerConnecting = false;
549
+ _workerBufferedEvents = [];
550
+ _workerOptions;
276
551
  constructor(opts) {
277
552
  this._logger = opts.logger ?? _noopLogger;
278
553
  this._dispatcher = opts.eventDispatcher;
@@ -281,17 +556,305 @@ export class RPCTransport {
281
556
  this._onDisconnect = opts.onDisconnect ?? null;
282
557
  this._verifySsl = opts.verifySsl ?? true;
283
558
  this._dnsNet = opts.dnsNet ?? null;
559
+ this._workerOptions = { timeout: opts.timeout, verifySsl: opts.verifySsl };
560
+ this._dispatcher.subscribe('_transport.observer', (item) => this._dispatchObserver(item));
561
+ // 断线回调统一经过应用分发边界,避免 WebSocket close 处理器直接进入用户/客户端逻辑。
562
+ this._dispatcher.subscribe('_transport.disconnect', (item) => {
563
+ const event = item;
564
+ const callback = this._onDisconnect;
565
+ if (!callback || !event)
566
+ return;
567
+ try {
568
+ const result = callback(event.error ?? null, event.closeCode);
569
+ if (result && typeof result.then === 'function') {
570
+ return Promise.resolve(result).catch(() => {
571
+ this._logger.warn('disconnect callback error');
572
+ });
573
+ }
574
+ }
575
+ catch {
576
+ this._logger.warn('disconnect callback error');
577
+ }
578
+ });
579
+ if (isMainThread && !process.env.VITEST && process.env.AUN_TRANSPORT_LOCAL !== '1') {
580
+ this._startNetworkWorker(opts);
581
+ }
582
+ }
583
+ _startNetworkWorker(opts) {
584
+ if (this._workerDisabled)
585
+ return;
586
+ try {
587
+ const worker = new Worker(new URL(import.meta.url), {
588
+ execArgv: process.execArgv.filter((arg) => !arg.startsWith('--input-type')),
589
+ workerData: {
590
+ aunTransportWorker: true,
591
+ timeout: opts.timeout ?? 10_000,
592
+ verifySsl: opts.verifySsl ?? true,
593
+ },
594
+ });
595
+ this._worker = worker;
596
+ this._workerReady = false;
597
+ this._workerReadyTimer = setTimeout(() => {
598
+ if (this._worker === worker && !this._workerReady) {
599
+ this._disableNetworkWorker(worker, new ConnectionError('transport worker startup timeout'));
600
+ }
601
+ }, WORKER_START_TIMEOUT_MS);
602
+ worker.on('message', (message) => {
603
+ if (this._worker !== worker)
604
+ return;
605
+ if (message.type === 'ready') {
606
+ this._workerReady = true;
607
+ if (this._workerReadyTimer !== null) {
608
+ clearTimeout(this._workerReadyTimer);
609
+ this._workerReadyTimer = null;
610
+ }
611
+ return;
612
+ }
613
+ if (message.type === 'event') {
614
+ this._handleWorkerEvent(message);
615
+ return;
616
+ }
617
+ if (!message.id)
618
+ return;
619
+ const pending = this._workerPending.get(message.id);
620
+ if (!pending)
621
+ return;
622
+ if (typeof message.generation === 'number'
623
+ && this._workerGeneration !== null
624
+ && pending.commandType !== 'connect'
625
+ && pending.commandType !== 'close'
626
+ && message.generation !== this._workerGeneration) {
627
+ this._workerPending.delete(message.id);
628
+ if (pending.timer !== null)
629
+ clearTimeout(pending.timer);
630
+ pending.reject(new ConnectionError('stale transport worker response'));
631
+ return;
632
+ }
633
+ this._workerPending.delete(message.id);
634
+ if (pending.timer !== null)
635
+ clearTimeout(pending.timer);
636
+ if (pending.commandType === 'connect' && message.ok && typeof message.generation === 'number') {
637
+ this._workerGeneration = message.generation;
638
+ }
639
+ if (message.ok)
640
+ pending.resolve(message.result);
641
+ else {
642
+ if (pending.commandType === 'connect') {
643
+ this._workerGeneration = null;
644
+ this._workerBufferedEvents = [];
645
+ }
646
+ pending.reject(deserializeWorkerError(message.error));
647
+ }
648
+ });
649
+ worker.on('error', (error) => {
650
+ this._disableNetworkWorker(worker, error);
651
+ });
652
+ worker.on('exit', (code) => {
653
+ const error = new ConnectionError(`transport worker exited: ${code}`);
654
+ this._disableNetworkWorker(worker, error);
655
+ });
656
+ }
657
+ catch (error) {
658
+ this._worker = null;
659
+ this._workerReady = false;
660
+ if (this._workerReadyTimer !== null) {
661
+ clearTimeout(this._workerReadyTimer);
662
+ this._workerReadyTimer = null;
663
+ }
664
+ this._workerDisabled = true;
665
+ this._logger.warn(`transport worker unavailable; using local transport: ${error instanceof Error ? error.message : String(error)}`);
666
+ }
667
+ }
668
+ _disableNetworkWorker(worker, error) {
669
+ if (this._worker !== worker)
670
+ return;
671
+ const wasConnected = this._workerConnected;
672
+ this._worker = null;
673
+ this._workerReady = false;
674
+ if (this._workerReadyTimer !== null) {
675
+ clearTimeout(this._workerReadyTimer);
676
+ this._workerReadyTimer = null;
677
+ }
678
+ this._workerDisabled = true;
679
+ this._workerConnected = false;
680
+ this._workerConnecting = false;
681
+ this._workerGeneration = null;
682
+ this._workerBufferedEvents = [];
683
+ for (const pending of this._workerPending.values()) {
684
+ if (pending.timer !== null)
685
+ clearTimeout(pending.timer);
686
+ pending.reject(error);
687
+ }
688
+ this._workerPending.clear();
689
+ void worker.terminate().catch(() => { });
690
+ if (wasConnected) {
691
+ this._dispatcher.enqueue('connection.error', { error });
692
+ this._dispatcher.enqueue('_transport.disconnect', { error, closeCode: 1006 });
693
+ }
694
+ else {
695
+ this._logger.warn(`transport worker unavailable; using local transport: ${error.message}`);
696
+ }
697
+ }
698
+ _ensureNetworkWorker() {
699
+ if (this._worker || this._workerDisabled || !isMainThread || process.env.VITEST || process.env.AUN_TRANSPORT_LOCAL === '1')
700
+ return;
701
+ this._startNetworkWorker(this._workerOptions);
702
+ const worker = this._worker;
703
+ if (!worker)
704
+ return;
705
+ try {
706
+ worker.postMessage({ type: 'set_timeout', value: this._timeout });
707
+ worker.postMessage({ type: 'set_connect_timeout', value: this._connectTimeout });
708
+ worker.postMessage({ type: 'set_trace_mode', value: this._traceMode });
709
+ }
710
+ catch (error) {
711
+ this._disableNetworkWorker(worker, error instanceof Error ? error : new Error(String(error)));
712
+ }
713
+ }
714
+ _dnsFallbackSnapshot(url) {
715
+ if (!this._dnsNet)
716
+ return null;
717
+ try {
718
+ const hostname = new URL(url).hostname;
719
+ const cached = this._dnsNet.loadDnsCache(hostname);
720
+ return cached ? { hostname, ip: cached.ip, port: cached.port } : null;
721
+ }
722
+ catch {
723
+ return null;
724
+ }
725
+ }
726
+ _handleWorkerEvent(message) {
727
+ if (typeof message.generation !== 'number')
728
+ return;
729
+ if (this._workerConnecting) {
730
+ this._workerBufferedEvents.push(message);
731
+ return;
732
+ }
733
+ if (this._workerGeneration === null || message.generation !== this._workerGeneration)
734
+ return;
735
+ if (message.event === '_transport.observer') {
736
+ const item = message.payload;
737
+ const observer = item?.kind?.includes('trace') ? this._traceObserver : this._metaObserver;
738
+ if (observer && item?.payload)
739
+ this._dispatcher.enqueue('_transport.observer', {
740
+ kind: item.kind ?? 'observer', observer, payload: item.payload,
741
+ });
742
+ return;
743
+ }
744
+ if (message.event === '_transport.disconnect') {
745
+ this._workerConnected = false;
746
+ this._workerGeneration = null;
747
+ const payload = (message.payload && typeof message.payload === 'object')
748
+ ? { ...message.payload }
749
+ : {};
750
+ const workerError = payload.error;
751
+ if (workerError && typeof workerError === 'object') {
752
+ payload.error = new ConnectionError(String(workerError.message ?? 'transport disconnected'));
753
+ }
754
+ this._dispatcher.enqueue('_transport.disconnect', payload);
755
+ }
756
+ else if (message.event) {
757
+ this._dispatcher.enqueue(message.event, message.payload);
758
+ }
759
+ }
760
+ _flushWorkerEvents() {
761
+ const buffered = this._workerBufferedEvents;
762
+ this._workerBufferedEvents = [];
763
+ for (const message of buffered)
764
+ this._handleWorkerEvent(message);
765
+ }
766
+ _workerCommand(command) {
767
+ this._ensureNetworkWorker();
768
+ if (!this._worker)
769
+ return Promise.reject(new ConnectionError('transport worker unavailable'));
770
+ const worker = this._worker;
771
+ const id = `worker-${++this._workerSeq}`;
772
+ const timeoutMs = command.type === 'call'
773
+ ? null
774
+ : command.type === 'connect' ? this._connectTimeout + 1_000 : 30_000;
775
+ let posted = false;
776
+ const promise = new Promise((resolve, reject) => {
777
+ const timer = timeoutMs === null ? null : setTimeout(() => {
778
+ if (!this._workerReady && this._worker === worker) {
779
+ this._disableNetworkWorker(worker, new ConnectionError('transport worker startup timeout'));
780
+ return;
781
+ }
782
+ this._workerPending.delete(id);
783
+ if (posted) {
784
+ try {
785
+ worker.postMessage({ type: 'cancel', targetId: id });
786
+ }
787
+ catch { /* worker exit path */ }
788
+ }
789
+ reject(new TimeoutError(`rpc timeout: ${String(command.method ?? command.type)}`, { retryable: true }));
790
+ }, timeoutMs);
791
+ this._workerPending.set(id, { resolve: resolve, reject, timer, commandType: command.type });
792
+ const postCommand = () => {
793
+ if (!this._workerPending.has(id))
794
+ return;
795
+ try {
796
+ worker.postMessage({ ...command, id });
797
+ posted = true;
798
+ }
799
+ catch (error) {
800
+ this._disableNetworkWorker(worker, error instanceof Error ? error : new Error(String(error)));
801
+ }
802
+ };
803
+ if (command.type === 'call')
804
+ queueMicrotask(postCommand);
805
+ else
806
+ postCommand();
807
+ });
808
+ return Object.assign(promise, {
809
+ cancel: () => {
810
+ const pending = this._workerPending.get(id);
811
+ if (!pending)
812
+ return;
813
+ this._workerPending.delete(id);
814
+ if (pending.timer !== null)
815
+ clearTimeout(pending.timer);
816
+ if (posted) {
817
+ try {
818
+ worker.postMessage({ type: 'cancel', targetId: id });
819
+ }
820
+ catch { /* worker exit path */ }
821
+ }
822
+ pending.reject(new TimeoutError(`rpc cancelled: ${String(command.method ?? command.type)}`, { retryable: true }));
823
+ },
824
+ });
284
825
  }
285
826
  /** 设置默认 RPC 超时(毫秒) */
286
827
  setTimeout(timeout) {
287
828
  this._timeout = timeout;
829
+ if (this._worker) {
830
+ const worker = this._worker;
831
+ try {
832
+ worker.postMessage({ type: 'set_timeout', value: timeout });
833
+ }
834
+ catch (error) {
835
+ this._disableNetworkWorker(worker, error instanceof Error ? error : new Error(String(error)));
836
+ }
837
+ return;
838
+ }
288
839
  }
289
840
  /** 设置 WebSocket 建连及 challenge 超时(毫秒)。 */
290
841
  setConnectTimeout(timeout) {
291
842
  this._connectTimeout = timeout;
843
+ if (this._worker) {
844
+ const worker = this._worker;
845
+ try {
846
+ worker.postMessage({ type: 'set_connect_timeout', value: timeout });
847
+ }
848
+ catch (error) {
849
+ this._disableNetworkWorker(worker, error instanceof Error ? error : new Error(String(error)));
850
+ }
851
+ return;
852
+ }
292
853
  }
293
854
  /** 当前是否仍有可用的 WebSocket。 */
294
855
  isConnected() {
856
+ if (this._worker)
857
+ return this._workerConnected;
295
858
  return !this._closed && this._ws !== null && this._ws.readyState === WebSocket.OPEN;
296
859
  }
297
860
  /**
@@ -309,11 +872,39 @@ export class RPCTransport {
309
872
  throw new ValidationError(`invalid trace mode: ${mode}, must be off/log/diag`);
310
873
  }
311
874
  this._traceMode = mode;
875
+ if (this._worker) {
876
+ const worker = this._worker;
877
+ try {
878
+ worker.postMessage({ type: 'set_trace_mode', value: mode });
879
+ }
880
+ catch (error) {
881
+ this._disableNetworkWorker(worker, error instanceof Error ? error : new Error(String(error)));
882
+ }
883
+ return;
884
+ }
312
885
  }
313
886
  /** 注册 trace observer;observer(traceInfo) 在每次 RPC/事件携带 _trace 时调用。 */
314
887
  setTraceObserver(observer) {
315
888
  this._traceObserver = observer;
316
889
  }
890
+ _queueObserver(kind, observer, payload) {
891
+ this._dispatcher.enqueue('_transport.observer', {
892
+ kind,
893
+ observer,
894
+ payload,
895
+ });
896
+ }
897
+ async _dispatchObserver(item) {
898
+ const observerEvent = item;
899
+ if (!observerEvent || typeof observerEvent.observer !== 'function')
900
+ return;
901
+ try {
902
+ await observerEvent.observer(observerEvent.payload);
903
+ }
904
+ catch (err) {
905
+ this._logger.debug(`${observerEvent.kind || 'observer'} raised: ${err instanceof Error ? err.message : String(err)}`);
906
+ }
907
+ }
317
908
  /** 获取上次连接的 challenge 消息 */
318
909
  get challenge() {
319
910
  return this._challenge;
@@ -334,11 +925,102 @@ export class RPCTransport {
334
925
  release();
335
926
  }
336
927
  }
928
+ _enqueueActor(operation) {
929
+ this._actorBusy = true;
930
+ const run = this._actorTail.then(async () => {
931
+ return await operation();
932
+ }, async () => {
933
+ return await operation();
934
+ });
935
+ const tail = run.then(() => undefined, () => undefined);
936
+ this._actorTail = tail;
937
+ void tail.then(() => {
938
+ if (this._actorTail === tail)
939
+ this._actorBusy = false;
940
+ });
941
+ return run;
942
+ }
943
+ /** 启动长生命周期 RPC,但不把 actor 队列占用到响应返回。 */
944
+ _enqueueActorStart(operation, cancellationError) {
945
+ let resolveResult;
946
+ let rejectResult;
947
+ const result = new Promise((resolve, reject) => {
948
+ resolveResult = resolve;
949
+ rejectResult = reject;
950
+ });
951
+ let cancel;
952
+ let cancelRequested = false;
953
+ const launch = () => {
954
+ if (cancelRequested)
955
+ return;
956
+ try {
957
+ const inner = operation();
958
+ cancel = inner.cancel;
959
+ inner.then(resolveResult, rejectResult);
960
+ }
961
+ catch (err) {
962
+ rejectResult(err);
963
+ }
964
+ };
965
+ // 调用命令本身在当前 turn 立即启动(若有 close 等独占命令则排队)。
966
+ // 网络响应仍异步完成,避免改变既有 call() 的入队/并发语义。
967
+ if (this._actorBusy) {
968
+ const gate = this._actorTail.then(launch, launch);
969
+ const tail = gate.then(() => undefined, () => undefined);
970
+ this._actorTail = tail;
971
+ void tail.then(() => {
972
+ if (this._actorTail === tail)
973
+ this._actorBusy = false;
974
+ });
975
+ }
976
+ else {
977
+ launch();
978
+ }
979
+ return Object.assign(result, {
980
+ cancel: () => {
981
+ if (cancelRequested)
982
+ return;
983
+ cancelRequested = true;
984
+ if (cancel) {
985
+ cancel();
986
+ }
987
+ else {
988
+ rejectResult(cancellationError?.() ?? new TimeoutError('rpc cancelled', { retryable: true }));
989
+ }
990
+ },
991
+ });
992
+ }
337
993
  /**
338
994
  * 连接到 Gateway WebSocket 端点。
339
995
  * 返回初始 challenge 消息(如果有)。
340
996
  */
341
997
  async connect(url) {
998
+ this._ensureNetworkWorker();
999
+ if (this._worker) {
1000
+ this._workerConnecting = true;
1001
+ try {
1002
+ const result = await this._workerCommand({
1003
+ type: 'connect', url, dnsFallback: this._dnsFallbackSnapshot(url),
1004
+ });
1005
+ this._workerConnected = true;
1006
+ this._workerConnecting = false;
1007
+ this._flushWorkerEvents();
1008
+ return result;
1009
+ }
1010
+ catch (error) {
1011
+ this._workerConnecting = false;
1012
+ this._workerConnected = false;
1013
+ this._workerGeneration = null;
1014
+ this._workerBufferedEvents = [];
1015
+ if (this._workerDisabled && this._worker === null) {
1016
+ return this._enqueueActorStart(() => this._connectLocal(url));
1017
+ }
1018
+ throw error;
1019
+ }
1020
+ }
1021
+ return this._enqueueActorStart(() => this._connectLocal(url));
1022
+ }
1023
+ async _connectLocal(url) {
342
1024
  const tStart = Date.now();
343
1025
  this._logger.debug(`connect enter: url=${url}`);
344
1026
  // 只串行化清理和监听器安装;后续 connect 可立即取消本次握手。
@@ -568,6 +1250,31 @@ export class RPCTransport {
568
1250
  }
569
1251
  /** 关闭连接 */
570
1252
  async close() {
1253
+ if (this._worker) {
1254
+ const worker = this._worker;
1255
+ try {
1256
+ await this._workerCommand({ type: 'close' });
1257
+ }
1258
+ finally {
1259
+ if (this._worker === worker)
1260
+ this._worker = null;
1261
+ this._workerConnected = false;
1262
+ this._workerConnecting = false;
1263
+ this._workerGeneration = null;
1264
+ this._workerBufferedEvents = [];
1265
+ for (const pending of this._workerPending.values()) {
1266
+ if (pending.timer !== null)
1267
+ clearTimeout(pending.timer);
1268
+ pending.reject(new ConnectionError('transport closed'));
1269
+ }
1270
+ this._workerPending.clear();
1271
+ await worker.terminate();
1272
+ }
1273
+ return;
1274
+ }
1275
+ await this._enqueueActor(() => this._closeLocal());
1276
+ }
1277
+ async _closeLocal() {
571
1278
  await this._withConnectionSetup(() => this._closeUnlocked());
572
1279
  }
573
1280
  /** 已持有连接建立串行权时关闭当前 WebSocket。 */
@@ -652,7 +1359,13 @@ export class RPCTransport {
652
1359
  /**
653
1360
  * 发送 JSON-RPC 2.0 请求并等待响应。
654
1361
  */
655
- async call(method, params, timeout, trace, background = false) {
1362
+ call(method, params, timeout, trace, background = false) {
1363
+ if (this._worker) {
1364
+ return this._workerCommand({ type: 'call', method, params, timeout, trace, background });
1365
+ }
1366
+ return this._enqueueActorStart(() => this._callLocal(method, params, timeout, trace, background), () => new TimeoutError(`rpc cancelled: ${method}`, { retryable: true }));
1367
+ }
1368
+ _callLocal(method, params, timeout, trace, background = false) {
656
1369
  if (this._closed || !this._ws) {
657
1370
  const suffix = this._lastCloseCode !== null ? `: close code ${this._lastCloseCode}` : '';
658
1371
  throw new ConnectionError(`transport not connected${suffix}`, {
@@ -721,12 +1434,7 @@ export class RPCTransport {
721
1434
  if (this._metaObserver !== null) {
722
1435
  const meta = response._meta;
723
1436
  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
- }
1437
+ this._queueObserver('meta_observer', this._metaObserver, meta);
730
1438
  }
731
1439
  }
732
1440
  // 处理 success 路径的 _trace
@@ -779,6 +1487,13 @@ export class RPCTransport {
779
1487
  }
780
1488
  /** 发送 JSON-RPC 2.0 Notification,不分配 id,也不等待响应。 */
781
1489
  async notify(method, params) {
1490
+ if (this._worker) {
1491
+ await this._workerCommand({ type: 'notify', method, params });
1492
+ return;
1493
+ }
1494
+ return this._enqueueActorStart(() => this._notifyLocal(method, params));
1495
+ }
1496
+ async _notifyLocal(method, params) {
782
1497
  if (this._closed || !this._ws) {
783
1498
  const suffix = this._lastCloseCode !== null ? `: close code ${this._lastCloseCode}` : '';
784
1499
  throw new ConnectionError(`transport not connected${suffix}`, {
@@ -965,7 +1680,9 @@ export class RPCTransport {
965
1680
  const enriched = { ...respTrace, spans };
966
1681
  this._logger.info(traceDisplay(method, status, elapsedMs, respTrace, spans));
967
1682
  if (this._traceObserver !== null) {
968
- this._traceObserver({ type: 'rpc', method, trace: enriched, status, duration_ms: elapsedMs });
1683
+ this._queueObserver('trace_observer', this._traceObserver, {
1684
+ type: 'rpc', method, trace: enriched, status, duration_ms: elapsedMs,
1685
+ });
969
1686
  }
970
1687
  }
971
1688
  catch (err) {
@@ -985,7 +1702,7 @@ export class RPCTransport {
985
1702
  }
986
1703
  catch (err) {
987
1704
  // 解析异常,发布错误事件
988
- this._dispatcher.publish('connection.error', {
1705
+ this._dispatcher.enqueue('connection.error', {
989
1706
  error: err instanceof Error ? err : String(err),
990
1707
  });
991
1708
  }
@@ -1018,8 +1735,10 @@ export class RPCTransport {
1018
1735
  }
1019
1736
  this._backgroundRpcQueue = [];
1020
1737
  if (!wasClosed && this._onDisconnect) {
1021
- const cb = this._onDisconnect;
1022
- Promise.resolve(cb(null, code)).catch(() => this._logger.warn('disconnect callback error'));
1738
+ this._dispatcher.enqueue('_transport.disconnect', {
1739
+ error: null,
1740
+ closeCode: code,
1741
+ });
1023
1742
  }
1024
1743
  });
1025
1744
  ws.on('error', (err) => {
@@ -1027,7 +1746,7 @@ export class RPCTransport {
1027
1746
  return;
1028
1747
  if (!this._closed) {
1029
1748
  this._logger.error(`WebSocket error: ${err.message}`);
1030
- this._dispatcher.publish('connection.error', { error: err });
1749
+ this._dispatcher.enqueue('connection.error', { error: err });
1031
1750
  }
1032
1751
  });
1033
1752
  this._startWebSocketHeartbeat(ws);
@@ -1136,7 +1855,7 @@ export class RPCTransport {
1136
1855
  if (method === 'challenge') {
1137
1856
  this._challenge = message;
1138
1857
  this._logger.debug('challenge received');
1139
- this._dispatcher.publish('connection.challenge', message.params ?? {});
1858
+ this._dispatcher.enqueue('connection.challenge', message.params ?? {});
1140
1859
  return;
1141
1860
  }
1142
1861
  // 事件消息(event/ 前缀)
@@ -1146,12 +1865,7 @@ export class RPCTransport {
1146
1865
  this._logger.debug(`event recv: event=${sdkEvent} ${summarizeDict(message.params, DIAG_RESULT_FIELDS)}`);
1147
1866
  const meta = message._meta;
1148
1867
  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
- }
1868
+ this._queueObserver('event meta_observer', this._metaObserver, meta);
1155
1869
  }
1156
1870
  // 提取事件中的 _trace 并回调 observer,然后从 params 中剥离
1157
1871
  const params = (message.params ?? {});
@@ -1160,10 +1874,9 @@ export class RPCTransport {
1160
1874
  delete params._trace;
1161
1875
  if (eventTrace && typeof eventTrace === 'object' && !Array.isArray(eventTrace)) {
1162
1876
  if (this._traceObserver !== null) {
1163
- try {
1164
- this._traceObserver({ type: 'event', event: sdkEvent, trace: eventTrace });
1165
- }
1166
- catch { /* observer 抛错被吞 */ }
1877
+ this._queueObserver('trace_observer', this._traceObserver, {
1878
+ type: 'event', event: sdkEvent, trace: eventTrace,
1879
+ });
1167
1880
  }
1168
1881
  const traceObj = eventTrace;
1169
1882
  this._logger.info(`[trace=${String(traceObj.trace_id ?? '')}] event_recv event=${sdkEvent}`);
@@ -1171,23 +1884,18 @@ export class RPCTransport {
1171
1884
  }
1172
1885
  // 发布为 _raw.{event},由 AUNClient 处理后再发布用户可见的事件
1173
1886
  if (sdkEvent.startsWith('app.')) {
1174
- this._dispatcher.publish(sdkEvent, params);
1887
+ this._dispatcher.enqueue(sdkEvent, params);
1175
1888
  return;
1176
1889
  }
1177
- this._dispatcher.publish(`_raw.${sdkEvent}`, params);
1890
+ this._dispatcher.enqueue(`_raw.${sdkEvent}`, params);
1178
1891
  return;
1179
1892
  }
1180
1893
  // 其他通知
1181
1894
  const meta = message._meta;
1182
1895
  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
- }
1896
+ this._queueObserver('notification meta_observer', this._metaObserver, meta);
1189
1897
  }
1190
- this._dispatcher.publish('notification', message);
1898
+ this._dispatcher.enqueue('notification', message);
1191
1899
  }
1192
1900
  /** 解码 WebSocket 消息为 JSON 对象 */
1193
1901
  _decodeMessage(raw) {
@@ -1222,4 +1930,118 @@ export class RPCTransport {
1222
1930
  }
1223
1931
  }
1224
1932
  }
1933
+ if (!isMainThread && Boolean(workerData?.aunShortRpcWorker) && parentPort) {
1934
+ const command = workerData;
1935
+ const controller = new AbortController();
1936
+ parentPort.postMessage({ type: 'ready', id: command.id });
1937
+ void executeShortRpcEnvelope(command.url, command.method, command.params, controller.signal)
1938
+ .then((result) => parentPort.postMessage({
1939
+ type: 'reply', id: command.id, ok: true, result,
1940
+ }))
1941
+ .catch((error) => parentPort.postMessage({
1942
+ type: 'reply', id: command.id, ok: false, error: serializeWorkerError(error),
1943
+ }))
1944
+ .finally(() => parentPort.close());
1945
+ }
1946
+ if (!isMainThread && Boolean(workerData?.aunTransportWorker) && parentPort) {
1947
+ let workerActor = null;
1948
+ const bridgeGeneration = () => Number(workerActor?._connectionGeneration ?? 0);
1949
+ const workerDispatcher = {
1950
+ enqueue(event, payload) {
1951
+ if (event === '_transport.observer') {
1952
+ const item = payload;
1953
+ parentPort.postMessage({ type: 'event', generation: bridgeGeneration(), event, payload: {
1954
+ kind: item.kind ?? 'observer', payload: item.payload ?? {},
1955
+ } });
1956
+ return;
1957
+ }
1958
+ parentPort.postMessage({ type: 'event', generation: bridgeGeneration(), event, payload });
1959
+ },
1960
+ subscribe() { return { unsubscribe() { } }; },
1961
+ };
1962
+ const actor = new RPCTransport({
1963
+ eventDispatcher: workerDispatcher,
1964
+ timeout: Number(workerData.timeout ?? 10_000),
1965
+ verifySsl: Boolean(workerData.verifySsl ?? true),
1966
+ // Worker 内只让 close handler 经 workerDispatcher 转发一次,主线程负责执行真实回调。
1967
+ onDisconnect: () => { },
1968
+ });
1969
+ workerActor = actor;
1970
+ actor.setMetaObserver((meta) => workerDispatcher.enqueue('_transport.observer', {
1971
+ kind: 'meta_observer', payload: meta,
1972
+ }));
1973
+ actor.setTraceObserver((trace) => workerDispatcher.enqueue('_transport.observer', {
1974
+ kind: 'trace_observer', payload: trace,
1975
+ }));
1976
+ const active = new Map();
1977
+ let dnsFallback = null;
1978
+ actor._dnsNet = {
1979
+ loadDnsCache(hostname) {
1980
+ return dnsFallback?.hostname === hostname ? { ip: dnsFallback.ip, port: dnsFallback.port } : null;
1981
+ },
1982
+ };
1983
+ parentPort.on('message', async (command) => {
1984
+ const id = command.id;
1985
+ try {
1986
+ if (command.type === 'cancel') {
1987
+ const pending = active.get(String(command.targetId ?? ''));
1988
+ pending?.cancel?.();
1989
+ return;
1990
+ }
1991
+ let result;
1992
+ switch (command.type) {
1993
+ case 'connect': {
1994
+ dnsFallback = command.dnsFallback ?? null;
1995
+ const request = actor.connect(String(command.url ?? ''));
1996
+ if (id)
1997
+ active.set(id, { cancel: request.cancel ?? (() => { void actor.close().catch(() => { }); }) });
1998
+ result = await request;
1999
+ if (id)
2000
+ active.delete(id);
2001
+ break;
2002
+ }
2003
+ case 'call': {
2004
+ const request = actor.call(String(command.method ?? ''), command.params ?? undefined, command.timeout, command.trace, Boolean(command.background));
2005
+ if (id)
2006
+ active.set(id, request);
2007
+ result = await request;
2008
+ if (id)
2009
+ active.delete(id);
2010
+ break;
2011
+ }
2012
+ case 'notify':
2013
+ result = await actor.notify(String(command.method ?? ''), command.params);
2014
+ break;
2015
+ case 'close':
2016
+ result = await actor.close();
2017
+ break;
2018
+ case 'set_timeout':
2019
+ actor.setTimeout(Number(command.value));
2020
+ return;
2021
+ case 'set_connect_timeout':
2022
+ actor.setConnectTimeout(Number(command.value));
2023
+ return;
2024
+ case 'set_trace_mode':
2025
+ actor.setTraceMode(String(command.value ?? 'off'));
2026
+ return;
2027
+ default: return;
2028
+ }
2029
+ if (id)
2030
+ parentPort.postMessage({ type: 'reply', id, generation: bridgeGeneration(), ok: true, result });
2031
+ }
2032
+ catch (error) {
2033
+ if (id)
2034
+ active.delete(id);
2035
+ if (id)
2036
+ parentPort.postMessage({
2037
+ type: 'reply',
2038
+ id,
2039
+ generation: bridgeGeneration(),
2040
+ ok: false,
2041
+ error: serializeWorkerError(error),
2042
+ });
2043
+ }
2044
+ });
2045
+ parentPort.postMessage({ type: 'ready' });
2046
+ }
1225
2047
  //# sourceMappingURL=transport.js.map