build(deps): route node proxy helpers through proxyline

This commit is contained in:
Peter Steinberger
2026-05-14 18:26:35 +01:00
parent 28550a798c
commit 59be6d6390
9 changed files with 99 additions and 146 deletions

View File

@@ -6,6 +6,7 @@ Docs: https://docs.openclaw.ai
### Changes
- Dependencies: route root ambient Node proxy agents through `@openclaw/proxyline` and drop root `proxy-agent`, `https-proxy-agent`, and `minimatch` dependencies.
- Control UI/i18n: add a `pnpm ui:i18n:report` baseline report for hardcoded-copy focus areas and locale fallback metadata. (#81320) Thanks @samzong.
- Maintainer tooling: add a repo-local `codex-review` skill for Codex closeout reviews, including local dirty-work and PR-branch review helpers that rerun until no accepted/actionable findings remain and avoid unsupported inline prompts with `--base`.
- Maintainer tooling: fail CI when pull requests add package patch files or pnpm patched dependencies, preserving the upstream-and-bump dependency workflow.

View File

@@ -18,7 +18,7 @@ const wsClientCtorMock = vi.hoisted(() =>
}),
);
const proxyAgentCtorMock = vi.hoisted(() =>
vi.fn(function proxyAgentCtor() {
vi.fn(function createAmbientNodeProxyAgent() {
return { proxied: true };
}),
);
@@ -158,8 +158,16 @@ beforeAll(async () => {
EventDispatcher: vi.fn(),
defaultHttpInstance: mockBaseHttpInstance,
}));
vi.doMock("proxy-agent", () => ({
ProxyAgent: proxyAgentCtorMock,
vi.doMock("@openclaw/proxyline", () => ({
createAmbientNodeProxyAgent: proxyAgentCtorMock,
hasAmbientNodeProxyConfigured: vi.fn(() =>
Boolean(
process.env.HTTPS_PROXY ??
process.env.https_proxy ??
process.env.HTTP_PROXY ??
process.env.http_proxy,
),
),
}));
({
@@ -227,7 +235,7 @@ afterAll(() => {
vi.doUnmock("./runtime.js");
vi.doUnmock("./subagent-hooks.js");
vi.doUnmock("@larksuiteoapi/node-sdk");
vi.doUnmock("proxy-agent");
vi.doUnmock("@openclaw/proxyline");
vi.resetModules();
});

View File

@@ -159,6 +159,46 @@ describe("evaluateFilePolicy — denyPaths always wins", () => {
expect(r.ok ? "" : r.reason).toMatch(/deny/);
});
it("treats globstar slash as zero or more directories in denyPaths", () => {
withConfig({
n1: {
allowReadPaths: ["~/Downloads/**"],
denyPaths: ["~/Downloads/**/*.pem"],
},
});
const r = evaluateFilePolicy({
nodeId: "n1",
kind: "read",
path: path.join(os.homedir(), "Downloads", "key.pem"),
});
expectResultFields(r, { ok: false, code: "POLICY_DENIED", askable: false });
});
it("preserves minimatch brace semantics in denyPaths", () => {
withConfig({
n1: {
allowReadPaths: ["~/Downloads/**"],
denyPaths: ["~/Downloads/**/*.{pem,key}", "**/.{ssh,aws}/**"],
},
});
expectResultFields(
evaluateFilePolicy({
nodeId: "n1",
kind: "read",
path: path.join(os.homedir(), "Downloads", "api.key"),
}),
{ ok: false, code: "POLICY_DENIED", askable: false },
);
expectResultFields(
evaluateFilePolicy({
nodeId: "n1",
kind: "read",
path: path.join(os.homedir(), "Downloads", ".aws", "credentials"),
}),
{ ok: false, code: "POLICY_DENIED", askable: false },
);
});
it("denies even with ask=always (denyPaths is hard)", () => {
withConfig({
n1: {

View File

@@ -5,7 +5,7 @@ const webSocketCtorMock = vi.hoisted(() =>
}),
);
const proxyAgentCtorMock = vi.hoisted(() =>
vi.fn(function proxyAgentCtorMockImpl() {
vi.fn(function createAmbientNodeProxyAgentMockImpl() {
return { proxied: true };
}),
);
@@ -21,8 +21,16 @@ let createQQWSClient: CreateQQWSClient;
let priorProxyEnv: Partial<Record<ProxyEnvKey, string | undefined>> = {};
beforeAll(async () => {
vi.doMock("proxy-agent", () => ({
ProxyAgent: proxyAgentCtorMock,
vi.doMock("@openclaw/proxyline", () => ({
createAmbientNodeProxyAgent: proxyAgentCtorMock,
hasAmbientNodeProxyConfigured: vi.fn(() =>
Boolean(
process.env.HTTPS_PROXY ??
process.env.https_proxy ??
process.env.HTTP_PROXY ??
process.env.http_proxy,
),
),
}));
({ createQQWSClient } = await import("./ws-client.js"));
});

View File

@@ -1763,6 +1763,7 @@
"@modelcontextprotocol/sdk": "1.29.0",
"@mozilla/readability": "0.6.0",
"@openclaw/fs-safe": "0.2.4",
"@openclaw/proxyline": "0.2.0",
"ajv": "8.20.0",
"chalk": "5.6.2",
"chokidar": "5.0.0",
@@ -1773,7 +1774,6 @@
"file-type": "22.0.1",
"global-agent": "4.1.3",
"grammy": "1.42.0",
"https-proxy-agent": "9.0.0",
"ipaddr.js": "2.4.0",
"jiti": "2.7.0",
"json5": "2.2.3",
@@ -1781,12 +1781,10 @@
"kysely": "0.29.0",
"linkedom": "0.18.12",
"markdown-it": "14.1.1",
"minimatch": "10.2.5",
"node-edge-tts": "1.2.10",
"openai": "6.37.0",
"pdfjs-dist": "5.7.284",
"playwright-core": "1.60.0",
"proxy-agent": "8.0.1",
"qrcode": "1.5.4",
"tar": "7.5.15",
"tokenjuice": "0.7.0",

125
pnpm-lock.yaml generated
View File

@@ -86,6 +86,9 @@ importers:
'@openclaw/fs-safe':
specifier: 0.2.4
version: 0.2.4
'@openclaw/proxyline':
specifier: 0.2.0
version: 0.2.0
ajv:
specifier: 8.20.0
version: 8.20.0
@@ -116,9 +119,6 @@ importers:
grammy:
specifier: 1.42.0
version: 1.42.0
https-proxy-agent:
specifier: 9.0.0
version: 9.0.0
ipaddr.js:
specifier: 2.4.0
version: 2.4.0
@@ -140,9 +140,6 @@ importers:
markdown-it:
specifier: 14.1.1
version: 14.1.1
minimatch:
specifier: 10.2.5
version: 10.2.5
node-edge-tts:
specifier: 1.2.10
version: 1.2.10
@@ -155,9 +152,6 @@ importers:
playwright-core:
specifier: 1.60.0
version: 1.60.0
proxy-agent:
specifier: 8.0.1
version: 8.0.1
qrcode:
specifier: 1.5.4
version: 1.5.4
@@ -3329,6 +3323,10 @@ packages:
resolution: {integrity: sha512-Fo3WTQhxu0asD/rZqIKBqhX6fuZfjyHxSW5yTKfcRx+D9BRAcz0AGoVh+3ur/4XRvZkvsh3Ud8XTw006yRYLgg==}
engines: {node: '>=20.11'}
'@openclaw/proxyline@0.2.0':
resolution: {integrity: sha512-puZtDMshL/ikWQPXq6GPYT5z3994m++Rp1mMs3Kw4/J1xaAeAlCkBH21n2DhTDdWiLwAbpeFqV3OL3jfwv99EQ==}
engines: {node: '>=20.18.1'}
'@opentelemetry/api-logs@0.217.0':
resolution: {integrity: sha512-Cdq0jW2lknrNfrAm92MyEAvpe2cRsKjdnQLHUL6xRA4IVUnsWx6P65E7NcUO0Y+L4w1Aee5iV8FvjSwd+lrs9A==}
engines: {node: '>=8.0.0'}
@@ -5111,10 +5109,6 @@ packages:
resolution: {integrity: sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==}
engines: {node: '>= 14'}
data-uri-to-buffer@8.0.0:
resolution: {integrity: sha512-6UHfyCux51b8PTGDgveqtz1tvphBku5DrMKKJbFAZAJOI2zsjDpDoYE1+QGj7FOMS4BdTFNJsJiR3zEB0xH0yQ==}
engines: {node: '>= 20'}
data-urls@7.0.0:
resolution: {integrity: sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==}
engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
@@ -5168,12 +5162,6 @@ packages:
resolution: {integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==}
engines: {node: '>= 14'}
degenerator@7.0.1:
resolution: {integrity: sha512-ABErK0IefDSyHjlPH7WUEenIAX2rPPnrDcDM+TS3z3+zu9TfyKKi07BQM+8rmxpdE2y1v5fjjdoAS/x4D2U60w==}
engines: {node: '>= 20'}
peerDependencies:
quickjs-wasi: ^2.2.0
delayed-stream@1.0.0:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
engines: {node: '>=0.4.0'}
@@ -5582,10 +5570,6 @@ packages:
resolution: {integrity: sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==}
engines: {node: '>= 14'}
get-uri@8.0.0:
resolution: {integrity: sha512-CqtZlMKvfJeY0Zxv8wazDwXmSKmnMnsmNy8j8+wudi8EyG/pMUB1NqHc+Tv1QaNtpYsK9nOYjb7r7Ufu32RPSw==}
engines: {node: '>= 20'}
gifwrap@0.10.1:
resolution: {integrity: sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw==}
@@ -5719,10 +5703,6 @@ packages:
resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==}
engines: {node: '>= 14'}
http-proxy-agent@9.0.0:
resolution: {integrity: sha512-FcF8VhXYLQcxWCnt/cCpT2apKsRDUGeVEeMqGu4HSTu29U8Yw0TLOjdYIlDsYk3IkUh+taX4IDWpPcCqKDhCjA==}
engines: {node: '>= 20'}
http_ece@1.2.0:
resolution: {integrity: sha512-JrF8SSLVmcvc5NducxgyOrKXe3EsyHMgBFgSaIUGmArKe+rwr0uphRkRXvwiom3I+fpIfoItveHrfudL8/rxuA==}
engines: {node: '>=16'}
@@ -6650,20 +6630,10 @@ packages:
resolution: {integrity: sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==}
engines: {node: '>= 14'}
pac-proxy-agent@9.0.1:
resolution: {integrity: sha512-3ZOSpLboOlpW4yp8Cuv21KlTULRqyJ5Uuad3wXpSKFrxdNgcHEyoa22GRaZ2UlgCVuR6z+5BiavtYVvbajL/Yw==}
engines: {node: '>= 20'}
pac-resolver@7.0.1:
resolution: {integrity: sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==}
engines: {node: '>= 14'}
pac-resolver@9.0.1:
resolution: {integrity: sha512-lJbS008tmkj08VhoM8Hzuv/VE5tK9MS0OIQ/7+s0lIF+BYhiQWFYzkSpML7lXs9iBu2jfmzBTLzhe9n6BX+dYw==}
engines: {node: '>= 20'}
peerDependencies:
quickjs-wasi: ^2.2.0
pako@1.0.11:
resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==}
@@ -6836,10 +6806,6 @@ packages:
resolution: {integrity: sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==}
engines: {node: '>= 14'}
proxy-agent@8.0.1:
resolution: {integrity: sha512-kccqGBqHZXR8onQhY/ganJjoO8QIKKRiFBhPOzbTZK16attzSZ/0XSmp9H7jrRxPKHjhGyx1q32lMPrJ3uLFgA==}
engines: {node: '>= 20'}
proxy-from-env@1.1.0:
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
@@ -6929,9 +6895,6 @@ packages:
quick-format-unescaped@4.0.4:
resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==}
quickjs-wasi@2.2.0:
resolution: {integrity: sha512-zQxXmQMrEoD3S+jQdYsloq4qAuaxKFHZj6hHqOYGwB2iQZH+q9e/lf5zQPXCKOk0WJuAjzRFbO4KwHIp2D05Iw==}
range-parser@1.2.1:
resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
engines: {node: '>= 0.6'}
@@ -7208,10 +7171,6 @@ packages:
resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==}
engines: {node: '>= 6.0.0', npm: '>= 3.0.0'}
socks-proxy-agent@10.0.0:
resolution: {integrity: sha512-pyp2YR3mNxAMu0mGLtzs4g7O3uT4/9sQOLAKcViAkaS9fJWkud7nmaf6ZREFqQEi24IPkBcjfHjXhPTUWjo3uA==}
engines: {node: '>= 20'}
socks-proxy-agent@8.0.5:
resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==}
engines: {node: '>= 14'}
@@ -10009,6 +9968,10 @@ snapshots:
jszip: 3.10.1
tar: 7.5.15
'@openclaw/proxyline@0.2.0':
dependencies:
undici: 7.25.0
'@opentelemetry/api-logs@0.217.0':
dependencies:
'@opentelemetry/api': 1.9.1
@@ -11768,8 +11731,6 @@ snapshots:
data-uri-to-buffer@6.0.2: {}
data-uri-to-buffer@8.0.0: {}
data-urls@7.0.0(@noble/hashes@2.0.1):
dependencies:
whatwg-mimetype: 5.0.0
@@ -11820,13 +11781,6 @@ snapshots:
escodegen: 2.1.0
esprima: 4.0.1
degenerator@7.0.1(quickjs-wasi@2.2.0):
dependencies:
ast-types: 0.13.4
escodegen: 2.1.0
esprima: 4.0.1
quickjs-wasi: 2.2.0
delayed-stream@1.0.0: {}
depd@2.0.0: {}
@@ -12305,14 +12259,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
get-uri@8.0.0:
dependencies:
basic-ftp: 6.0.1
data-uri-to-buffer: 8.0.0
debug: 4.4.3
transitivePeerDependencies:
- supports-color
gifwrap@0.10.1:
dependencies:
image-q: 4.0.0
@@ -12518,13 +12464,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
http-proxy-agent@9.0.0:
dependencies:
agent-base: 9.0.0
debug: 4.4.3
transitivePeerDependencies:
- supports-color
http_ece@1.2.0: {}
https-proxy-agent@7.0.6:
@@ -13685,30 +13624,11 @@ snapshots:
transitivePeerDependencies:
- supports-color
pac-proxy-agent@9.0.1:
dependencies:
agent-base: 9.0.0
debug: 4.4.3
get-uri: 8.0.0
http-proxy-agent: 9.0.0
https-proxy-agent: 9.0.0
pac-resolver: 9.0.1(quickjs-wasi@2.2.0)
quickjs-wasi: 2.2.0
socks-proxy-agent: 10.0.0
transitivePeerDependencies:
- supports-color
pac-resolver@7.0.1:
dependencies:
degenerator: 5.0.1
netmask: 2.1.1
pac-resolver@9.0.1(quickjs-wasi@2.2.0):
dependencies:
degenerator: 7.0.1(quickjs-wasi@2.2.0)
netmask: 2.1.1
quickjs-wasi: 2.2.0
pako@1.0.11: {}
pako@2.1.0: {}
@@ -13878,19 +13798,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
proxy-agent@8.0.1:
dependencies:
agent-base: 9.0.0
debug: 4.4.3
http-proxy-agent: 9.0.0
https-proxy-agent: 9.0.0
lru-cache: 7.18.3
pac-proxy-agent: 9.0.1
proxy-from-env: 2.1.0
socks-proxy-agent: 10.0.0
transitivePeerDependencies:
- supports-color
proxy-from-env@1.1.0: {}
proxy-from-env@2.1.0: {}
@@ -14003,8 +13910,6 @@ snapshots:
quick-format-unescaped@4.0.4: {}
quickjs-wasi@2.2.0: {}
range-parser@1.2.1: {}
raw-body@3.0.2:
@@ -14366,14 +14271,6 @@ snapshots:
smart-buffer@4.2.0: {}
socks-proxy-agent@10.0.0:
dependencies:
agent-base: 9.0.0
debug: 4.4.3
socks: 2.8.9
transitivePeerDependencies:
- supports-color
socks-proxy-agent@8.0.5:
dependencies:
agent-base: 7.1.4

View File

@@ -8,6 +8,7 @@ minimumReleaseAge: 2880
minimumReleaseAgeExclude:
- "@openclaw/fs-safe@0.2.4"
- "@openclaw/proxyline@0.2.0"
- "acpx"
- "tokenjuice"
- "@agentclientprotocol/sdk"

View File

@@ -1,6 +1,6 @@
import { createAmbientNodeProxyAgent, hasAmbientNodeProxyConfigured } from "@openclaw/proxyline";
import type { z } from "zod";
import type { OpenClawConfig } from "../config/config.js";
import { hasEnvHttpProxyConfigured } from "../infra/net/proxy-env.js";
import { resolveDefaultSecretProviderAlias } from "../secrets/ref-contract.js";
import { runPassiveAccountLifecycle } from "./channel-lifecycle.core.js";
import { createLoggerBackedRuntime } from "./runtime-logger.js";
@@ -227,25 +227,22 @@ export function readPluginPackageVersion(params: {
return params.fallback ?? "unknown";
}
let proxyAgentConstructorPromise: Promise<typeof import("proxy-agent").ProxyAgent> | null = null;
async function loadProxyAgentConstructor(): Promise<typeof import("proxy-agent").ProxyAgent> {
proxyAgentConstructorPromise ??= import("proxy-agent").then(({ ProxyAgent }) => ProxyAgent);
return proxyAgentConstructorPromise;
}
export async function resolveAmbientNodeProxyAgent<TAgent>(params?: {
onError?: (error: unknown) => void;
onUsingProxy?: () => void;
protocol?: "http" | "https";
}): Promise<TAgent | undefined> {
if (!hasEnvHttpProxyConfigured(params?.protocol ?? "https")) {
const protocol = params?.protocol ?? "https";
if (!hasAmbientNodeProxyConfigured({ protocol })) {
return undefined;
}
try {
const ProxyAgent = await loadProxyAgentConstructor();
const agent = createAmbientNodeProxyAgent({ protocol });
if (agent === undefined) {
return undefined;
}
params?.onUsingProxy?.();
return new ProxyAgent() as TAgent;
return agent as TAgent;
} catch (error) {
params?.onError?.(error);
return undefined;

View File

@@ -1,7 +1,7 @@
import { randomUUID } from "node:crypto";
import type { Agent } from "node:http";
import { createRequire } from "node:module";
import process from "node:process";
import { createAmbientNodeProxyAgent } from "@openclaw/proxyline";
import {
resolveDebugProxyBlobDir,
resolveDebugProxyCertDir,
@@ -28,14 +28,6 @@ export type DebugProxySettings = {
};
let cachedImplicitSessionId: string | undefined;
let cachedHttpsProxyAgent: typeof import("https-proxy-agent").HttpsProxyAgent | undefined;
function loadHttpsProxyAgent(): typeof import("https-proxy-agent").HttpsProxyAgent {
cachedHttpsProxyAgent ??= (
createRequire(import.meta.url)("https-proxy-agent") as typeof import("https-proxy-agent")
).HttpsProxyAgent;
return cachedHttpsProxyAgent;
}
function isTruthy(value: string | undefined): boolean {
return value === "1" || value === "true" || value === "yes" || value === "on";
@@ -88,8 +80,19 @@ export function createDebugProxyWebSocketAgent(settings: DebugProxySettings): Ag
if (!settings.enabled || !settings.proxyUrl) {
return undefined;
}
const HttpsProxyAgent = loadHttpsProxyAgent();
return new HttpsProxyAgent(settings.proxyUrl);
return createAmbientNodeProxyAgent({
protocol: "https",
env: {
HTTP_PROXY: settings.proxyUrl,
HTTPS_PROXY: settings.proxyUrl,
ALL_PROXY: undefined,
NO_PROXY: undefined,
http_proxy: undefined,
https_proxy: undefined,
all_proxy: undefined,
no_proxy: undefined,
},
}) as Agent | undefined;
}
export function resolveEffectiveDebugProxyUrl(configuredProxyUrl?: string): string | undefined {