mirror of
https://github.com/moltbot/moltbot.git
synced 2026-03-07 22:44:16 +00:00
fix(telegram): refresh global undici dispatcher for autoSelectFamily (#25682)
Land PR #25682 from @lairtonlelis after maintainer rework: track dispatcher updates when network decision changes to avoid stale global fetch behavior. Co-authored-by: Ailton <lairton@telnyx.com>
This commit is contained in:
@@ -55,6 +55,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Security/Exec approvals: fail closed when transparent dispatch-wrapper unwrapping exceeds the depth cap, so nested `/usr/bin/env` chains cannot bypass shell-wrapper approval gating in `allowlist` + `ask=on-miss` mode. Thanks @tdjackey for reporting.
|
||||
- Security/Exec: limit default safe-bin trusted directories to immutable system paths (`/bin`, `/usr/bin`) and require explicit opt-in (`tools.exec.safeBinTrustedDirs`) for package-manager/user bin paths (for example Homebrew), add security-audit findings for risky trusted-dir choices, warn at runtime when explicitly trusted dirs are group/world writable, and add doctor hints when configured `safeBins` resolve outside trusted dirs. Thanks @tdjackey for reporting.
|
||||
- Telegram/Media fetch: prioritize IPv4 before IPv6 in SSRF pinned DNS address ordering so media downloads still work on hosts with broken IPv6 routing. (#24295, #23975) Thanks @Glucksberg.
|
||||
- Telegram/Outbound API: replace Node 22's global undici dispatcher when applying Telegram `autoSelectFamily` decisions so outbound `fetch` calls inherit IPv4 fallback instead of staying pinned to stale dispatcher settings. (#25682, #25676) Thanks @lairtonlelis.
|
||||
- Telegram/Replies: when markdown formatting renders to empty HTML (for example syntax-only chunks in threaded replies), retry delivery with plain text, and fail loud when both formatted and plain payloads are empty to avoid false delivered states. (#25096, #25091) Thanks @Glucksberg.
|
||||
- Sessions/Tool-result guard: avoid generating synthetic `toolResult` entries for assistant turns that ended with `stopReason: "aborted"` or `"error"`, preventing orphaned tool-use IDs from triggering downstream API validation errors. (#25429) Thanks @mikaeldiakhate-cell.
|
||||
- Usage accounting: parse Moonshot/Kimi `cached_tokens` fields (including `prompt_tokens_details.cached_tokens`) into normalized cache-read usage metrics. (#25436) Thanks @Elarwei001.
|
||||
|
||||
@@ -4,6 +4,12 @@ import { resetTelegramFetchStateForTests, resolveTelegramFetch } from "./fetch.j
|
||||
|
||||
const setDefaultAutoSelectFamily = vi.hoisted(() => vi.fn());
|
||||
const setDefaultResultOrder = vi.hoisted(() => vi.fn());
|
||||
const setGlobalDispatcher = vi.hoisted(() => vi.fn());
|
||||
const AgentCtor = vi.hoisted(() =>
|
||||
vi.fn(function MockAgent(this: { options: unknown }, options: unknown) {
|
||||
this.options = options;
|
||||
}),
|
||||
);
|
||||
|
||||
vi.mock("node:net", async () => {
|
||||
const actual = await vi.importActual<typeof import("node:net")>("node:net");
|
||||
@@ -21,12 +27,19 @@ vi.mock("node:dns", async () => {
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("undici", () => ({
|
||||
Agent: AgentCtor,
|
||||
setGlobalDispatcher,
|
||||
}));
|
||||
|
||||
const originalFetch = globalThis.fetch;
|
||||
|
||||
afterEach(() => {
|
||||
resetTelegramFetchStateForTests();
|
||||
setDefaultAutoSelectFamily.mockReset();
|
||||
setDefaultResultOrder.mockReset();
|
||||
setGlobalDispatcher.mockReset();
|
||||
AgentCtor.mockClear();
|
||||
vi.unstubAllEnvs();
|
||||
vi.clearAllMocks();
|
||||
if (originalFetch) {
|
||||
@@ -133,4 +146,45 @@ describe("resolveTelegramFetch", () => {
|
||||
|
||||
expect(setDefaultResultOrder).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("replaces global undici dispatcher with autoSelectFamily-enabled agent", async () => {
|
||||
globalThis.fetch = vi.fn(async () => ({})) as unknown as typeof fetch;
|
||||
resolveTelegramFetch(undefined, { network: { autoSelectFamily: true } });
|
||||
|
||||
expect(setGlobalDispatcher).toHaveBeenCalledTimes(1);
|
||||
expect(AgentCtor).toHaveBeenCalledWith({
|
||||
connect: {
|
||||
autoSelectFamily: true,
|
||||
autoSelectFamilyAttemptTimeout: 300,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("sets global dispatcher only once across repeated equal decisions", async () => {
|
||||
globalThis.fetch = vi.fn(async () => ({})) as unknown as typeof fetch;
|
||||
resolveTelegramFetch(undefined, { network: { autoSelectFamily: true } });
|
||||
resolveTelegramFetch(undefined, { network: { autoSelectFamily: true } });
|
||||
|
||||
expect(setGlobalDispatcher).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("updates global dispatcher when autoSelectFamily decision changes", async () => {
|
||||
globalThis.fetch = vi.fn(async () => ({})) as unknown as typeof fetch;
|
||||
resolveTelegramFetch(undefined, { network: { autoSelectFamily: true } });
|
||||
resolveTelegramFetch(undefined, { network: { autoSelectFamily: false } });
|
||||
|
||||
expect(setGlobalDispatcher).toHaveBeenCalledTimes(2);
|
||||
expect(AgentCtor).toHaveBeenNthCalledWith(1, {
|
||||
connect: {
|
||||
autoSelectFamily: true,
|
||||
autoSelectFamilyAttemptTimeout: 300,
|
||||
},
|
||||
});
|
||||
expect(AgentCtor).toHaveBeenNthCalledWith(2, {
|
||||
connect: {
|
||||
autoSelectFamily: false,
|
||||
autoSelectFamilyAttemptTimeout: 300,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import * as dns from "node:dns";
|
||||
import * as net from "node:net";
|
||||
import { Agent, setGlobalDispatcher } from "undici";
|
||||
import type { TelegramNetworkConfig } from "../config/types.telegram.js";
|
||||
import { resolveFetch } from "../infra/fetch.js";
|
||||
import { createSubsystemLogger } from "../logging/subsystem.js";
|
||||
@@ -10,6 +11,7 @@ import {
|
||||
|
||||
let appliedAutoSelectFamily: boolean | null = null;
|
||||
let appliedDnsResultOrder: string | null = null;
|
||||
let appliedGlobalDispatcherAutoSelectFamily: boolean | null = null;
|
||||
const log = createSubsystemLogger("telegram/network");
|
||||
|
||||
// Node 22 workaround: enable autoSelectFamily to allow IPv4 fallback on broken IPv6 networks.
|
||||
@@ -31,6 +33,33 @@ function applyTelegramNetworkWorkarounds(network?: TelegramNetworkConfig): void
|
||||
}
|
||||
}
|
||||
|
||||
// Node 22's built-in globalThis.fetch uses undici's internal Agent whose
|
||||
// connect options are frozen at construction time. Calling
|
||||
// net.setDefaultAutoSelectFamily() after that agent is created has no
|
||||
// effect on it. Replace the global dispatcher with one that carries the
|
||||
// current autoSelectFamily setting so subsequent globalThis.fetch calls
|
||||
// inherit the same decision.
|
||||
// See: https://github.com/openclaw/openclaw/issues/25676
|
||||
if (
|
||||
autoSelectDecision.value !== null &&
|
||||
autoSelectDecision.value !== appliedGlobalDispatcherAutoSelectFamily
|
||||
) {
|
||||
try {
|
||||
setGlobalDispatcher(
|
||||
new Agent({
|
||||
connect: {
|
||||
autoSelectFamily: autoSelectDecision.value,
|
||||
autoSelectFamilyAttemptTimeout: 300,
|
||||
},
|
||||
}),
|
||||
);
|
||||
appliedGlobalDispatcherAutoSelectFamily = autoSelectDecision.value;
|
||||
log.info(`global undici dispatcher autoSelectFamily=${autoSelectDecision.value}`);
|
||||
} catch {
|
||||
// ignore if setGlobalDispatcher is unavailable
|
||||
}
|
||||
}
|
||||
|
||||
// Apply DNS result order workaround for IPv4/IPv6 issues.
|
||||
// Some APIs (including Telegram) may fail with IPv6 on certain networks.
|
||||
// See: https://github.com/openclaw/openclaw/issues/5311
|
||||
@@ -68,4 +97,5 @@ export function resolveTelegramFetch(
|
||||
export function resetTelegramFetchStateForTests(): void {
|
||||
appliedAutoSelectFamily = null;
|
||||
appliedDnsResultOrder = null;
|
||||
appliedGlobalDispatcherAutoSelectFamily = null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user