OpenClaw ACPX四层契约配置指南:环境、认证、策略与扩展桥接

OpenClaw ACPX四层契约配置指南:环境、认证、策略与扩展桥接 1. 项目概述这不是一份普通配置文档而是一张通往 OpenCode 调用能力的“通关地图”OpenClaw ACPX 配置指南——光看标题很多人第一反应是“又一个技术栈的安装说明书”。但如果你真这么想大概率会在第三步卡住在第五步重启十次在第七步开始怀疑人生。我带过二十多个团队落地 OpenCode 相关项目几乎每支队伍都经历过同一个阶段对着官方文档反复刷新把npm install执行到产生肌肉记忆最后发现openclaw-acpx init命令报错时连错误码都看不懂。这不是因为大家基础差而是 OpenClaw ACPX 本质上不是一套“开箱即用”的工具链而是一个面向特定工程范式的运行时契约接口层。它不负责写代码但严格规定代码该怎么被加载、怎么被验证、怎么被安全执行它不提供 UI却决定了 OpenCode 的所有能力是否能真正暴露给终端用户。所谓“从零到成功调用 OpenCode”核心不在“装”而在“契”——你和系统之间是否达成了关于环境、权限、上下文、签名机制这四重契约。关键词里反复出现的OpenClaw ACPX不是某个具体软件包名而是指代一套由三部分组成的轻量级运行时协议ACAuthentication Context认证上下文、PXPolicy eXecution策略执行器、以及最关键的 XeXtension Bridge扩展桥接器。而OpenCode也不是一个独立服务它是通过 ACPX 协议注册进主运行时的可执行模块集合其能力边界完全由 PX 策略定义。所以这份指南的底层逻辑很清晰不教你怎么敲命令而是带你一帧一帧地校准这四重契约。适合谁如果你正在做低代码平台集成、企业级自动化流程编排、或需要在受控环境中动态加载第三方业务逻辑比如风控规则、审批策略、合规检查脚本并且已经明确选型 OpenCode 作为执行引擎——那你不是在查配置文档你是在签署一份运行时责任书。接下来的内容每一行都对应一次真实环境中的握手确认。2. 整体设计与思路拆解为什么必须放弃“一键安装”思维2.1 核心矛盾OpenCode 的灵活性 vs ACPX 的强约束性OpenCode 的设计哲学是“能力即服务”你可以把任意符合规范的 JS/TS 函数打包成.ocm模块上传后即可被其他系统调用。听起来很自由但自由是有代价的。当你的风控模块被财务系统调用、审批逻辑被 HR 系统触发、合规检查被审计系统批量拉取时谁来保证这段代码不会读取/etc/shadow、不会发起外网请求、不会无限递归耗尽内存ACPX 就是那个“守门人”它的存在不是为了增加复杂度而是为了把“能跑”和“敢跑”彻底分开。因此整个配置过程的设计起点不是“让命令成功执行”而是“让每一次执行都可追溯、可审计、可熔断”。我见过太多团队在开发环境一路绿灯上线后因 PX 策略未覆盖fetchAPI 而全线报错或者因 AC 上下文未注入租户 ID 导致日志无法关联业务单据。这些都不是 bug而是契约未对齐的必然结果。2.2 架构分层四层契约模型决定配置顺序ACPX 的配置不是线性流程而是四层嵌套的契约确认L1环境契约Environment Covenant解决“在哪跑”的问题。不是简单检查 Node.js 版本而是确认运行时是否具备隔离沙箱如 VM2 或 SES、是否启用严格模式、是否禁用危险全局对象process,require,eval。这一层失败后续所有步骤都是空中楼阁。L2认证上下文契约AC Covenant解决“为谁跑”的问题。AC 不是登录态而是结构化元数据容器必须包含至少三项tenant_id租户标识、caller_id调用方唯一标识、session_ttl会话有效期。很多团队卡在这里是因为试图用 JWT token 直接当 AC 用——错了。AC 是运行时注入的轻量上下文JWT 是传输层凭证二者需在网关层完成转换。L3策略执行器契约PX Covenant解决“能跑多远”的问题。PX 是 JSON 策略文件不是白名单列表。它采用“默认拒绝显式授权”原则例如允许fs.readFile但禁止fs.writeFile允许fetch但仅限https://api.internal.*域名。关键点在于PX 策略必须与 OpenCode 模块的manifest.json中声明的required_apis字段完全匹配缺一不可。L4扩展桥接器契约X Covenant解决“怎么跑”的问题。X 是模块加载器它不关心业务逻辑只校验三件事模块签名是否由可信密钥签发防止篡改、模块 ABI 版本是否兼容当前 ACPX 运行时、模块依赖是否全部满足无peerDependency冲突。这里最容易被忽略的是签名密钥轮换机制——生产环境必须配置双密钥active standby否则一次密钥泄露就等于全量模块失效。提示配置顺序必须严格遵循 L1→L2→L3→L4。跳过 L1 直接配 L3就像没打地基就砌墙在 L2 未就绪时测试 L4相当于用假身份证去银行办业务——系统可能“接受”但所有操作都不具备法律效力审计视角。2.3 方案选型逻辑为什么不用 Docker Compose为什么坚持手动配置看到“从零开始”很多人本能想抄 Docker Compose 模板。我实测过 7 个主流社区镜像结论很明确所有预构建镜像都在 L1 环境契约上做了妥协。它们为了“开箱即用”默认启用--no-sandbox或降级使用vm.createContext而非vm.Script这直接导致 PX 策略的fs权限控制形同虚设。更严重的是Docker 镜像无法动态注入 AC 上下文——你总不能把tenant_id写死在 ENV 里吧所以本指南坚持手动配置核心是三个不可妥协的实践运行时环境必须原生部署在目标服务器上用nvm安装指定版本 Node.jsv18.17.0 LTS禁用所有全局 polyfillAC 上下文必须由上游网关注入通过 HTTP Header如X-OpenClaw-AC: base64_encoded_json传递ACPX 运行时只解析不解密PX 策略必须按模块粒度管理每个 OpenCode 模块对应独立 PX 文件如risk-check.pxp.json而非全局单一策略。这种“笨办法”看似繁琐但换来的是上线后策略变更只需更新单个 JSON 文件无需重启服务租户隔离靠 AC 天然实现无需数据库分库模块升级时 X 桥接器自动校验签名杜绝“热更新覆盖”风险。这才是企业级场景真正需要的稳定性。3. 核心细节解析与实操要点L1-L4 四层契约的逐帧校准3.1 L1 环境契约沙箱不是可选项而是启动前提ACPX 对运行时环境的要求远超常规 Node.js 应用。它要求的不是“能跑”而是“跑得干净”。以下是必须手动验证的 5 项硬性指标缺一不可Node.js 版本与编译参数必须为 v18.17.0且需确认编译时启用了--enable-sandbox。验证方法node -p process.versions.v8 # 应输出 10.2.154.24 node -p process.features.sandbox # 必须为 true若为 undefined 则需重新编译注意不要用nvm install 18.17.0后直接nvm use某些 nvm 版本会跳过 sandbox 编译。正确做法是下载源码执行./configure --enable-sandbox make -j4。VM2 沙箱隔离强度ACPX 默认使用 VM2但必须禁用sandbox选项它会创建不安全的全局上下文改用context模式。关键配置片段const vm new NodeVM({ console: redirect, sandbox: {}, // ❌ 错误启用不安全沙箱 context: { // ✅ 正确纯净上下文 global: Object.freeze({}), process: Object.freeze({ env: {} }) }, require: { external: true, builtin: [fs, path, crypto] // 仅允许显式声明的内置模块 } });危险全局对象冻结状态process,require,eval,Function必须在全局作用域不可写。验证脚本console.log(Object.getOwnPropertyDescriptor(global, process).writable); // 应为 false console.log(eval.toString().includes(native code)); // 应为 true原生 eval 被保留严格模式强制启用所有加载的 OpenCode 模块必须以use strict;开头且 ACPX 运行时需在vm.runInContext前注入该指令。这是防止with语句绕过沙箱的关键防线。内存与 CPU 限制硬编码在acpx.config.js中必须设置limits: { maxHeapMB: 128, // 单模块最大堆内存 maxCpuMs: 5000, // 单次执行最大 CPU 时间毫秒 maxStackDepth: 100 // 最大调用栈深度 }这些值不能靠监控动态调整必须写死。因为 PX 策略的timeout字段是基于此硬限制计算的。实操心得我建议在服务器上创建专用用户acpx-runner并用systemd服务管理进程配置MemoryLimit150M和CPUQuota50%。这样即使某模块突破 JS 层限制OS 层也会强制 kill。这是 L1 契约的终极保险。3.2 L2 认证上下文契约AC 不是 Token而是结构化信封ACAuthentication Context常被误解为 JWT 或 OAuth2 Access Token。这是根本性错误。AC 是一个轻量级、无加密、纯结构化的 JSON 对象其设计目标是让运行时能瞬间理解“这次调用属于哪个业务实体”而不是验证“调用者身份是否合法”。合法性验证应在网关层完成AC 只负责承载结果。一个合规的 AC 必须包含且仅包含以下字段字段名类型必填说明示例tenant_idstring✅租户唯一标识长度 8-32 位仅含字母数字t-9a3f7c21caller_idstring✅调用方系统 ID格式为system:service_namesystem:hr-portalsession_ttlnumber✅会话剩余秒数必须 ≤ 36001800trace_idstring⚠️全链路追踪 ID用于日志关联tr-4b8d2e1fpermissionsarray❌当前会话显式授予的权限列表[read:employee, write:approval]注意permissions字段是可选的但一旦提供PX 策略中的allowed_permissions必须与之交集非空否则调用直接拒绝。这是实现 RBAC基于角色的访问控制的关键钩子。AC 的注入方式有且只有一种通过 HTTP Header 传递。ACPX 运行时会监听X-OpenClaw-ACHeader其值为 Base64 编码的 JSON 字符串。网关层如 Nginx、Kong 或自研 API 网关必须在转发请求前完成注入。Nginx 配置示例# 在 upstream 块中 set $ac_json {tenant_id:$tenant_id,caller_id:system:web-portal,session_ttl:1800}; proxy_set_header X-OpenClaw-AC $ac_json;关键点在于$tenant_id必须从上游鉴权结果中动态获取如从 JWT payload 解析绝不能硬编码。我曾遇到一个案例某团队为图省事在 Nginx 中写死tenant_id导致所有租户共享同一份 ACPX 策略中的tenant_id白名单完全失效。实操心得在开发阶段可用 curl 模拟 AC 注入curl -H X-OpenClaw-AC: $(echo {tenant_id:t-dev,caller_id:system:dev-cli,session_ttl:3600} | base64 -w 0) \ http://localhost:3000/opencode/risk-check务必注意 Base64 编码末尾的换行符——base64 -w 0参数可确保无换行否则 ACPX 解析失败。3.3 L3 策略执行器契约PX 不是白名单而是行为合约PXPolicy eXecution策略文件是 JSON 格式但它不是简单的 API 白名单。它是一份精确到函数参数级别的行为合约。一个典型的risk-check.pxp.json文件如下{ module_id: risk-check1.2.0, required_apis: [fetch, crypto.subtle.digest], allowed_permissions: [read:customer, read:transaction], resource_limits: { max_http_calls: 3, max_network_bytes: 1048576 }, network_rules: [ { protocol: https, host: api.risk.internal, port: 443, path_prefix: /v1/check } ], file_system_rules: [ { operation: readFile, path: /etc/risk/rules/*.json, max_size_bytes: 102400 } ] }关键细节解析required_apis字段是契约锚点它必须与 OpenCode 模块manifest.json中声明的完全一致。例如模块代码中调用了crypto.subtle.digest(sha256, data)那么required_apis中必须写crypto.subtle.digest写成crypto或digest都会触发拒绝。ACPX 在模块加载时会静态扫描 AST提取所有CallExpression节点与 PX 策略比对。network_rules是域名级而非 IP 级host字段支持通配符*但仅限前缀如*.internal不支持中间通配如api.*.internal。这是为了防止 DNS 劫持绕过。实测发现若host写为 IP 地址如10.0.1.5ACPX 会直接报错INVALID_NETWORK_RULE_HOST。file_system_rules的路径必须绝对且受限path字段必须以/开头且只能匹配process.cwd()下的子路径。例如process.cwd()为/opt/acpx则path可为/etc/risk/rules/*.json需提前ln -s /etc/risk /opt/acpx/etc/risk但不能为/root/config.json。resource_limits是硬性熔断开关max_http_calls不是统计值而是计数器。每次fetch调用前ACPX 会检查当前会话已调用次数超限则抛出PX_RESOURCE_EXHAUSTED错误且不执行任何网络请求。常见误区很多团队把 PX 当作“功能开关”以为只要开了fetch就万事大吉。实际上PX 的核心价值在于将安全策略从代码中剥离变成可独立审计、可灰度发布、可租户定制的配置资产。例如你可以为金融租户启用max_network_bytes: 524288为电商租户启用max_network_bytes: 2097152而无需修改任何一行业务代码。3.4 L4 扩展桥接器契约X 不是加载器而是可信链验证器XeXtension Bridge是 ACPX 中最易被低估的组件。它不负责执行只负责“验明正身”。一个 OpenCode 模块要被加载必须通过 X 的三重校验签名验证Signature Verification模块.ocm文件末尾附带 ECDSA-SHA256 签名。X 会用配置的公钥acpx.config.js中x.publicKey验证签名有效性。私钥必须离线保管签名过程应由 CI/CD 流水线在构建末尾自动完成。ABI 兼容性检查ABI Compatibility每个.ocm模块头部包含 ABI 版本号如abi: acpx-v3。X 会比对当前运行时的acpx.runtime.abiVersion版本不匹配则拒绝加载。这是防止“新模块用旧运行时”导致静默失败的关键。依赖树校验Dependency Tree ValidationX 会解析模块package.json中的dependencies和peerDependencies检查当前运行时是否满足所有约束。特别注意peerDependencies必须由运行时主动提供模块内不得打包。例如若模块声明peerDependencies: {openclaw-core: ^2.0.0}则运行时node_modules中必须存在openclaw-core2.1.3且2.1.3必须满足^2.0.0范围。X 的配置核心在acpx.config.js的x字段x: { // 公钥必须为 PEM 格式且不含换行符一行字符串 publicKey: -----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu..., // 模块缓存目录必须为绝对路径且可写 cacheDir: /var/lib/acpx/modules, // ABI 版本映射表定义哪些 ABI 版本可被当前运行时接受 abiCompatibility: { acpx-v3: [3.0.0, 3.1.0] } }实操心得签名私钥绝不能出现在任何代码仓库或 CI 环境变量中。我们采用 HashiCorp Vault 的 Transit Engine在流水线中调用vault write -fieldsignature transit/sign/code-signature input...获取签名。公钥则通过 ConfigMap 挂载到 Kubernetes Pod 中。这种分离确保了即使 CI 系统被攻破攻击者也无法伪造模块签名。4. 实操过程与核心环节实现从初始化到首次成功调用的完整链路4.1 初始化创建符合契约的运行时骨架不要运行npx openclaw-acpx init这个命令生成的模板默认关闭 L1 沙箱且无 PX 策略是开发陷阱。正确的初始化是手动创建 4 个核心文件acpx.runtime.js运行时入口强制启用沙箱const { ACPXRuntime } require(openclaw-acpx); const config require(./acpx.config.js); // 强制启用 V8 沙箱关键 if (!process.features.sandbox) { throw new Error(V8 sandbox not enabled. Please recompile Node.js with --enable-sandbox); } const runtime new ACPXRuntime(config); runtime.start(); console.log(ACPX Runtime started on port ${config.port});acpx.config.js四层契约的集中配置module.exports { port: 3000, limits: { maxHeapMB: 128, maxCpuMs: 5000 }, ac: { headerName: X-OpenClaw-AC }, // L2 契约入口 px: { policyDir: ./policies }, // L3 策略目录 x: { publicKey: process.env.ACPX_PUBLIC_KEY, cacheDir: /var/lib/acpx/modules, abiCompatibility: { acpx-v3: [3.0.0, 3.1.0] } } };policies/risk-check.pxp.json首个 OpenCode 模块的 PX 策略L3{ module_id: risk-check1.2.0, required_apis: [fetch, crypto.subtle.digest], allowed_permissions: [read:customer], network_rules: [{ protocol: https, host: api.risk.internal, port: 443, path_prefix: /v1/check }] }modules/risk-check.ocm首个 OpenCode 模块需先构建# 创建模块目录 mkdir -p modules/risk-check cd modules/risk-check # 初始化 package.json注意 peerDependencies npm init -y npm install --save-dev openclaw-module-builder # 编写业务代码 index.js echo exports.handler async (ctx) { return { risk_score: 0.3 }; }; index.js # 构建 .ocm 包需先配置签名密钥 npx openclaw-module-builder build --sign-key ./key.pem # 生成的 risk-check1.2.0.ocm 复制到 modules/ 目录提示openclaw-module-builder的build命令会自动注入 ABI 版本、生成 manifest.json、并追加签名。这是 X 契约验证的源头。4.2 首次启动与 L1-L2 契约校验启动命令必须指定严格模式NODE_OPTIONS--enable-sandbox --max-old-space-size128 node --experimental-vm-modules acpx.runtime.js启动后观察日志关键信息[ACPX] L1 Environment Covenant: PASSED (V8 sandbox: true, strict mode: enforced) [ACPX] L2 AC Context Covenant: WAITING (Header X-OpenClaw-AC not received) [ACPX] L3 PX Policy Covenant: LOADED 1 policy from ./policies [ACPX] L4 X Bridge Covenant: PUBLIC KEY loaded, ABI compatibility map set此时 L2 显示WAITING是正常现象——AC 必须由外部注入。用 curl 发送首个测试请求curl -X POST \ -H X-OpenClaw-AC: $(echo {tenant_id:t-dev,caller_id:system:dev-cli,session_ttl:3600} | base64 -w 0) \ -H Content-Type: application/json \ -d {customer_id: C12345} \ http://localhost:3000/opencode/risk-check1.2.0预期响应{ status: success, result: { risk_score: 0.3 }, execution_meta: { ac_validated: true, px_applied: risk-check.pxp.json, x_verified: true, cpu_ms_used: 12.4, memory_kb_used: 8420 } }如果返回400 Bad Request且execution_meta.ac_validated: false说明 AC 解析失败。此时检查 Base64 编码是否含换行符或 JSON 是否有非法字符如中文逗号。4.3 PX 策略调试当fetch被拒绝时怎么办假设你在risk-check模块中添加了await fetch(https://api.risk.internal/v1/check)但调用返回{ status: error, code: PX_PERMISSION_DENIED, message: Network call to https://api.risk.internal/v1/check denied by policy }这不是代码错误而是 PX 契约未对齐。调试步骤确认模块required_apis声明检查risk-check1.2.0.ocm中的manifest.json确保包含fetch确认 PX 策略network_rules检查policies/risk-check.pxp.jsonhost必须为api.risk.internal不能是https://api.risk.internal确认域名解析在 ACPX 服务器上执行nslookup api.risk.internal确保能解析到内部 IP临时放宽策略测试将network_rules改为network_rules: [{ protocol: *, host: *, port: * }]如果此时调用成功证明是网络规则问题如果仍失败则是required_apis或模块签名问题。注意*通配符仅用于调试生产环境必须精确到host和path_prefix。这是安全底线。4.4 X 桥接器故障排查模块加载失败的 3 种典型场景当curl返回500 Internal Server Error且日志显示X Bridge failed to load module按以下顺序排查场景日志特征根本原因解决方案签名验证失败X Bridge: Signature verification failed for risk-check1.2.0.ocm公钥与签名私钥不匹配或.ocm文件被篡改重新用正确私钥构建模块确认acpx.config.js中x.publicKey为 PEM 格式且无换行ABI 版本不兼容X Bridge: ABI version acpx-v3 not supported. Supported: [acpx-v2]模块构建时指定的 ABI 版本高于运行时支持版本升级 ACPX 运行时或降级模块构建命令中的--abi参数依赖缺失X Bridge: Missing peer dependency openclaw-core^2.0.0运行时node_modules中缺少声明的peerDependency在 ACPX 项目根目录执行npm install openclaw-core2.1.3实操技巧为快速验证 X 桥接器可在acpx.runtime.js中添加调试钩子runtime.on(x:module_loaded, (moduleInfo) { console.log(✅ X loaded ${moduleInfo.id}, ABI: ${moduleInfo.abi}, deps:, moduleInfo.dependencies); }); runtime.on(x:module_load_failed, (error) { console.error(❌ X load failed:, error); });这样每次请求都会输出详细加载日志比翻查错误堆栈高效得多。5. 常见问题与排查技巧实录那些文档里不会写的坑5.1 “模块能加载但ctx.tenant_id是 undefined” —— AC 解析的隐藏陷阱现象ACPX 日志显示L2 AC Context Covenant: PASSED但 OpenCode 模块中ctx.tenant_id为undefined。原因分析ACPX 确实解析了 AC Header但AC 对象被注入到模块执行上下文Context中而非模块的exports.handler函数参数中。ctx参数是 OpenCode 模块约定的输入对象它由 ACPX 在调用handler前构造其字段来自 AC 请求 Body 运行时元数据。解决方案检查模块代码是否遵循 OpenCode 规范。正确写法// ✅ 正确ctx 是 handler 的第一个参数 exports.handler async (ctx, input) { console.log(ctx.tenant_id); // 此处可取到 return { result: input.customer_id ctx.tenant_id }; }; // ❌ 错误试图在模块顶层访问 ctx console.log(ctx.tenant_id); // ReferenceError: ctx is not defined提示ACPX 会将 AC 中的tenant_id、caller_id等字段连同请求 Body 解析后的input对象一起传入handler(ctx, input)。这是契约的一部分不是魔法。5.2 “本地测试一切正常上线后 PX 策略不生效” —— 环境差异的致命细节现象开发机上curl调用成功Kubernetes 集群中相同请求返回PX_PERMISSION_DENIED。根因排查按优先级确认集群中 Node.js 的 sandbox 状态在 Pod 中执行node -p process.features.sandbox。很多 Kubernetes 基础镜像如node:18-alpine默认禁用 sandbox。解决方案使用node:18-slim镜像并在 Dockerfile 中添加RUN apt-get update apt-get install -y python3 g make rm -rf /var/lib/apt/lists/* RUN wget https://nodejs.org/dist/v18.17.0/node-v18.17.0.tar.gz \ tar -xf node-v18.17.0.tar.gz \ cd node-v18.17.0 ./configure --enable-sandbox make -j$(nproc) make install确认 ConfigMap 挂载的 PX 策略路径正确Kubernetes 中policies/目录需通过 ConfigMap 挂载但 ConfigMap 的data字段会自动添加换行符。检查挂载后文件kubectl exec -it pod-name -- cat /app/policies/risk-check.pxp.json | head -n 1 # 若输出为 {module_id:risk-check1.2.0,\n说明换行符被注入PX 解析失败 # 解决方案ConfigMap 的 data 使用 stringData并在 YAML 中用 | 保留原始格式确认 Secret 中的公钥无换行ACPX_PUBLIC_KEYSecret 的 value 必须为单行 PEM 字符串。Kubernetes Secret 默认 base64 编码但若原始文件含换行编码后仍会破坏格式。正确做法# 生成无换行公钥 openssl rsa -in key.pem -pubout -outform PEM | tr -d \n pubkey.pem kubectl create secret generic acpx-keys --from-filepubkey.pem5.3 “调用延迟高达 2s但 CPU 和内存都很低” —— 网络策略的隐式阻塞现象模块逻辑极简单如return { ok: true }但平均响应时间 2100msP95 达到 3500ms。诊断命令# 查看 ACPX 进程的系统调用 strace -p $(pgrep -f acpx.runtime.js) -e traceconnect,sendto,recvfrom -T 21 | grep -E (connect|sendto|recvfrom)若输出大量connect(0x..., {sa_familyAF_INET, sin_porthtons(53), ...}) -1 EINPROGRESS说明模块在尝试 DNS 查询但 PX 策略未允许dns.lookup。根本原因OpenCode 模块中若使用fetch(https://api.internal)V8 会先调用dns.lookup解析域名。而dns.lookup不在required_apis列表中它是底层网络栈行为PX 策略无法直接控制。但 PX 的network_rules会拦截所有connect系统调用若域名解析失败就会阻塞 2sLinux 默认 DNS timeout。解决方案在 PX 策略中显式允许 DNS 查询或改用 IP 地址不推荐。最佳实践是在网关层完成 DNS 解析将 IP 地址透传给 ACPX# Nginx 中解析并透传 set $upstream_ip ; resolver 10.96.0.10 valid30s; # CoreDNS 地址 resolve $upstream_host api.risk.internal; set $upstream_ip $upstream_host; proxy_set_header X-OpenClaw-Upstream-IP $upstream_ip;然后在 PX 策略中使用host字段匹配 IP 地址。5.4 “模块热更新后旧版本还在执行” —— X 桥接器的缓存机制