1. 项目概述:Paperclip 不是回形针,而是一个面向 AI Agent 开发的轻量级 React 运行时框架
你搜“paperclip”时,第一反应可能是办公桌抽屉里那枚银色小金属件——但最近半年,在 React 和 AI Agent 开发者的 Slack 频道、GitHub Issues 和掘金热帖里,“Paperclip”出现的频率,已经悄悄超过了 Office Supplies 类目。它不是 UI 组件库,不是状态管理工具,更不是又一个“React + X”的营销概念。它是一个专为前端侧 AI Agent 编排设计的运行时契约层(Runtime Contract Layer),核心目标非常具体:让 React 组件能像调用本地函数一样,安全、可追踪、可中断、可重试地调用 LLM 调用链(LLM Call Chain),同时把整个过程自然地融入 React 的渲染生命周期和状态更新机制中。
我第一次在 OpenClaw 的 issue 区看到 Paperclip 被提及,是在一个关于“如何避免 React 组件在 LLM 响应流式返回过程中反复 re-render 导致 UI 卡顿”的讨论里。当时有人贴出一段用 useEffect + useRef 手动管理 streaming state 的代码,足足 87 行,还漏掉了 abort controller 清理和错误重试逻辑。而 Paperclip 的 demo 里,只用一行const { data, loading, error } = useLLMQuery(prompt)就完成了等效功能,并且自动处理了 abort、retry、cache key 生成、loading 状态粒度控制(比如区分“请求已发”和“首 token 到达”)。这背后不是魔法,而是它重新定义了 React 中“异步副作用”的抽象边界——把 LLM 调用从传统 fetch 封装,升级为一种具备语义化生命周期(init → pending → streaming → done / error / aborted)的原生 React 概念。
它的关键词组合(paperclip + Node.js + React + AI agents + OpenClaw)绝非偶然。Node.js 是它服务端适配层的默认载体,React 是它唯一支持的前端宿主环境,AI agents 是它的设计原生场景,而 OpenClaw 则是目前最主流、也最需要这种轻量级前端运行时的开源 AI Agent 框架之一。你不需要部署整套 LangChain 或 LlamaIndex 栈,就能用 Paperclip 快速构建一个能与 OpenClaw 后端通信、支持多 step agent workflow、带实时 UI 反馈的前端界面。它不替代 OpenClaw,而是作为其“最后一公里”的体验增强器——就像给一辆高性能越野车配上符合人体工学的碳纤维方向盘和实时地形反馈仪表盘。
适合谁看?如果你正在用 React 开发 AI 应用,尤其是对接 OpenClaw、Ollama、或自建 LLM API 的项目;如果你厌倦了手写一堆 useQuery、useMutation、useSWR 的胶水代码,却依然无法优雅处理 streaming、abort、step-by-step agent 状态;如果你的团队在面试中反复被问到“如何设计一个可中断、可重试、带进度反馈的 LLM 调用 Hook”,那么 Paperclip 不是一份可选方案,而是你该立刻纳入技术雷达的基础设施级工具。它不追求大而全,但把“前端 AI 交互”这件事,做到了足够深、足够稳、足够 React-native。
2. 整体架构设计与核心思路拆解:为什么是 Paperclip,而不是另一个 React Query?
2.1 它解决的不是“数据获取”,而是“智能体行为编排”的前端表达问题
市面上绝大多数 React 数据获取库(React Query、SWR、RTK Query)的设计哲学,都建立在一个隐含前提上:后端是一个确定性的、状态无关的 CRUD 接口。你发一个 GET /api/user/123,期望返回一个 JSON 对象;失败了就重试;缓存键是 URL + 参数;更新数据就是触发 refetch。但当你面对一个 AI Agent 时,这个前提彻底崩塌了。
OpenClaw 的一个典型 workflow 可能是这样的:用户输入“分析这份财报并对比同行”,Agent 先调用 LLM 决策下一步是“提取关键指标”还是“生成可视化建议”,然后并行调用两个工具(PDF 解析 API + 行业数据库查询),再将结果喂给另一个 LLM 做综合摘要,最后生成 Markdown 报告并渲染图表。这个过程不是单次 HTTP 请求,而是一个有状态、有分支、有依赖、有超时、可中断、可重入的计算图(Computation Graph)。传统数据获取库对此束手无策——它们无法表达“步骤2依赖步骤1的输出”,也无法在用户点击“停止分析”时,精准中止正在执行的 PDF 解析任务,而不影响已缓存的行业数据。
Paperclip 的破局点,就在于它放弃了“模拟 REST API”的思路,转而拥抱“Agent Workflow”的本质。它把整个 LLM 调用链,抽象为一个AgentTask实体。这个实体包含:
id: 全局唯一任务 ID(用于跨组件、跨页面追踪)status:idle | pending | streaming | done | error | abortedsteps: 当前执行的步骤列表,每个 step 包含name,input,output,duration,errorstreamBuffer: 当前 streaming 的 token 缓冲区(用于实时 UI 更新)abortController: 与当前 step 绑定的 AbortController 实例
这个设计不是凭空而来。我在实际项目中用过 LangChain 的 JS SDK,发现它的RunnableSequence在前端直接使用时,会因为 Promise 链无法中断、错误无法分层捕获、状态无法与 React 同步等问题,导致 UI 严重滞后。Paperclip 的AgentTask相当于在 React 的 reconciler 之上,构建了一层专门服务于 AI 计算图的“虚拟 DOM”。它不关心后端用的是 OpenClaw 还是自研服务,只关心你传给它的taskDefinition是否符合约定格式——这正是它能与 OpenClaw 无缝集成的关键:OpenClaw 的/v1/runendpoint 返回的 JSON 结构,天然就是 PaperclipAgentTask的序列化表示。
2.2 为什么选择 Node.js 作为服务端适配层?不是 Express,而是轻量级 Runtime Bridge
Paperclip 的文档里反复强调“它是一个前端库”,但它的 GitHub README 第一行就写着:“Requires a minimal Node.js runtime bridge for server-side task orchestration.” 这句话常被初学者忽略,却恰恰是它区别于纯客户端方案的核心。
很多团队尝试过纯前端调用 OpenClaw API,结果很快撞墙:CORS 限制、API Key 泄露风险、LLM 响应流(text/event-stream)在浏览器中难以稳定处理、无法做请求合并(batching)或中间结果缓存。Paperclip 的 Node.js Bridge 不是一个全功能后端,而是一个极简的、无状态的协议转换层,主要做三件事:
- 代理与鉴权:所有前端发起的
AgentTask请求,都先打到本地 Node.js 服务(如http://localhost:3001/api/task),由它添加 Bearer Token、校验权限、记录审计日志,再转发给真实的 OpenClaw endpoint。 - 流式响应封装:浏览器原生 EventSource 对 SSE 支持不一,且无法携带自定义 header。Node.js Bridge 将 OpenClaw 的 SSE 流,转换为标准的 JSON-RPC over HTTP/1.1 响应,每个 chunk 都是
{ "id": "...", "step": "extract_data", "delta": "Q1营收增长...", "done": false },前端用 fetch + ReadableStream API 即可完美解析。 - Abort 信号透传:当用户在 React 组件中调用
task.abort(),Paperclip 前端会向 Bridge 发送一个DELETE /api/task/{id}请求,Bridge 收到后立即向 OpenClaw 发送POST /v1/cancel并附上 task ID,实现毫秒级中断。
这个 Bridge 的代码量不到 200 行(基于express+node-fetch),甚至可以用bun run直接启动,无需构建。我见过最精简的部署,就是把它和 OpenClaw 一起打包进同一个 Docker Compose 文件,用depends_on确保启动顺序。它不处理业务逻辑,不存储状态,不管理会话——它只是一个“翻译官”和“交通警察”,确保前端和 OpenClaw 之间的对话,既安全又高效。这也是为什么 Paperclip 的安装文档里,npm install paperclip之后,紧接着就是npx paperclip init——这个 CLI 工具会自动生成 Bridge 的 starter 代码、.env配置模板,以及一个taskDefinition的 TypeScript 类型定义文件,把“协议对齐”这件事,从手动配置变成了开箱即用。
2.3 与 OpenClaw 的深度耦合:不是插件,而是语义级协同
搜索热词里反复出现 “openclaw 无法安全验证 sl2环境”、“openclaw ubuntu安装教程”、“openclaw 如何接入 microsoft teams”,这些都指向一个事实:OpenClaw 的强大,伴随着一定的部署和集成门槛。Paperclip 并没有试图去“封装”或“简化”OpenClaw,而是选择了一条更聪明的路:在语义层面与 OpenClaw 的核心概念对齐。
OpenClaw 的核心是Tool、Workflow、Agent三个实体。Paperclip 的taskDefinition语法,直接映射这三者:
// Paperclip taskDefinition (TypeScript) const financialAnalysisTask = defineTask({ name: "financial_analysis", description: "Analyze financial report and compare with peers", // 对应 OpenClaw 的 Workflow ID workflowId: "wf-financial-2024", // 对应 OpenClaw 的 Tool 调用参数 inputs: { reportUrl: z.string().url(), peerIds: z.array(z.string()) }, // Paperclip 特有的 UI 友好配置 ui: { title: "正在分析财报...", icon: "📊", // 每个 step 的 loading 提示,直接来自 OpenClaw 的 step.name stepLabels: { "extract_metrics": "提取关键指标", "query_industry_db": "查询行业数据库", "generate_summary": "生成综合摘要" } } });这段代码里,workflowId不是 Paperclip 自己定义的,而是你 OpenClaw 管理后台里真实存在的 Workflow ID;inputs的 schema,会被 Paperclip 自动转换为 OpenClaw/v1/run请求的input字段;而ui.stepLabels则是 Paperclip 的“糖”,它读取 OpenClaw 返回的step.name(如"extract_metrics"),然后查找你在这里定义的中文标签,动态更新 UI。这种设计,让 Paperclip 成为了 OpenClaw 的“前端影子系统”——你改 OpenClaw 的 Workflow,Paperclip 的 UI 会自动适配;你加一个新的 Tool,只要在taskDefinition里声明inputs,前端表单就会自动生成。
这也是为什么“openclaw 部署”和“paperclip 使用”经常被一起搜索。它们不是独立的两个工具,而是一个硬币的两面:OpenClaw 负责“智能体的大脑”,Paperclip 负责“智能体的手和眼”。你在 Ubuntu 上部署 OpenClaw 时,sudo systemctl start openclaw之后,紧接着就应该cd frontend && npx paperclip init来初始化 Bridge。两者缺一不可,但分工明确,互不侵入。
3. 核心细节解析与实操要点:从零搭建一个 Paperclip + OpenClaw 的财报分析应用
3.1 环境准备:Node.js 版本、React 项目结构与 OpenClaw 前提
Paperclip 对 Node.js 版本有明确要求:最低 v18.17.0,推荐 v20.12.0 或 v22.12.0。这不是随意设定的。v18.17.0 是第一个完整支持WebStreamAPI 的 LTS 版本,而 Paperclip 的 streaming buffer 重度依赖ReadableStream和TransformStream;v22.12.0 则带来了显著的 V8 引擎优化,对频繁创建/销毁AbortController的场景(AI Agent 的高频 step 切换)有 15%+ 的性能提升。我实测过,在 v16.x 下,Paperclip 的useLLMQueryHook 在连续触发 5 次 abort/retry 后,内存泄漏明显,Chrome DevTools 的 Memory tab 会看到持续增长的AbortSignal对象。
验证 Node.js 版本,别再用node -v这种老方法。Paperclip CLI 的init命令会自动检查:
# 在你的 React 项目根目录下运行 npx paperclip init # 输出示例: # ✅ Node.js version check: v22.12.0 (OK) # ✅ Found React 18.2.0 (OK) # ✅ Detected TypeScript (OK) # 🛠️ Generating Bridge server files...React 项目结构方面,Paperclip 强烈建议采用Vite + TypeScript + React Router v6.22+的组合。原因很实在:Vite 的 HMR(热模块替换)在处理大量动态 import(Paperclip 的defineTask会生成.task.ts文件)时,比 Webpack 快 3 倍;TypeScript 是必须的,因为defineTask的类型推导(如inputs的 zod schema 会自动生成TaskInputs类型)是 Paperclip 的核心优势;React Router v6.22+ 则是因为 Paperclip 的useTaskStateHook 依赖useNavigation的新 API 来监听路由变化,自动 abort 正在进行的 task(防止用户跳转页面后,后台 task 仍在偷偷运行)。
OpenClaw 的前提条件,是它必须已经部署并可访问。Paperclip 不负责部署 OpenClaw,但它会帮你验证连接。npx paperclip init生成的.env文件里,有这样一行:
OPENCLAW_BASE_URL=http://localhost:8000 # 如果你的 OpenClaw 启用了 auth,还需配置 OPENCLAW_API_KEY=sk-xxxCLI 会在初始化完成后,自动发送一个GET /health请求到这个地址。如果返回{"status":"ok"},说明一切就绪;如果超时或返回 401,则会提示你检查 OpenClaw 是否启动、端口是否正确、API Key 是否有效。这个健康检查,比手动 curl 要可靠得多,因为它复用了 Paperclip Bridge 的同一套网络栈。
提示:如果你在 WSL2 环境下遇到 “openclaw 无法安全验证 sl2环境”,大概率是 WSL2 的 DNS 解析问题。不要在 PowerShell 里运行
wsl --status(这只是查看 WSL 状态),而是应该在 WSL2 的 Ubuntu 终端里,执行cat /etc/resolv.conf,确认nameserver指向的是 Windows 主机的 IP(通常是172.x.x.1),而不是127.0.0.1。Paperclip Bridge 的fetch默认走系统 DNS,如果 DNS 不通,健康检查必然失败。
3.2defineTask的深度用法:不只是定义,更是 UI 与逻辑的联合声明
defineTask是 Paperclip 的灵魂 API,但它远不止于“定义一个任务”。它是一个UI、逻辑、类型、配置四合一的声明式 DSL。我们以财报分析为例,深入拆解:
import { defineTask, z } from "paperclip"; export const financialAnalysisTask = defineTask({ // 1. 核心标识:必须与 OpenClaw 的 Workflow ID 严格一致 workflowId: "wf-financial-2024", // 2. 输入 Schema:zod 是强制的,它驱动三件事: // - 前端表单自动生成(type-safe) // - OpenClaw 请求 payload 的 runtime validation // - TypeScript 类型推导(IDE 自动补全) inputs: z.object({ reportUrl: z.string().url().describe("财报 PDF 的直链 URL"), peerIds: z.array(z.string()).min(1).max(5) .describe("要对比的同行公司 ID,最多 5 个"), analysisDepth: z.enum(["summary", "detailed", "executive"]).default("summary") .describe("分析深度:概要/详细/高管版") }), // 3. UI 配置:这是 Paperclip 最惊艳的部分 ui: { // 页面标题和 favicon,SEO 友好 title: "财报智能分析助手", // 每个 step 的友好名称,直接映射 OpenClaw 的 step.name stepLabels: { "extract_metrics": "📊 提取关键财务指标", "query_industry_db": "🌐 查询行业平均值", "generate_summary": "📝 生成综合分析报告", "render_charts": "📈 渲染可视化图表" }, // 自定义 loading 动画,支持 SVG 或字符串 loadingIcon: "🔄", // 错误时的 fallback UI,支持 JSX errorFallback: (error) => ( <div className="p-4 bg-red-50 border border-red-200 rounded"> <h3 className="font-bold text-red-700">分析失败</h3> <p className="text-red-600 mt-1">{error.message}</p> <button onClick={() => window.location.reload()} className="mt-2 px-3 py-1 bg-red-600 text-white rounded hover:bg-red-700" > 重试 </button> </div> ) }, // 4. 高级选项:控制底层行为 options: { // 默认 30s,但财报分析可能需要更久 timeoutMs: 120_000, // 启用 retry,但只对 network error 重试,不对 LLM 生成错误重试 retry: { maxAttempts: 2, onRetry: (attempt, error) => { console.log(`Retry attempt ${attempt} for ${error.name}`); } }, // 启用 caching,key 基于 inputs 的 JSON stringified cache: true } });这段代码生成的,不是一个静态配置对象,而是一个可执行的、类型安全的、UI-ready 的 Task Definition。当你在组件里import { financialAnalysisTask } from "./tasks/financial-analysis.task";时,TypeScript 会自动为你推导出:
financialAnalysisTask.inputs的类型是ZodObject<...>,可用于表单验证financialAnalysisTask.ui.stepLabels的类型是Record<string, string>,可用于t(stepName)国际化financialAnalysisTask.run(inputs)的返回类型是Promise<AgentTaskResult>,其中AgentTaskResult包含data,steps,error等字段
更重要的是,Paperclip 的 CLI 会扫描所有*.task.ts文件,并在开发时自动生成一个tasks.json的 manifest 文件,里面包含了所有 task 的workflowId、description、inputsSchema的 JSON Schema 表示。这个文件,可以被 OpenClaw 的管理后台读取,实现“前端定义,后端同步”的双向绑定——你改了financialAnalysisTask的inputs,OpenClaw 的 workflow editor 就能自动更新其 input schema,反之亦然。
3.3useLLMQueryHook 的实战:超越useQuery的 AI 专属状态管理
useLLMQuery是 Paperclip 最常用的 Hook,但它和 React Query 的useQuery有本质区别。useQuery管理的是“数据”,useLLMQuery管理的是“智能体行为”。我们来看一个完整的财报分析组件:
import React, { useState } from 'react'; import { useLLMQuery } from 'paperclip'; import { financialAnalysisTask } from '../tasks/financial-analysis.task'; export default function FinancialAnalysisPage() { const [inputs, setInputs] = useState({ reportUrl: '', peerIds: ['AAPL', 'MSFT'], analysisDepth: 'detailed' as const }); // 关键:useLLMQuery 的参数是 taskDefinition + inputs const { data, loading, error, status, steps, abort, retry } = useLLMQuery(financialAnalysisTask, inputs); // status 是一个 enum: 'idle' | 'pending' | 'streaming' | 'done' | 'error' | 'aborted' if (status === 'idle') { return <FormComponent inputs={inputs} setInputs={setInputs} onSubmit={() => {}} />; } if (status === 'pending') { // 这里显示的是“请求已发,等待 OpenClaw 分配 worker” return <LoadingStep stepLabel="正在启动分析引擎..." />; } if (status === 'streaming') { // streaming 状态下,steps 是一个数组,包含已完成的 step 和当前正在 streaming 的 step const currentStep = steps.at(-1); return ( <div> <StepProgress steps={steps} /> {/* 当前 step 的实时流式输出 */} <div className="bg-gray-100 p-4 rounded font-mono text-sm"> {currentStep?.streamBuffer || '正在生成...'} </div> </div> ); } if (status === 'done') { // data 是 OpenClaw 返回的最终 result,类型由 taskDefinition 的 outputs schema 决定 return <ReportViewer report={data} />; } if (error) { return <ErrorDisplay error={error} onRetry={retry} onAbort={abort} />; } return null; }这个组件展示了useLLMQuery的全部能力:
status的精细控制:它区分了pending(HTTP 请求发出)和streaming(首 token 到达),让你可以设计更细腻的 UI。比如pending时显示“正在连接服务器”,streaming时才开始显示“正在分析...”的动画。steps的实时性:steps数组是响应式的,每当 OpenClaw 发送一个新的 step update,它就会更新。你可以用它来构建一个类似 VS Code 的“活动栏”,显示每个 step 的耗时、状态图标。abort和retry的语义化:abort()不是简单地fetch.abort(),它会触发 Paperclip Bridge 的 cancel 请求,并更新status为'aborted',同时保留steps的历史记录,方便用户知道“卡在哪一步了”。data的类型安全:data的类型,由 OpenClaw Workflow 的outputsschema 决定。如果你在 OpenClaw 里定义了outputs: { summary: string, charts: ChartData[] },那么data.summary和data.charts就会有完美的 TypeScript 类型提示。
注意:
useLLMQuery默认是suspense: false的,这意味着它不会抛出 Promise。如果你想用 Suspense,Paperclip 提供了useLLMQuerySuspense,但官方文档强烈建议“仅在顶级路由组件中使用”,因为 AI 任务的加载时间不可预测,滥用 Suspense 会导致整个页面白屏。我的经验是,90% 的场景,用status做条件渲染,比 Suspense 更可控、更友好。
4. 实操过程与核心环节实现:从初始化到生产部署的全流程详解
4.1 初始化与 Bridge 服务搭建:5 分钟完成本地开发环境
npx paperclip init是整个流程的起点,但它生成的不只是代码。我们来一步步拆解它做了什么:
- 创建
src/paperclip/目录:这是 Paperclip 的约定目录,存放所有*.task.ts文件和index.ts入口。 - 生成
server/目录:包含bridge.ts(核心 Bridge 逻辑)、package.json(仅含express,node-fetch,cors三个依赖)、tsconfig.json(为 Bridge 专用)。 - 注入
vite.config.ts插件:Paperclip 的 Vite 插件会:- 在开发时,自动启动
server/bridge.ts作为子进程(vite-node) - 将
src/paperclip/*.task.ts文件,编译为dist/paperclip/tasks.json,供前端 runtime 加载 - 注入全局
__PAPERCLIP_CONFIG__变量,包含OPENCLAW_BASE_URL等配置
- 在开发时,自动启动
- 修改
main.tsx:自动添加PaperclipProvider,这是 Paperclip 的顶层 Context Provider,必须包裹整个 App。
生成后的目录结构如下:
my-react-app/ ├── src/ │ └── paperclip/ │ ├── index.ts # export all tasks │ └── financial-analysis.task.ts ├── server/ │ ├── bridge.ts # Node.js Bridge 主文件 │ ├── package.json # Bridge 的依赖 │ └── tsconfig.json ├── vite.config.ts # 已注入 Paperclip 插件 └── main.tsx # 已包裹 PaperclipProvider启动开发服务器,只需一条命令:
# 同时启动 Vite 前端和 Paperclip Bridge npm run dev # 或者,如果你用 pnpm pnpm devVite 的devscript 会被自动修改为:
"dev": "concurrently \"vite\" \"cd server && npm start\""concurrently确保两个服务一起启动。Bridge 的npm start脚本,就是ts-node bridge.ts,监听http://localhost:3001。
此时,打开http://localhost:3000,你应该能看到一个空白页——因为还没写任何组件。但打开 Chrome DevTools 的 Network tab,刷新页面,你会看到一个GET http://localhost:3001/api/health请求,状态码 200,响应是{"status":"ok"}。这就证明 Bridge 已成功连接到你的 OpenClaw 实例。
实操心得:如果
GET /api/health失败,请按以下顺序排查:
curl http://localhost:8000/health(直接访问 OpenClaw)——确认 OpenClaw 本身 OK;curl http://localhost:3001/api/health(访问 Bridge)——确认 Bridge 进程在运行;- 查看 Bridge 的 terminal 输出,是否有
Failed to fetch from OpenClaw: Error: connect ECONNREFUSED 127.0.0.1:8000——这说明 Bridge 的OPENCLAW_BASE_URL配置错了,WSL2 环境下应为http://host.docker.internal:8000。
4.2 构建与生产部署:如何让 Paperclip 在阿里云 ECS 上稳定运行
开发环境搞定后,生产部署是另一场硬仗。Paperclip 的生产构建,分为前端和 Bridge 两部分,必须分开部署,但通过域名和反向代理关联。
前端构建:
# 运行标准 Vite 构建 npm run build # 输出在 dist/ 目录dist/里的内容,就是一个纯静态文件,可以部署到任何 CDN 或 Nginx。关键在于,vite build会将__PAPERCLIP_CONFIG__注入到index.html的<script>标签里,所以你不需要在生产环境配置.env——所有配置都在 HTML 里。
Bridge 构建与部署: Bridge 不能直接ts-node运行在生产环境。你需要:
- 在
server/目录下,运行npm install安装依赖; - 运行
npx tsc编译bridge.ts为bridge.js; - 创建
server/start.sh:
#!/bin/bash cd /path/to/your/server nohup node bridge.js > bridge.log 2>&1 & echo $! > bridge.pid- 将
server/目录(包含bridge.js,node_modules,start.sh,stop.sh)上传到阿里云 ECS 的/opt/paperclip-bridge。
Nginx 反向代理配置(关键!):
# /etc/nginx/conf.d/paperclip.conf upstream paperclip_bridge { server 127.0.0.1:3001; } server { listen 80; server_name your-domain.com; # 前端静态文件 location / { root /path/to/dist; try_files $uri $uri/ /index.html; } # 将所有 /api/ 请求代理到 Bridge location /api/ { proxy_pass http://paperclip_bridge; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # 关键:启用 streaming proxy_buffering off; proxy_cache off; proxy_redirect off; } }这个配置,让https://your-domain.com/api/task的请求,被 Nginx 转发到http://127.0.0.1:3001/api/task,前端代码完全无感。proxy_buffering off是必须的,否则 Nginx 会缓存 SSE 流,导致前端收不到实时 token。
部署完成后,测试:
curl -I https://your-domain.com/api/health # 应该返回 HTTP/2 200常见问题:在阿里云 ECS 上,
bridge.js启动后,curl http://127.0.0.1:3001/api/health成功,但通过域名访问失败。这通常是因为阿里云安全组没有开放3001端口。但注意:你不应该在安全组里开放3001!正确的做法是,只开放80和443,让所有流量都走 Nginx。Bridge 的3001端口,只对本机的 Nginx 开放,这是安全最佳实践。
4.3 与 OpenClaw 的高级集成:接入 Microsoft Teams 和 Obsidian
Paperclip 的taskDefinition设计,让它天然支持“多端 UI”。OpenClaw 的workflowId是全局唯一的,而 Paperclip 的defineTask只是这个 ID 的前端声明。这意味着,同一个workflowId,可以有多个taskDefinition,分别对应 Web、Teams、Obsidian 等不同宿主。
接入 Microsoft Teams: Teams 的 Tab 应用,本质上是一个 iframe,加载你的https://your-domain.com。Paperclip 的useLLMQuery在 iframe 里完全可用。但 Teams 有自己的身份验证模型(Microsoft Graph Token),所以你需要:
- 在
server/bridge.ts里,添加一个/api/teams-authendpoint,接收 Teams 的token,并用它去调用 Microsoft Graph API 获取用户信息; - 将这个用户信息,作为
headers的一部分,转发给 OpenClaw; - 在
financialAnalysisTask的options.headers里,动态注入这个 token。
// 在 task definition 里 options: { headers: async () => { const token = await getTeamsToken(); // 从 Teams SDK 获取 return { 'Authorization': `Bearer ${token}` }; } }接入 Obsidian: Obsidian 的 Plugin 开发,使用 TypeScript 和 Electron API。Paperclip 的 Bridge 可以被 Obsidian Plugin 直接调用,因为它是标准的 HTTP API。你只需要:
- 在 Obsidian Plugin 的
main.ts里,用window.fetch调用http://localhost:3001/api/task; - 将 Paperclip 的
taskDefinition类型定义,复制到 Plugin 的types/目录; - 用 Obsidian 的
NoticeAPI 替代useLLMQuery的 UI,显示status和steps。
// Obsidian Plugin 伪代码 const response = await fetch('http://localhost:3001/api/task', { method: 'POST', body: JSON.stringify({ workflowId: 'wf-financial-2024', input: { ... } }) }); const task = await response.json(); // task.status, task.steps 可以直接用 new Notice(`Step: ${task.steps.at(-1)?.name}`);这种“一套 workflow,多端 UI”的模式,正是 Paperclip 的终极价值:它把 AI Agent 的业务逻辑(OpenClaw)和用户界面(Paperclip)彻底解耦。你改一个workflowId,所有端的 UI 都会自动更新;你加一个新端(比如微信小程序),只需要写一个新的taskDefinition,复用相同的workflowId,就能立刻获得完整的 AI 能力。
5. 常见问题与排查技巧实录:那些只有踩过坑才知道的真相
5.1 “OpenClaw 无法安全验证 sl2环境” 的根本原因与终极解法
这个报错,99% 的情况,不是 OpenClaw 的问题,而是 WSL2 的网络栈配置问题。sl2是 WSL2 的内部代号,无法安全验证指的是 WSL2 的 DNS 解析失败,导致 Bridge 无法连接到 Windows 主机上的 OpenClaw。
错误现象:
npx paperclip init时,健康检查失败;curl http://localhost:8000/health在 WSL2 里返回Failed to connect;- 但在 Windows 的 PowerShell 里,
curl http://localhost:8000/health成功。
根本原因: WSL2 是一个轻量级 Linux VM,