尧图精选

Paperclip:AI工具链协议桥接代理的核心原理与工程实践

🕒 发布时间:2026/10/1 5:54:01 📁 来源:尧图网络
1. 项目概述Paperclip 不是回形针而是一个被严重误读的 AI 工程化枢纽你搜“paperclip”第一反应可能是办公桌抽屉里那个弯弯扭扭的金属小物件——但在这个技术语境下它根本不是物理世界里的文具。它是一个代号一个在近期开发者社区里高频闪现、却始终缺乏官方定义的工程化符号。它不隶属于 Node.js 官方生态不是 React 的新 Hooks更不是 OpenClaw 或 Claude 的子模块。它真实存在但它的“身份”恰恰藏在那些热搜词的缝隙里当人们反复搜索“openclaw 无法安全验证”、“claude code desktop 国内下载”、“react sse 轮询文件变化”时背后真正卡住的往往不是某个单一工具而是整个本地 AI 开发流中缺失的那个“粘合层”——Paperclip就是这个粘合层的代称。我第一次在 GitHub 的一个私有仓库 issue 里看到这个词是在调试一个 OpenClaw Claude Code Desktop 自研 React 前端的三端联调失败时。报错信息是Error: claude native binary not installed但claude-code --version明明能跑通wsl --status显示正常可 OpenClaw 就是死活连不上本地 Claude 实例。最后发现问题出在一个被忽略的中间进程它负责监听 Claude 的 IPC 端口、将 OpenClaw 的 JSON-RPC 请求转换为 Claude Code Desktop 的 WebSocket 协议、再把响应反向封装回 OpenClaw 能识别的格式——这个进程的启动脚本文件名就叫paperclip.js。它没有 npm 包没有文档甚至没有 README但它像胶水一样把三个原本互不兼容的系统强行焊在了一起。所以 Paperclip 的本质是一个轻量级、协议桥接型的本地代理服务。它解决的核心问题是AI 工具链碎片化带来的协议鸿沟。Claude Code Desktop 用的是自定义 WebSocketIPC 混合协议OpenClaw 默认走 HTTP/REST但其企业版又支持 gRPCReact 前端要实时获取代码生成状态最自然的选择是 SSE但 Claude 并不原生暴露 SSE 接口。Paperclip 就是那个站在中间左手接 A 的输出右手喂 B 的输入还顺手给 C 做了数据格式标准化的“翻译官”。它不处理模型推理不管理 UI 渲染也不做权限控制——它只做一件事让不同协议、不同进程、不同安全上下文的组件能像同一个进程里的函数调用一样通信。这正是为什么你在所有官方文档里都找不到它却在无数开发者的本地node_modules/.bin目录或~/.local/bin下发现它的身影它不是产品是生存策略。2. 核心设计逻辑与架构选型深度拆解2.1 为什么必须是 Node.js而不是 Rust 或 Python看到这里你可能会问既然是个协议桥接服务为什么几乎清一色用 Node.js 实现Rust 性能更好Python 生态更成熟Docker 镜像也更小。答案藏在三个硬性约束里。第一是进程间通信IPC的兼容性。Claude Code Desktop 在 Windows 上依赖 Windows Subsystem for Linux (WSL) 的 Unix Domain Socket而在 macOS 上则使用 macOS 的launchdsocketOpenClaw 在 Linux 服务器上常以 systemd service 启动暴露的是 TCP 端口React 开发服务器Vite/webpack dev server则运行在 localhost:3000走的是 HTTP。Node.js 的net、http、https、fs用于 Unix socket模块能无缝覆盖这全部四类通信原语且 API 风格高度统一。我试过用 Python 的asyncio实现同样的多协议监听结果在 WSL 环境下AF_UNIXsocket 的路径解析会因/mnt/wsl和/home的挂载点差异而随机失败Rust 的tokio虽然强大但对 Windows 上的 Named Pipe 支持需要额外 crate且错误堆栈极其晦涩一次EACCES权限错误就能卡住三天。第二是与前端生态的零成本集成。Paperclip 的核心任务之一是把 Claude 的原始 token 流如{type:token,content:const}转换成 React 前端能直接消费的 SSE 格式data: {type:token,content:const}\n\n。Node.js 的EventEmitter和ReadableStream天然支持这种流式转换一行stream.pipe(res)就能搞定。换成 Python你需要手动管理asyncio.Queue的背压换成 Rust得写tokio::sync::mpsc通道并处理Sendtrait 约束。而 Paperclip 的典型部署场景是和 Vite 开发服务器共存于同一台开发者机器——Node.js 进程可以直接require(./paperclip)共享内存、复用package.json的engines字段约束连.nvmrc都不用改。第三是调试友好性。当 OpenClaw 报错connection refused时你不可能去翻 Rust 的cargo run --release日志。Paperclip 的日志必须能一眼看出是哪一端断了是 Claude 的 WebSocket 连接超时还是 OpenClaw 的 HTTP POST 被 CORS 拦截Node.js 的console.log加上util.inspect的深度展开配合 VS Code 的 Attach to Process 调试能让问题定位时间从小时级降到分钟级。我见过最典型的案例一个团队用 Python 写的类似服务在生产环境偶发BrokenPipeError查了两周才发现是 WSL 的max_connections限制被突破而 Node.js 的net.Server.maxConnections属性一行就能配置。提示不要被“Node.js 是单线程”的旧观念误导。Paperclip 的瓶颈从来不在 CPU而在 I/O 等待。Node.js 的事件循环模型恰恰是最适合处理大量并发短连接的——它不像 Python 的threading那样有 GIL 锁也不像 Rust 那样需要显式管理ArcMutex。一个 Paperclip 实例轻松支撑 50 OpenClaw 客户端和 3 个 Claude 实例内存占用稳定在 80MB 以内。2.2 为什么选择 React 作为前端载体而非 Electron 或 TauriPaperclip 本身是个后端服务但它必然配套一个最小化前端控制台——这是所有实际部署中不可或缺的部分。这个控制台要干三件事显示当前连接状态Claude 是否在线、OpenClaw 是否已注册、提供手动触发重连的按钮、展示最近 100 条协议转换日志。那么为什么几乎所有开源实现都用 React而不是更“原生”的方案根本原因在于开发效率与调试确定性。Electron 的主进程/渲染进程模型会让 Paperclip 的 IPC 调试变成噩梦你得同时打开 DevTools 的主进程和渲染进程两个窗口日志分散在两处Tauri 虽然更轻量但其tauri://协议在本地开发时需额外配置devPath且热重载支持远不如 Vite。而 React Vite 的组合让你能用npm run dev一键启动前端用npm run paperclip启动后端两者通过http://localhost:3000/api/paperclip/status通信——这个 URL 在任何浏览器里都能直接访问返回 JSON无需任何客户端 SDK。更重要的是React 的组件化思维天然契合 Paperclip 的状态管理需求。比如“连接状态”这个概念它其实是三个子状态的聚合claudeStatusWebSocket 连接、openclawStatusHTTP 健康检查、proxyStatus内部路由表是否就绪。用 React 的useReducer或 Zustand你可以把这三者声明为独立的原子状态再用一个useEffect监听它们的变化自动计算出最终的overallStatus。换成 Electron 的ipcRenderer.sendSync你得手动维护一个全局状态对象每次更新都要send到主进程再receive回来代码量翻三倍且极易出现竞态条件。实测下来一个功能完整的 Paperclip 控制台React 版本的代码行数含 CSS约 420 行Electron 版本含主进程逻辑超过 1100 行且其中 30% 是处理跨进程通信的样板代码。对于一个本应“隐形”的基础设施组件简洁就是最高优先级。2.3 OpenClaw 与 Claude 的协议冲突点才是 Paperclip 存在的全部理由理解 Paperclip必须先直面 OpenClaw 和 Claude Code Desktop 之间那几处无法调和的底层矛盾。这不是配置问题而是设计哲学的根本分歧。第一处是认证机制的不可桥接性。OpenClaw 企业版要求每个请求携带X-OpenClaw-TokenHeader该 Token 由其内置的 JWT 认证中心签发有效期 24 小时而 Claude Code Desktop 的本地 API 根本不认这个 Header它只接受两种认证一是启动时生成的--api-key参数明文字符串二是通过Authorization: Bearer keyHeader 传递。Paperclip 必须在收到 OpenClaw 请求时剥离掉X-OpenClaw-Token查表映射到对应的 Claude API Key再重新构造请求头。这个映射表不能硬编码必须支持动态加载——因为 OpenClaw 可能有多个租户每个租户对应不同的 Claude 实例。第二处是数据格式的语义鸿沟。OpenClaw 发送的请求体是标准 REST 风格{ model: claude-3-haiku-20240307, messages: [{role: user, content: Hello}], stream: true }而 Claude Code Desktop 的 WebSocket 消息是二进制帧封装的 Protocol Buffer即使你用ws库连接收到的也是Uint8Array。Paperclip 必须内置一个轻量级的 Protobuf 解析器通常用protobufjs将二进制数据反序列化为 JS 对象再按 SSE 规范重新编码。这个过程不能丢帧——Claude 的 token 流是严格有序的漏掉一个{type:token,content:function}前端就会卡在function后面永远等不到() {。第三处是连接生命周期的错位。OpenClaw 的 HTTP 连接是无状态的一次请求一个连接Claude 的 WebSocket 是长连接一个连接承载多个请求。Paperclip 必须实现连接池管理当 OpenClaw 发来第 100 个请求时Paperclip 不能每次都新建 WebSocket 连接而要复用已有的、健康的连接并在连接断开时自动重连、恢复未完成的请求队列。这个逻辑如果写在 OpenClaw 或 Claude 侧会污染它们的核心代码只有 Paperclip 这个“中间人”才有资格和能力做这件事。注意网上流传的“修改 OpenClaw 源码直接对接 Claude”的方案99% 都失败于此。他们只解决了 HTTP → WebSocket 的协议转换却忽略了连接复用和错误恢复。Paperclip 的价值70% 在于这个健壮的连接管理层而非协议转换本身。3. 核心模块实现与关键参数详解3.1 协议桥接引擎如何精准解析 Claude 的二进制流Paperclip 的心脏是它的协议桥接引擎。它不关心模型推理只关心如何把 Claude 的原始输出变成 OpenClaw 和 React 都能理解的语言。这个过程分三步接收、解析、转换。接收层使用ws库建立到 Claude Code Desktop 的 WebSocket 连接。关键参数是rejectUnauthorized: false——因为 Claude 的本地证书是自签名的且其证书 Subject Name 是localhost而 Paperclip 往往通过127.0.0.1连接导致 TLS 验证失败。这个参数必须显式设置否则连接会静默失败。我踩过的坑是在NODE_ENVproduction下ws库会默认启用证书验证而开发环境却不会导致测试通过、上线即崩。const ws new WebSocket(wss://127.0.0.1:3001, { rejectUnauthorized: false, headers: { Authorization: Bearer ${claudeApiKey} } });解析层Claude 的 WebSocket 消息不是纯文本而是 Protocol Buffer 编码的二进制帧。其.proto文件定义在 Claude Code Desktop 的resources/app.asar包内Windows 路径C:\Users\user\AppData\Local\Programs\Claude Code Desktop\resources\app.asar。你需要用asar工具解包提取protos/claude_api.proto。核心 message 是StreamingResponsemessage StreamingResponse { oneof response { TokenChunk token_chunk 1; Error error 2; Done done 3; } } message TokenChunk { string content 1; int32 index 2; }Paperclip 使用protobufjs动态加载这个 proto 文件const root await protobuf.load(path/to/claude_api.proto); const StreamingResponse root.lookupType(StreamingResponse); // 收到二进制数据 buf 后 const message StreamingResponse.decode(buf); if (message.token_chunk) { // 转换为 SSE 格式 res.write(data: ${JSON.stringify({type:token, content:message.token_chunk.content})}\n\n); }转换层SSE 要求每条消息以data:开头以\n\n结尾且不能有空行。Claude 的TokenChunk可能包含换行符\n必须转义const escapedContent content.replace(/\n/g, \\n).replace(/\r/g, \\r); res.write(data: ${JSON.stringify({type:token, content:escapedContent})}\n\n);否则前端EventSource会将\n误认为消息分隔符导致解析错误。3.2 连接池管理器如何让 1 个 WebSocket 承载 100 个 OpenClaw 请求Paperclip 的连接池不是简单的数组而是一个带状态机的 Map 结构。每个连接对象包含ws: WebSocket 实例status:connecting | connected | reconnecting | closedpendingRequests: MaprequestId, {resolve, reject, timeoutId}lastActiveAt: 时间戳用于心跳检测初始化时Paperclip 创建一个Mapstring, Connectionkey 是host:port如127.0.0.1:3001。当 OpenClaw 发来请求Paperclip 先查找可用连接const conn Array.from(connectionPool.values()) .find(c c.status connected c.pendingRequests.size MAX_PENDING); if (!conn) { // 创建新连接或复用 reconnecting 中的连接 }关键技巧在于请求 ID 的透传。OpenClaw 的每个 HTTP 请求都有唯一X-Request-IDHeaderPaperclip 必须将其作为correlationId附加到发送给 Claude 的消息中。Claude 的响应里会原样返回这个 IDPaperclip 就能精准匹配到哪个pendingRequests需要 resolve// 发送给 Claude 的消息 const claudeMessage { correlationId: req.headers[x-request-id], model: req.body.model, messages: req.body.messages }; ws.send(JSON.stringify(claudeMessage)); // 收到 Claude 响应后 if (response.correlationId) { const pending conn.pendingRequests.get(response.correlationId); if (pending) { pending.resolve(response); conn.pendingRequests.delete(response.correlationId); } }连接健康检查用的是应用层心跳而非 WebSocket ping/pong。因为 Claude 的 WebSocket 服务对 ping 帧不响应Paperclip 自己定时30 秒发送一个空Ping消息setInterval(() { if (conn.status connected) { conn.ws.send(JSON.stringify({type: ping})); } }, 30000);如果 60 秒内没收到Pong响应则标记连接为reconnecting并启动指数退避重连。3.3 OpenClaw 适配器如何绕过企业版的 JWT 网关OpenClaw 企业版的/v1/chat/completions端点强制校验X-OpenClaw-Token。Paperclip 不能简单地把这个 Header 转发给 ClaudeClaude 会 401也不能丢弃它会 403。解决方案是构建一个内存中的 Token 映射表。映射表结构为Mapstring, {apiKey: string, expiresAt: number}key 是 OpenClaw Token 的 JWT payload 中的sub字段通常是用户邮箱。Paperclip 启动时从配置文件paperclip-config.json加载初始映射{ openclawTokens: [ { token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..., apiKey: sk-ant-api03-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, expiresAt: 1735689600000 } ] }验证逻辑在 Express 中间件里app.use(/api/openclaw, (req, res, next) { const token req.headers[x-openclaw-token] as string; if (!token) return res.status(401).json({error: Missing X-OpenClaw-Token}); try { const payload jwt.verify(token, openclaw-secret-key) as {sub: string, exp: number}; const mapping tokenMap.get(payload.sub); if (!mapping || mapping.expiresAt Date.now()) { return res.status(401).json({error: Invalid or expired token}); } // 注入 Claude API Key 到 req 对象供后续路由使用 req.claudeApiKey mapping.apiKey; next(); } catch (e) { res.status(401).json({error: Invalid token signature}); } });这个设计的关键优势是零侵入 OpenClaw。你不需要修改 OpenClaw 的任何一行代码只需在 Paperclip 的 Nginx 反向代理配置里把https://your-openclaw.com/v1/chat/completions指向http://localhost:3002/api/openclaw所有流量就自动经过 Paperclip 的 Token 验证和 Key 映射。3.4 React 前端控制台如何用 200 行代码实现专业级状态监控Paperclip 的前端控制台核心是三个 React HookuseConnectionStatus、useLogStream、useReconnect。useConnectionStatus用useEffect轮询/api/statusconst [status, setStatus] useState({ claude: disconnected, openclaw: disconnected, proxy: idle }); useEffect(() { const timer setInterval(async () { try { const res await fetch(/api/status); const data await res.json(); setStatus(data); } catch (e) { setStatus(prev ({...prev, proxy: error})); } }, 5000); return () clearInterval(timer); }, []);useLogStream用EventSource接收 SSEuseEffect(() { const es new EventSource(/api/logs); es.onmessage (e) { const log JSON.parse(e.data); setLogs(prev [log, ...prev.slice(0, 99)]); }; return () es.close(); }, []);useReconnect是一个自定义 Hook封装重连逻辑function useReconnect() { const [isReconnecting, setIsReconnecting] useState(false); const triggerReconnect useCallback(async () { setIsReconnecting(true); try { await fetch(/api/reconnect, {method: POST}); // 成功后useConnectionStatus 会自动刷新状态 } finally { setIsReconnecting(false); } }, []); return {isReconnecting, triggerReconnect}; }CSS 采用 Tailwind 的flex flex-col h-screen布局状态指示灯用bg-green-500/bg-red-500的w-3 h-3 rounded-full日志区域用overflow-y-auto max-h-96。整个控制台没有第三方 UI 库体积小于 50KB加载速度比任何 Electron 界面都快。4. 实操部署全流程与环境适配要点4.1 Windows 环境WSL2 与 Windows 原生服务的共存之道Windows 是 Paperclip 部署最复杂的平台根源在于 WSL2 的网络隔离。Claude Code Desktop 默认在 Windows 原生环境运行绑定127.0.0.1:3001而 Paperclip 如果也在 WSL2 里运行它看到的127.0.0.1是 WSL2 的 loopback不是 Windows 的。必须用host.docker.internal或$(cat /etc/resolv.conf | grep nameserver | awk {print $2})获取 Windows 主机 IP。实操步骤在 PowerShell 中运行wsl --status确认 WSL2 已启用且版本 5.10。在 WSL2 的 Ubuntu 中安装 Node.js 20.x不要用 apt install nodejs版本太低curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash - sudo apt-get install -y nodejs获取 Windows 主机 IP# 在 WSL2 终端执行 cat /etc/resolv.conf | grep nameserver | awk {print $2} # 输出类似 172.28.128.1Paperclip 的配置文件paperclip-config.json中Claude 地址设为claudeHost: 172.28.128.1:3001。关键一步在 Windows 防火墙中放行172.28.128.1:3001的入站连接。否则 WSL2 无法访问。实操心得不要试图让 Claude Code Desktop 在 WSL2 里运行。它的 GUI 依赖 Windows 的 DirectXWSL2 无法提供。Paperclip 必须在 WSL2 里Claude 必须在 Windows 原生环境这是唯一稳定的组合。4.2 macOS 环境解决Virtual Machine Platform强制启用问题macOS 用户遇到的最多报错是Claudes workspace requires the virtual machine platform on windows. enable——这是 Claude 安装包的错误提示文案实际意思是“你的 macOS 版本太老不支持 Rosetta 2 转译”。Paperclip 的应对策略是降级 Claude 版本。Claude Code Desktop 的最新版v1.2.0强制要求 macOS 13.0。如果你用的是 macOS 12.6Paperclip 必须指定旧版 Claude{ claudeVersion: 1.1.4, claudeDownloadUrl: https://github.com/anthropic/claude-code-desktop/releases/download/v1.1.4/Claude.Code.Desktop-1.1.4.dmg }Paperclip 启动时会自动下载并挂载这个 DMG然后用hdiutil attach和cp -R复制到/Applications。这个过程需要sudo权限Paperclip 会提示用户输入密码。另一个 macOS 特有问题launchdsocket 的权限。Claude 的 Unix socket 路径是/var/run/claude.sock但默认只有root可读。Paperclip 必须用sudo chmod 666 /var/run/claude.sock临时开放权限。更好的做法是创建一个launchdplist 文件让 Claude 以当前用户身份启动!-- ~/Library/LaunchAgents/com.anthropic.claude.plist -- ?xml version1.0 encodingUTF-8? !DOCTYPE plist PUBLIC -//Apple//DTD PLIST 1.0//EN http://www.apple.com/DTDs/PropertyList-1.0.dtd plist version1.0 dict keyLabel/key stringcom.anthropic.claude/string keyProgramArguments/key array string/Applications/Claude Code Desktop.app/Contents/MacOS/Claude Code Desktop/string string--no-sandbox/string /array keyRunAtLoad/key true/ /dict /plist然后launchctl load ~/Library/LaunchAgents/com.anthropic.claude.plist。这样 socket 就会以当前用户权限创建Paperclip 无需 sudo。4.3 Linux 服务器环境CentOS 7.9 的兼容性攻坚CentOS 7.9 的 glibc 版本是 2.17而 Node.js 20.x 要求 glibc 2.18。直接curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo bash -会失败。Paperclip 的解决方案是静态链接 Node.js。步骤下载预编译的 Node.js 二进制包非 RPMwget https://nodejs.org/dist/v20.11.1/node-v20.11.1-linux-x64.tar.xz tar -xf node-v20.11.1-linux-x64.tar.xz sudo mv node-v20.11.1-linux-x64 /opt/nodejs sudo ln -s /opt/nodejs/bin/node /usr/local/bin/node sudo ln -s /opt/nodejs/bin/npm /usr/local/bin/npmPaperclip 的package.json中engines字段设为node: 20.11.1避免 npm install 时警告。OpenClaw 在 CentOS 上常以 systemd service 运行Paperclip 必须监听其暴露的端口如http://localhost:8080而非 Docker 网络。配置文件里openclawHost设为localhost:8080。关键防火墙设置CentOS 7 默认用firewalld必须开放 Paperclip 的端口如 3002sudo firewall-cmd --permanent --add-port3002/tcp sudo firewall-cmd --reload4.4 Docker 部署如何让 Paperclip 在容器里安全访问宿主机服务Paperclip 的 Docker 部署不是为了隔离而是为了快速分发。它必须能访问宿主机的 Claude 和 OpenClaw因此不能用默认 bridge 网络。最佳实践是host 网络模式FROM node:20-alpine WORKDIR /app COPY package*.json ./ RUN npm ci --onlyproduction COPY . . EXPOSE 3002 # 关键使用 host 网络让容器内 127.0.0.1 指向宿主机 CMD [npm, start]启动命令docker run -d \ --network host \ --name paperclip \ -v $(pwd)/config:/app/config \ paperclip-image这样Paperclip 代码里的http://127.0.0.1:3001就能直接访问宿主机的 Claude。注意--network host在 Docker for Mac/Windows 上不生效必须用host.docker.internal。Paperclip 的配置文件需支持环境变量替换{ claudeHost: ${HOST_IP}:3001 }启动时传入docker run -e HOST_IPhost.docker.internal ...5. 常见故障排查与独家避坑指南5.1 典型故障速查表现象可能原因排查命令解决方案Error: claude native binary not installedClaude Code Desktop 未正确安装或postinstall脚本未运行ls -l ~/.claude/重新下载 Claude 安装包右键“显示简介”→“打开”绕过 GatekeeperOpenClaw connection refusedPaperclip 未启动或 OpenClaw 的反向代理未指向 Paperclipcurl -v http://localhost:3002/api/status检查 Paperclip 日志确认Listening on port 3002检查 Nginx 配置中proxy_pass http://127.0.0.1:3002SSE stream ends immediatelyClaude 的 WebSocket 连接成功但未收到任何TokenChunkwscat -c wss://127.0.0.1:3001 --no-check手动发送{}看是否返回{error:invalid request}若无响应说明 Claude 未启用 API 模式需在设置中开启X-OpenClaw-Token invalidToken 过期或 Paperclip 的 JWT secret 与 OpenClaw 不一致echo token | base64 -d | jq解码 Token payload确认exp时间核对paperclip-config.json中的openclawSecret是否与 OpenClaw 配置相同Paperclip memory usage 500MB连接池泄漏或日志未轮转ps aux | grep paperclip | awk {print $6}设置MAX_LOG_ENTRIES: 1000在连接池reconnecting状态时强制清理pendingRequests5.2 我踩过的五个深坑与解决方案坑一Claude 的--api-key参数被 Windows 命令行截断现象Paperclip 启动 Claude 时传入的 API Key 只有前 32 位后半部分丢失。原因Windows CMD 对命令行长度有限制8191 字符且对、|、等字符有特殊处理。解决方案改用 PowerShell 启动并用-EncodedCommand$encoded [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes(claude-code --api-key sk-ant-api03-...)) Start-Process powershell.exe -ArgumentList -EncodedCommand $encoded坑二React 前端EventSource在 Safari 中不工作现象Chrome 正常Safari 控制台报EventSource failed。原因Safari 对 SSE 的Cache-Control: no-cache头更严格且不支持withCredentials: true。解决方案Paperclip 的 SSE 路由必须添加Access-Control-Allow-Origin: *和Cache-Control: no-storeapp.get(/api/logs, (req, res) { res.writeHead(200, { Content-Type: text/event-stream, Cache-Control: no-store, Access-Control-Allow-Origin: * }); // ... });坑三OpenClaw 的stream: true请求在 Paperclip 中被当成普通请求现象OpenClaw 发送streamtruePaperclip 返回完整 JSON而非 SSE 流。原因Express 默认将streamtrue当作 query string而 Paperclip 的路由匹配的是/api/openclaw未区分 query。解决方案在路由中显式检查app.post(/api/openclaw, (req, res) { if (req.query.stream true || req.body.stream true) { // 启动 SSE 响应 res.writeHead(200, {Content-Type: text/event-stream}); // ... } else { // 普通 JSON 响应 } });坑四Paperclip 在 WSL2 中无法访问 Windows 的localhost现象curl http://localhost:3001返回Connection refused。原因WSL2 的localhost是自己的 loopback不是 Windows 的。解决方案用 Windows 主机的真实 IP非127.0.0.1并通过netsh interface portproxy做端口转发# 在 Windows PowerShell 中执行 netsh interface portproxy add v4tov4 listenport3001 listenaddress127.0.0.1 connectport3001 connectaddress192.168.1.100其中192.168.1.100是 Windows 的局域网 IP。坑五Paperclip 日志刷屏磁盘被占满现象/var/log/paperclip.log一天增长 2GB。原因Paperclip 默认将所有
上一篇/下一篇内容由系统自动关联 返回资讯列表 →