mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:30:42 +00:00
fix: bootstrap gateway env proxy dispatcher
Co-authored-by: mjamiv <74088820+mjamiv@users.noreply.github.com>
This commit is contained in:
@@ -80,6 +80,9 @@ Docs: https://docs.openclaw.ai
|
||||
- Gateway/Fly.io: seed Control UI allowed origins from the actual runtime
|
||||
bind and port so CLI-driven non-loopback starts do not crash before config
|
||||
exists. Fixes #71823.
|
||||
- Gateway/proxy: bootstrap env proxy dispatching from direct Gateway startup
|
||||
so provider and plugin network requests honor `HTTPS_PROXY`/`HTTP_PROXY`
|
||||
before the first embedded agent attempt runs. (#71833) Thanks @mjamiv.
|
||||
- Models/LM Studio: preserve `@iq*` quant suffixes in model refs and provider
|
||||
matching so `/model lmstudio/...@iq3_xxs` keeps the exact LM Studio variant.
|
||||
Fixes #71474. (#71486) Thanks @Bartok9, @XinwuC, and @Sanjays2402.
|
||||
|
||||
109
src/gateway/server-network-runtime.e2e.test.ts
Normal file
109
src/gateway/server-network-runtime.e2e.test.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { Agent, getGlobalDispatcher, setGlobalDispatcher } from "undici";
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import { clearAllBootstrapSnapshots } from "../agents/bootstrap-cache.js";
|
||||
import { clearConfigCache, clearRuntimeConfigSnapshot } from "../config/config.js";
|
||||
import { clearSessionStoreCacheForTest } from "../config/sessions/store.js";
|
||||
import { resetAgentRunContextForTest } from "../infra/agent-events.js";
|
||||
import { PROXY_ENV_KEYS } from "../infra/net/proxy-env.js";
|
||||
import { clearGatewaySubagentRuntime } from "../plugins/runtime/index.js";
|
||||
import { captureEnv } from "../test-utils/env.js";
|
||||
import { startGatewayServer } from "./server.js";
|
||||
import { getFreeGatewayPort } from "./test-helpers.e2e.js";
|
||||
|
||||
const NETWORK_GATEWAY_ENV_KEYS = [
|
||||
"HOME",
|
||||
"OPENCLAW_STATE_DIR",
|
||||
"OPENCLAW_CONFIG_PATH",
|
||||
"OPENCLAW_GATEWAY_TOKEN",
|
||||
"OPENCLAW_SKIP_CHANNELS",
|
||||
"OPENCLAW_SKIP_GMAIL_WATCHER",
|
||||
"OPENCLAW_SKIP_CRON",
|
||||
"OPENCLAW_SKIP_CANVAS_HOST",
|
||||
"OPENCLAW_SKIP_BROWSER_CONTROL_SERVER",
|
||||
"OPENCLAW_SKIP_PROVIDERS",
|
||||
"OPENCLAW_BUNDLED_PLUGINS_DIR",
|
||||
"OPENCLAW_TEST_MINIMAL_GATEWAY",
|
||||
...PROXY_ENV_KEYS,
|
||||
"NO_PROXY",
|
||||
"no_proxy",
|
||||
] as const;
|
||||
|
||||
function isEnvHttpProxyDispatcher(dispatcher: unknown): boolean {
|
||||
return (
|
||||
(dispatcher as { constructor?: { name?: string } } | undefined)?.constructor?.name ===
|
||||
"EnvHttpProxyAgent"
|
||||
);
|
||||
}
|
||||
|
||||
describe("gateway network runtime", () => {
|
||||
beforeEach(() => {
|
||||
clearRuntimeConfigSnapshot();
|
||||
clearConfigCache();
|
||||
clearSessionStoreCacheForTest();
|
||||
resetAgentRunContextForTest();
|
||||
clearAllBootstrapSnapshots();
|
||||
clearGatewaySubagentRuntime();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
clearRuntimeConfigSnapshot();
|
||||
clearConfigCache();
|
||||
clearSessionStoreCacheForTest();
|
||||
resetAgentRunContextForTest();
|
||||
clearAllBootstrapSnapshots();
|
||||
clearGatewaySubagentRuntime();
|
||||
});
|
||||
|
||||
it("bootstraps env proxy dispatching when the gateway starts directly", async () => {
|
||||
const envSnapshot = captureEnv([...NETWORK_GATEWAY_ENV_KEYS]);
|
||||
const originalDispatcher = getGlobalDispatcher();
|
||||
const tempHome = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-gw-proxy-home-"));
|
||||
let server: Awaited<ReturnType<typeof startGatewayServer>> | undefined;
|
||||
|
||||
try {
|
||||
setGlobalDispatcher(new Agent());
|
||||
for (const key of NETWORK_GATEWAY_ENV_KEYS) {
|
||||
delete process.env[key];
|
||||
}
|
||||
process.env.HTTPS_PROXY = "http://127.0.0.1:9";
|
||||
|
||||
process.env.HOME = tempHome;
|
||||
process.env.OPENCLAW_STATE_DIR = path.join(tempHome, ".openclaw");
|
||||
process.env.OPENCLAW_SKIP_CHANNELS = "1";
|
||||
process.env.OPENCLAW_SKIP_GMAIL_WATCHER = "1";
|
||||
process.env.OPENCLAW_SKIP_CRON = "1";
|
||||
process.env.OPENCLAW_SKIP_CANVAS_HOST = "1";
|
||||
process.env.OPENCLAW_SKIP_BROWSER_CONTROL_SERVER = "1";
|
||||
process.env.OPENCLAW_SKIP_PROVIDERS = "1";
|
||||
process.env.OPENCLAW_TEST_MINIMAL_GATEWAY = "1";
|
||||
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = path.join(tempHome, "empty-bundled-plugins");
|
||||
await fs.mkdir(process.env.OPENCLAW_BUNDLED_PLUGINS_DIR, { recursive: true });
|
||||
|
||||
const token = `proxy-token-${process.pid}-${process.env.VITEST_POOL_ID ?? "0"}`;
|
||||
process.env.OPENCLAW_GATEWAY_TOKEN = token;
|
||||
const configPath = path.join(tempHome, ".openclaw", "openclaw.json");
|
||||
await fs.mkdir(path.dirname(configPath), { recursive: true });
|
||||
await fs.writeFile(
|
||||
configPath,
|
||||
`${JSON.stringify({ gateway: { auth: { mode: "token", token } } }, null, 2)}\n`,
|
||||
);
|
||||
process.env.OPENCLAW_CONFIG_PATH = configPath;
|
||||
|
||||
server = await startGatewayServer(await getFreeGatewayPort(), {
|
||||
bind: "loopback",
|
||||
auth: { mode: "token", token },
|
||||
controlUiEnabled: false,
|
||||
});
|
||||
|
||||
expect(isEnvHttpProxyDispatcher(getGlobalDispatcher())).toBe(true);
|
||||
} finally {
|
||||
await server?.close({ reason: "gateway proxy bootstrap test complete" });
|
||||
setGlobalDispatcher(originalDispatcher);
|
||||
await fs.rm(tempHome, { recursive: true, force: true });
|
||||
envSnapshot.restore();
|
||||
}
|
||||
});
|
||||
});
|
||||
21
src/gateway/server-network-runtime.test.ts
Normal file
21
src/gateway/server-network-runtime.test.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const ensureGlobalUndiciEnvProxyDispatcherMock = vi.fn();
|
||||
|
||||
vi.mock("../infra/net/undici-global-dispatcher.js", () => ({
|
||||
ensureGlobalUndiciEnvProxyDispatcher: ensureGlobalUndiciEnvProxyDispatcherMock,
|
||||
}));
|
||||
|
||||
const { bootstrapGatewayNetworkRuntime } = await import("./server-network-runtime.js");
|
||||
|
||||
describe("bootstrapGatewayNetworkRuntime", () => {
|
||||
beforeEach(() => {
|
||||
ensureGlobalUndiciEnvProxyDispatcherMock.mockClear();
|
||||
});
|
||||
|
||||
it("installs the env proxy dispatcher for gateway-owned network work", () => {
|
||||
bootstrapGatewayNetworkRuntime();
|
||||
|
||||
expect(ensureGlobalUndiciEnvProxyDispatcherMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
5
src/gateway/server-network-runtime.ts
Normal file
5
src/gateway/server-network-runtime.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { ensureGlobalUndiciEnvProxyDispatcher } from "../infra/net/undici-global-dispatcher.js";
|
||||
|
||||
export function bootstrapGatewayNetworkRuntime(): void {
|
||||
ensureGlobalUndiciEnvProxyDispatcher();
|
||||
}
|
||||
@@ -55,6 +55,7 @@ import { createGatewayServerLiveState, type GatewayServerLiveState } from "./ser
|
||||
import { GATEWAY_EVENTS } from "./server-methods-list.js";
|
||||
import { coreGatewayHandlers } from "./server-methods.js";
|
||||
import { loadGatewayModelCatalog } from "./server-model-catalog.js";
|
||||
import { bootstrapGatewayNetworkRuntime } from "./server-network-runtime.js";
|
||||
import { createGatewayNodeSessionRuntime } from "./server-node-session-runtime.js";
|
||||
import { setFallbackGatewayContextResolver } from "./server-plugins.js";
|
||||
import { startManagedGatewayConfigReloader } from "./server-reload-handlers.js";
|
||||
@@ -244,6 +245,8 @@ export async function startGatewayServer(
|
||||
port = 18789,
|
||||
opts: GatewayServerOptions = {},
|
||||
): Promise<GatewayServer> {
|
||||
bootstrapGatewayNetworkRuntime();
|
||||
|
||||
const minimalTestGateway =
|
||||
isVitestRuntimeEnv() && process.env.OPENCLAW_TEST_MINIMAL_GATEWAY === "1";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user