11import * as dns from "node:dns" ;
22import * as net from "node:net" ;
3- import { Agent , setGlobalDispatcher } from "undici" ;
3+ import { Agent , getGlobalDispatcher , setGlobalDispatcher } from "undici" ;
4+ import type { Dispatcher } from "undici" ;
45import type { TelegramNetworkConfig } from "../config/types.telegram.js" ;
56import { resolveFetch } from "../infra/fetch.js" ;
67import { createSubsystemLogger } from "../logging/subsystem.js" ;
@@ -12,6 +13,7 @@ import {
1213let appliedAutoSelectFamily : boolean | null = null ;
1314let appliedDnsResultOrder : string | null = null ;
1415let appliedGlobalDispatcherAutoSelectFamily : boolean | null = null ;
16+ let originalGlobalDispatcher : Dispatcher | null = null ;
1517const log = createSubsystemLogger ( "telegram/network" ) ;
1618
1719// Node 22 workaround: enable autoSelectFamily to allow IPv4 fallback on broken IPv6 networks.
@@ -41,26 +43,39 @@ function applyTelegramNetworkWorkarounds(network?: TelegramNetworkConfig): void
4143 // inherit the same decision.
4244 // See: https://github.com/openclaw/openclaw/issues/25676
4345 //
44- // IMPORTANT: Only replace the global dispatcher when autoSelectFamily is
45- // explicitly *enabled*. Replacing it unconditionally (even with false)
46- // swaps in a bare Agent that discards any configuration the original
47- // default dispatcher carried, which can break other HTTP clients in the
48- // same process (e.g. LLM provider fetches returning 403).
46+ // IMPORTANT: Replacing the global dispatcher unconditionally (even with
47+ // autoSelectFamily=false) swaps in a bare Agent that discards any config
48+ // the original default dispatcher carried, breaking other HTTP clients in
49+ // the same process (e.g. LLM provider fetches returning 403).
50+ //
51+ // When enabling (true): save the original dispatcher and replace it.
52+ // When disabling (false) after a previous enable: restore the original.
4953 if (
50- autoSelectDecision . value === true &&
54+ autoSelectDecision . value !== null &&
5155 autoSelectDecision . value !== appliedGlobalDispatcherAutoSelectFamily
5256 ) {
5357 try {
54- setGlobalDispatcher (
55- new Agent ( {
56- connect : {
57- autoSelectFamily : true ,
58- autoSelectFamilyAttemptTimeout : 300 ,
59- } ,
60- } ) ,
61- ) ;
62- appliedGlobalDispatcherAutoSelectFamily = true ;
63- log . info ( `global undici dispatcher autoSelectFamily=true` ) ;
58+ if ( autoSelectDecision . value ) {
59+ // Save the original dispatcher before first replacement.
60+ if ( originalGlobalDispatcher === null ) {
61+ originalGlobalDispatcher = getGlobalDispatcher ( ) ;
62+ }
63+ setGlobalDispatcher (
64+ new Agent ( {
65+ connect : {
66+ autoSelectFamily : true ,
67+ autoSelectFamilyAttemptTimeout : 300 ,
68+ } ,
69+ } ) ,
70+ ) ;
71+ appliedGlobalDispatcherAutoSelectFamily = true ;
72+ log . info ( `global undici dispatcher autoSelectFamily=true` ) ;
73+ } else if ( originalGlobalDispatcher !== null ) {
74+ // Restore the original dispatcher so other HTTP clients are unaffected.
75+ setGlobalDispatcher ( originalGlobalDispatcher ) ;
76+ appliedGlobalDispatcherAutoSelectFamily = false ;
77+ log . info ( "global undici dispatcher restored to original" ) ;
78+ }
6479 } catch {
6580 // ignore if setGlobalDispatcher is unavailable
6681 }
@@ -104,4 +119,5 @@ export function resetTelegramFetchStateForTests(): void {
104119 appliedAutoSelectFamily = null ;
105120 appliedDnsResultOrder = null ;
106121 appliedGlobalDispatcherAutoSelectFamily = null ;
122+ originalGlobalDispatcher = null ;
107123}
0 commit comments