Merge cb4d03bb868c02e11f40b7ace1027ef895b21da0 into 5e417b44e1540f528d2ae63e3e20229a902d1db2
This commit is contained in:
commit
315b877001
@ -6,6 +6,7 @@ import { danger } from "openclaw/plugin-sdk/runtime-env";
|
||||
import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { ProxyAgent, fetch as undiciFetch } from "undici";
|
||||
import WebSocket from "ws";
|
||||
import { resolveEnvHttpProxyUrl } from "../../../../src/infra/net/proxy-env.js";
|
||||
|
||||
const DISCORD_GATEWAY_BOT_URL = "https://discord.com/api/v10/gateway/bot";
|
||||
const DEFAULT_DISCORD_GATEWAY_URL = "wss://gateway.discord.gg/";
|
||||
@ -270,7 +271,7 @@ export function createDiscordGatewayPlugin(params: {
|
||||
runtime: RuntimeEnv;
|
||||
}): GatewayPlugin {
|
||||
const intents = resolveDiscordGatewayIntents(params.discordConfig?.intents);
|
||||
const proxy = params.discordConfig?.proxy?.trim();
|
||||
const proxy = params.discordConfig?.proxy?.trim() || resolveEnvHttpProxyUrl("https");
|
||||
const options = {
|
||||
reconnect: { maxAttempts: 50 },
|
||||
intents,
|
||||
@ -287,7 +288,7 @@ export function createDiscordGatewayPlugin(params: {
|
||||
|
||||
try {
|
||||
const wsAgent = new HttpsProxyAgent<string>(proxy);
|
||||
const fetchAgent = new ProxyAgent(proxy);
|
||||
const fetchAgent = new ProxyAgent({ uri: proxy, connect: { keepAlive: false } });
|
||||
|
||||
params.runtime.log?.("discord: gateway proxy enabled");
|
||||
|
||||
|
||||
@ -89,7 +89,8 @@ vi.mock("https-proxy-agent", () => ({
|
||||
vi.mock("undici", () => ({
|
||||
ProxyAgent: class {
|
||||
proxyUrl: string;
|
||||
constructor(proxyUrl: string) {
|
||||
constructor(opts: string | { uri: string }) {
|
||||
const proxyUrl = typeof opts === "string" ? opts : opts.uri;
|
||||
this.proxyUrl = proxyUrl;
|
||||
undiciProxyAgentSpy(proxyUrl);
|
||||
restProxyAgentSpy(proxyUrl);
|
||||
@ -98,6 +99,10 @@ vi.mock("undici", () => ({
|
||||
fetch: undiciFetchMock,
|
||||
}));
|
||||
|
||||
vi.mock("../../../../src/infra/net/proxy-env.js", () => ({
|
||||
resolveEnvHttpProxyUrl: () => undefined,
|
||||
}));
|
||||
|
||||
vi.mock("ws", () => ({
|
||||
default: class MockWebSocket {
|
||||
constructor(url: string, options?: { agent?: unknown }) {
|
||||
|
||||
@ -9,7 +9,8 @@ const { undiciFetchMock, proxyAgentSpy } = vi.hoisted(() => ({
|
||||
vi.mock("undici", () => {
|
||||
class ProxyAgent {
|
||||
proxyUrl: string;
|
||||
constructor(proxyUrl: string) {
|
||||
constructor(opts: string | { uri: string }) {
|
||||
const proxyUrl = typeof opts === "string" ? opts : opts.uri;
|
||||
if (proxyUrl === "bad-proxy") {
|
||||
throw new Error("bad proxy");
|
||||
}
|
||||
|
||||
@ -12,7 +12,7 @@ export function resolveDiscordRestFetch(
|
||||
return fetch;
|
||||
}
|
||||
try {
|
||||
const agent = new ProxyAgent(proxy);
|
||||
const agent = new ProxyAgent({ uri: proxy, connect: { keepAlive: false } });
|
||||
const fetcher = ((input: RequestInfo | URL, init?: RequestInit) =>
|
||||
undiciFetch(input as string | URL, {
|
||||
...(init as Record<string, unknown>),
|
||||
|
||||
@ -17,6 +17,10 @@ import type { GatewayWsLogStyle } from "../../gateway/ws-logging.js";
|
||||
import { setGatewayWsLogStyle } from "../../gateway/ws-logging.js";
|
||||
import { setVerbose } from "../../globals.js";
|
||||
import { GatewayLockError } from "../../infra/gateway-lock.js";
|
||||
import {
|
||||
ensureGlobalUndiciEnvProxyDispatcher,
|
||||
ensureGlobalUndiciStreamTimeouts,
|
||||
} from "../../infra/net/undici-global-dispatcher.js";
|
||||
import { formatPortDiagnostics, inspectPortUsage } from "../../infra/ports.js";
|
||||
import { cleanStaleGatewayProcessesSync } from "../../infra/restart-stale-pids.js";
|
||||
import { setConsoleSubsystemFilter, setConsoleTimestampPrefix } from "../../logging/console.js";
|
||||
@ -418,6 +422,11 @@ async function runGatewayCommand(opts: GatewayRunOpts) {
|
||||
}
|
||||
: undefined;
|
||||
|
||||
// Proxy bootstrap must happen before timeout tuning so the timeouts wrap the
|
||||
// active EnvHttpProxyAgent instead of being replaced by a bare proxy dispatcher.
|
||||
ensureGlobalUndiciEnvProxyDispatcher();
|
||||
ensureGlobalUndiciStreamTimeouts();
|
||||
|
||||
try {
|
||||
await runGatewayLoop({
|
||||
runtime: defaultRuntime,
|
||||
|
||||
@ -205,8 +205,9 @@ export function isNodeCommandAllowed(params: {
|
||||
if (!params.allowlist.has(command)) {
|
||||
return { ok: false, reason: "command not allowlisted" };
|
||||
}
|
||||
if (Array.isArray(params.declaredCommands) && params.declaredCommands.length > 0) {
|
||||
if (!params.declaredCommands.includes(command)) {
|
||||
const { declaredCommands } = params;
|
||||
if (Array.isArray(declaredCommands) && declaredCommands.length > 0) {
|
||||
if (!declaredCommands.includes(command)) {
|
||||
return { ok: false, reason: "command not declared by node" };
|
||||
}
|
||||
} else {
|
||||
|
||||
@ -1003,15 +1003,16 @@ export const nodeHandlers: GatewayRequestHandlers = {
|
||||
`node wake done node=${nodeId} req=${wakeReqId} connected=true totalMs=${totalDurationMs}`,
|
||||
);
|
||||
}
|
||||
const session = nodeSession;
|
||||
const cfg = loadConfig();
|
||||
const allowlist = resolveNodeCommandAllowlist(cfg, nodeSession);
|
||||
const allowlist = resolveNodeCommandAllowlist(cfg, session);
|
||||
const allowed = isNodeCommandAllowed({
|
||||
command,
|
||||
declaredCommands: nodeSession.commands,
|
||||
declaredCommands: session.commands,
|
||||
allowlist,
|
||||
});
|
||||
if (!allowed.ok) {
|
||||
const hint = buildNodeCommandRejectionHint(allowed.reason, command, nodeSession);
|
||||
const hint = buildNodeCommandRejectionHint(allowed.reason, command, session);
|
||||
respond(
|
||||
false,
|
||||
undefined,
|
||||
|
||||
@ -111,6 +111,7 @@ describe("ensureGlobalUndiciStreamTimeouts", () => {
|
||||
expect(next.options?.bodyTimeout).toBe(DEFAULT_UNDICI_STREAM_TIMEOUT_MS);
|
||||
expect(next.options?.headersTimeout).toBe(DEFAULT_UNDICI_STREAM_TIMEOUT_MS);
|
||||
expect(next.options?.connect).toEqual({
|
||||
keepAlive: false,
|
||||
autoSelectFamily: false,
|
||||
autoSelectFamilyAttemptTimeout: 300,
|
||||
});
|
||||
|
||||
@ -93,7 +93,10 @@ export function ensureGlobalUndiciEnvProxyDispatcher(): void {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
setGlobalDispatcher(new EnvHttpProxyAgent());
|
||||
// Many local proxies (Clash, Surge, etc.) close HTTP CONNECT tunnels after
|
||||
// the first request, causing ECONNRESET on reuse. Disable keep-alive so
|
||||
// undici opens a fresh tunnel per request instead of pooling.
|
||||
setGlobalDispatcher(new EnvHttpProxyAgent({ connect: { keepAlive: false } }));
|
||||
lastAppliedProxyBootstrap = true;
|
||||
} catch {
|
||||
// Best-effort bootstrap only.
|
||||
@ -120,10 +123,11 @@ export function ensureGlobalUndiciStreamTimeouts(opts?: { timeoutMs?: number }):
|
||||
const connect = resolveConnectOptions(autoSelectFamily);
|
||||
try {
|
||||
if (kind === "env-proxy") {
|
||||
const proxyConnect = { ...connect, keepAlive: false };
|
||||
const proxyOptions = {
|
||||
bodyTimeout: timeoutMs,
|
||||
headersTimeout: timeoutMs,
|
||||
...(connect ? { connect } : {}),
|
||||
connect: proxyConnect,
|
||||
} as ConstructorParameters<typeof EnvHttpProxyAgent>[0];
|
||||
setGlobalDispatcher(new EnvHttpProxyAgent(proxyOptions));
|
||||
} else {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user