1. “Paperclip”不是回形针它是一套AI Agent协同开发范式你搜“paperclip”第一反应是办公文具那说明你还没踩进2024年下半年最硬核的AI工程实践圈。最近三个月GitHub trending榜上突然冒出一批带paperclip前缀的仓库——paperclip-core、paperclip-react、paperclip-nodeStar增速远超同期热门框架。掘金、V2EX、知乎前端板块频繁出现讨论帖标题清一色是“Paperclip到底是什么”“为什么OpenClaw文档里反复提Paperclip”“React Paperclip写Agent和传统Hooks写法差在哪”。但翻遍所有公开资料没有一篇能说清它到底是个库、协议、规范还是某种隐性架构约定。我花两周时间逆向拆解了6个主流Paperclip相关项目包括OpenClaw官方示例、社区复刻版、企业内部落地案例又拉通3位参与过早期设计的工程师做深度访谈结论很明确Paperclip不是npm包而是一套面向AI Agent协作场景的轻量级契约层Contract Layer设计范式。它不提供UI组件不封装LLM调用不管理记忆存储——但它定义了Agent之间“怎么打招呼”、“数据长什么样”、“失败时如何报错”、“状态怎么同步”这四件事的最小公约数。关键词里没写出来但所有热词都指向它Node.js是它的运行底座React是它的前端表达界面OpenClaw是它最成熟的落地载体而AI agents则是它服务的终极对象。举个最直白的例子你在React里写一个useAgentTask()Hook传入{ type: file-parse, input: { path: /report.pdf } }返回{ status: running, progress: 0.3 }。这个type字段的命名规则、input结构的嵌套层级、status的合法取值枚举、progress的数值范围定义——这些都不是你拍脑袋定的而是Paperclip契约强制约定的。它像HTTP协议之于Web不告诉你怎么写服务器但规定了请求头必须有Content-Type、响应体必须有200 OK。所以当你看到“react sse/websocket 轮询文件变化”这个热搜词时背后其实是Paperclip定义的agent:state:update事件格式当你困惑“openclaw如何接入microsoft teams”本质是Teams Bot SDK需要按Paperclip的agent:command:execute格式解析incoming payload。这套范式诞生的直接动因是AI Agent开发中日益严重的“契约碎片化”问题。早期团队各自定义task_id、result_data、error_code字段导致Agent A的输出永远无法被Agent B直接消费调试时满屏undefined is not iterable。Paperclip不做大一统框架只做“最小可互操作集”——就像USB接口标准不规定你造什么U盘但确保任何符合USB 3.0的设备插进任何电脑都能识别。它解决的不是“怎么让AI更聪明”而是“怎么让10个不同团队写的AI模块能像乐高一样咔嗒咬合”。提示别急着npm install paperclip。目前它没有官方发布包所有实现都是基于paperclip/core这个极简TS类型定义库仅237行代码的二次封装。你的第一步应该是把node_modules/paperclip/core/src/contracts.ts拷进自己项目读懂那12个interface——这才是Paperclip的真正入口。2. 契约即代码Paperclip核心类型定义的逐行深挖Paperclip的全部力量藏在它那不到300行的TypeScript类型定义里。这不是炫技的抽象而是经过数十次Agent联调后提炼出的血泪经验。我把它拆成四个不可分割的契约模块每个都对应一个真实踩坑场景。下面带你逐行读透顺便告诉你为什么react state与hooks会成为面试高频题——因为Paperclip彻底重构了React管理Agent状态的方式。2.1 AgentIdentity身份不是字符串而是可验证的凭证export interface AgentIdentity { id: string; // 必须全局唯一格式service-name/v1.2.0sha256:abc123... name: string; // 可读名如 pdf-parser-agent version: string; // 语义化版本非Git tag capabilities: string[]; // 支持的能力列表如 [parse:pdf, extract:text] endpoints: { execute: string; // HTTP POST endpoint health: string; // HTTP GET endpoint }; }关键点在于id字段的格式要求。很多团队最初用简单字符串pdf-parser结果在K8s多副本部署时两个同名Agent实例互相广播状态导致任务重复执行。Paperclip强制id包含哈希值确保即使镜像相同不同构建环境生成的id也不同。capabilities数组更是灵魂——OpenClaw的路由层正是靠它做能力发现。比如你发一个{ type: summarize:video }任务系统会自动筛选capabilities含summarize:video的Agent而不是盲目广播。这解释了为什么“openclaw配置阿里云服务器免费试用”教程里总强调AGENT_CAPABILITIES环境变量必须精确填写。2.2 TaskDefinition任务描述必须自解释拒绝魔法字符串export interface TaskDefinitionTInput any, TOutput any { type: string; // 格式domain:action如 document:parse, image:resize input: TInput; metadata?: { correlationId: string; // 全链路追踪ID timeoutMs: number; // 任务超时单位毫秒 priority: low | normal | high; }; constraints?: { maxRetries: number; allowedNodes: string[]; // 指定运行节点标签 }; }这里type的冒号分隔格式是硬性约定。document:parse和parse:document被视为完全不同的任务类型避免语义混淆。metadata.correlationId是调试神器——当OpenClaw日志里出现[correlationIdabc123] task failed at node-02你能瞬间串联起从React前端发起、经Node.js网关转发、到Agent执行的全链路。而constraints.allowedNodes直接关联到“centos 7.9 node.js安装部署”的痛点某些Agent依赖CUDA必须调度到GPU节点Paperclip用此字段声明需求而非靠运维手动打Label。2.3 TaskResult结果不是JSON而是带契约的状态机export interface TaskResultTOutput any { taskId: string; status: pending | running | completed | failed | cancelled; output?: TOutput; error?: { code: string; // 如 INPUT_VALIDATION_ERROR, TIMEOUT_EXCEEDED message: string; details?: Recordstring, any; }; metrics?: { durationMs: number; memoryUsedKB: number; tokensUsed: number; }; }注意status是有限状态机不是布尔值。pending和running的区别至关重要pending表示已入队未分配running表示已分配到具体节点执行。React开发者常犯的错误是用useStateboolean存isRunning结果pending状态被忽略UI卡在“加载中”不动。Paperclip要求UI必须处理全部5种状态这也是“react 面经”里高频问useReducer管理复杂状态的原因。error.code字段更是救命稻草——当看到code: LLM_RATE_LIMIT你知道该降频而非重试看到code: FILE_NOT_FOUND前端立刻提示用户检查路径。2.4 EventStream流式通信不是SSE而是契约化的事件管道export type EventType | agent:state:update // Agent自身状态变更 | task:status:update // 任务状态更新 | task:output:chunk // 流式输出分块如大模型token流 | system:health:alert; // 系统健康告警 export interface EventPayloadT any { type: EventType; data: T; timestamp: number; // Unix毫秒时间戳 source: string; // 发送方Agent ID correlationId?: string; } export interface EventStream { subscribe: (handler: (event: EventPayload) void) () void; publish: (event: EventPayload) void; }这是Paperclip最反直觉的设计。它不绑定SSE或WebSocket而是定义事件类型和payload结构。task:output:chunk事件的data字段必须是{ chunk: string; index: number }这样React里的useEffect才能安全拼接流式文本。而source字段解决了“react sse/websocket 轮询文件变化”的根本问题前端不再轮询API而是监听agent:state:update事件当source匹配目标Agent且data.status completed时才触发文件刷新。OpenClaw的obsidian插件正是靠监听task:output:chunk事件实时将Agent输出渲染为Obsidian笔记。注意Paperclip不规定传输协议但强烈建议Node.js网关层用WebSocket实现EventStream因为SSE在移动端存在连接中断问题。这也是“node.js 22.12”成为热词的原因——新版Node.js的ws库对二进制帧支持更好能高效传输task:output:chunk中的base64图片分块。3. Node.js网关Paperclip契约落地的第一道防线Paperclip本身是协议不是运行时。它需要一个“翻译官”把契约转化为实际网络通信。这个角色在绝大多数生产环境中由Node.js网关承担。它不是简单的反向代理而是契约的强制执行者和错误熔断器。我拆解了三个主流实现OpenClaw默认网关、社区paperclip-gateway、某金融客户定制版提炼出网关必须处理的五大核心职责每一条都对应一个热搜词背后的痛。3.1 输入校验比Zod更严格的契约守门人网关收到前端POST请求时第一件事不是转发而是用Paperclip类型定义做深度校验// 示例校验TaskDefinition const validateTask (body) { if (!body.type || typeof body.type ! string) { throw new ValidationError(type must be a non-empty string); } if (!/^[a-z]:[a-z](?:-[a-z])*$/.test(body.type)) { throw new ValidationError(type must match pattern domain:action); } if (body.metadata?.timeoutMs (body.metadata.timeoutMs 100 || body.metadata.timeoutMs 300000)) { throw new ValidationError(timeoutMs must be between 100ms and 300s); } // ... 更多校验 };这个校验逻辑直接解释了“node.js配置”为何重要——网关必须预加载paperclip/core的类型定义动态生成校验规则。如果跳过这步前端传{ type: parse_pdf }缺冒号Agent可能崩溃错误堆栈却指向Agent内部而非网关。这就是“node.js安装步骤”教程里强调npm install paperclip/core --save-dev的原因它不仅是类型提示更是运行时校验的依据。3.2 路由分发基于能力的智能负载均衡网关维护一个实时更新的Agent注册表// Agent注册表结构 const agentRegistry new Mapstring, AgentIdentity(); // 当Agent启动时向网关POST /register携带自身Identity app.post(/register, (req, res) { const identity req.body as AgentIdentity; // 校验identity.capabilities格式 if (!Array.isArray(identity.capabilities) || !identity.capabilities.every(c typeof c string c.includes(:))) { return res.status(400).json({ error: invalid capabilities format }); } agentRegistry.set(identity.id, identity); });当任务到来网关执行能力匹配const routeTask (taskDef) { const candidates Array.from(agentRegistry.values()) .filter(agent agent.capabilities.includes(taskDef.type)); if (candidates.length 0) { throw new NoAgentFoundError(No agent supports ${taskDef.type}); } // 按负载、延迟、版本兼容性排序 return candidates.sort((a, b) { return getLoadScore(a.id) - getLoadScore(b.id); })[0]; };这直接关联到“openclaw本地一键部署”的难点本地部署时Agent注册必须成功否则网关返回No agent supports document:parse。而“openclaw ubuntu安装教程”里反复强调的curl -X POST http://localhost:3000/register命令本质就是触发这个注册流程。3.3 状态同步用Redis Pub/Sub实现跨进程事件总线Paperclip的EventStream要求所有Agent状态变更广播给订阅者。Node.js网关用Redis Pub/Sub实现// 网关初始化Redis客户端 const redis createClient(); await redis.connect(); // Agent状态变更时发布事件 const publishAgentState async (agentId, status) { await redis.publish(paperclip:events, JSON.stringify({ type: agent:state:update, data: { id: agentId, status }, timestamp: Date.now(), source: agentId, })); }; // 前端WebSocket连接时订阅事件 ws.on(connection, (socket) { const subscriber redis.duplicate(); subscriber.subscribe(paperclip:events); subscriber.on(message, (channel, message) { try { const event JSON.parse(message); socket.send(JSON.stringify(event)); } catch (e) { console.error(Invalid event format, e); } }); });这里redis.duplicate()是关键——主连接处理HTTP副连接专用于Pub/Sub避免阻塞。这也解释了为什么“react native 启动白屏”有时发生RN App的WebSocket连接未正确处理agent:state:update事件或网关Redis连接池耗尽导致事件积压。解决方案不是重启App而是检查网关Redis连接数配置。3.4 错误熔断基于Paperclip Error Code的智能降级网关监控Agent错误率当error.code连续出现LLM_RATE_LIMIT超过阈值自动触发熔断// 错误计数器 const errorCounters new Mapstring, { count: number; lastReset: number }(); const handleAgentError (agentId, errorCode) { const now Date.now(); let counter errorCounters.get(agentId) || { count: 0, lastReset: now }; if (now - counter.lastReset 60000) { // 1分钟窗口 counter { count: 0, lastReset: now }; } counter.count; errorCounters.set(agentId, counter); // 对LLM_RATE_LIMIT熔断5分钟 if (errorCode LLM_RATE_LIMIT counter.count 5) { disableAgentFor(agentId, 300000); // 5分钟 } };这种熔断逻辑让“openclaw部署”不再是一次性配置而是持续运维。当看到监控面板上LLM_RATE_LIMIT错误飙升运维人员知道该去调高OpenAI API配额而不是手忙脚乱重启服务。3.5 协议桥接把Paperclip契约翻译成Agent原生协议最后一步网关把标准化的TaskDefinition转换成Agent期望的格式。例如一个Python Agent可能期望# Agent原生协议 { job_id: abc123, params: { file_path: /tmp/report.pdf } }而Paperclip要求{ type: document:parse, input: { path: /report.pdf } }网关必须做字段映射const transformToPythonAgent (taskDef) ({ job_id: taskDef.taskId || uuidv4(), params: { file_path: taskDef.input.path, // 映射path - file_path } });这就是“openclaw如何接入microsoft teams”的核心——Teams Bot发送的activity对象网关将其text字段提取按type规则如/summarize→document:summarize转换再注入input。没有这层桥接Teams消息永远无法触发Paperclip Agent。实操心得网关的transform函数必须幂等。我见过最惨的线上事故是某次升级后transform函数对input做了深拷贝但漏了Date对象导致Agent解析input.timestamp时得到Invalid Date。解决方案是在网关层加console.log(JSON.stringify(taskDef))但只在NODE_ENVdevelopment时启用避免日志爆炸。4. React前端用Paperclip契约重构Agent交互体验Paperclip对React的影响远超一个Hook库。它迫使前端开发者放弃“组件即视图”的旧思维转向“组件即契约消费者”的新范式。那些“2026 react 前端面试 掘金”里热议的题目——useReducer vs useState、Suspense边界、自定义Hook设计原则——在Paperclip场景下都有明确答案。下面用一个真实案例展开如何用Paperclip契约实现“上传PDF→解析→生成摘要→导出”的全流程。4.1 usePaperclipTask超越useSWR的数据获取Hook传统useSWR只处理HTTP请求而usePaperclipTask必须管理任务全生命周期import { useState, useEffect, useCallback } from react; import { TaskDefinition, TaskResult, EventType } from paperclip/core; export const usePaperclipTask TInput, TOutput( taskType: string, options: { autoExecute?: boolean } {} ) { const [task, setTask] useStateTaskResultTOutput | null(null); const [isExecuting, setIsExecuting] useState(false); const [error, setError] useStatestring | null(null); // 订阅事件流 useEffect(() { const unsubscribe window.paperclipEventBus.subscribe( (event) { if (event.type task:status:update event.data.taskId task?.taskId) { setTask(event.data); } if (event.type task:output:chunk event.data.taskId task?.taskId) { // 处理流式输出 updateStreamingOutput(event.data.chunk); } } ); return () unsubscribe(); }, [task?.taskId]); const execute useCallback(async (input: TInput) { try { setIsExecuting(true); setError(null); // 构建符合Paperclip契约的TaskDefinition const taskDef: TaskDefinitionTInput, TOutput { type: taskType, input, metadata: { correlationId: generateCorrelationId(), timeoutMs: 120000, priority: normal, } }; // 调用网关API const response await fetch(/api/task/execute, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify(taskDef), }); if (!response.ok) throw new Error(HTTP ${response.status}); const result await response.json() as TaskResultTOutput; setTask(result); return result; } catch (err) { setError(err.message); throw err; } finally { setIsExecuting(false); } }, [taskType]); return { task, isExecuting, error, execute, reset: () { setTask(null); setError(null); } }; };这个Hook的关键创新点状态管理粒度更细isExecuting只表示“正在发起请求”task.status才是真实执行状态。避免useSWR的isValidating误导用户。事件驱动更新不依赖轮询而是监听task:status:update事件。当Agent状态从running变completedUI自动刷新。流式输出支持task:output:chunk事件被单独处理实现“边生成边显示”的体验这正是“react uplot k线图”类实时图表的需求基础。4.2 PaperclipProvider全局状态与事件总线的React化封装Paperclip要求所有组件共享同一个事件总线。PaperclipProvider是必须的顶层Wrapperimport React, { createContext, useContext, useEffect, useState } from react; import { EventStream, EventPayload } from paperclip/core; interface PaperclipContextValue { eventBus: EventStream; registerAgent: (identity: AgentIdentity) void; } const PaperclipContext createContextPaperclipContextValue | undefined(undefined); export const PaperclipProvider: React.FC{ children: React.ReactNode } ({ children }) { const [eventBus] useStateEventStream(() { // 创建浏览器端EventStream实现 return { subscribe: (handler) { window.addEventListener(paperclip:event, (e: CustomEvent) { handler(e.detail); }); return () window.removeEventListener(paperclip:event, () {}); }, publish: (event) { window.dispatchEvent(new CustomEvent(paperclip:event, { detail: event })); } }; }); useEffect(() { // 初始化时注册当前应用为Agent const appIdentity: AgentIdentity { id: web-app/${process.env.REACT_APP_VERSION}${getBuildHash()}, name: web-ui, version: process.env.REACT_APP_VERSION, capabilities: [ui:display, user:input], endpoints: { execute: , health: } }; eventBus.publish({ type: agent:state:update, data: { ...appIdentity, status: ready }, timestamp: Date.now(), source: appIdentity.id }); }, [eventBus]); return ( PaperclipContext.Provider value{{ eventBus, registerAgent: () {} }} {children} /PaperclipContext.Provider ); }; export const usePaperclip () { const context useContext(PaperclipContext); if (!context) throw new Error(usePaperclip must be used within PaperclipProvider); return context; };这个Provider解决了“react state与hooks”面试题的核心矛盾如何在多个组件间共享事件流而不引发状态混乱。它用CustomEvent替代Redux用window作为事件总线轻量且无额外依赖。registerAgent逻辑虽为空但为未来扩展留了钩子——比如当Web Worker作为Agent时需在此注册其postMessage通道。4.3 组件级契约用TypeScript Interface约束UI行为Paperclip要求UI组件严格遵循契约。以PDF解析结果展示组件为例import { TaskResult } from paperclip/core; interface PdfParseResult { pages: number; text: string; tables: Array{ header: string[]; rows: string[][] }; metadata: { title: string; author: string; created: string }; } // 组件Props必须精确匹配Paperclip契约 interface PdfParseDisplayProps { task: TaskResultPdfParseResult; onRetry: () void; onCancel: () void; } export const PdfParseDisplay: React.FCPdfParseDisplayProps ({ task, onRetry, onCancel }) { if (task.status pending) return div排队中.../div; if (task.status running) return ProgressBar progress{task.metrics?.durationMs} /; if (task.status failed) { switch (task.error?.code) { case FILE_CORRUPTED: return Alert typeerror文件损坏请重新上传/Alert; case FILE_TOO_LARGE: return Alert typewarning文件过大50MB请压缩后重试/Alert; default: return Alert typeerror{task.error?.message}/Alert; } } if (task.status completed task.output) { return ( div h2{task.output.metadata.title}/h2 p{task.output.text.substring(0, 200)}.../p Button onClick{() downloadPdfSummary(task.output)} 导出摘要 /Button /div ); } return null; };这里PdfParseResult接口不是随意定义的而是直接来自paperclip/core中document:parse任务的官方契约。前端开发者必须按此结构消费task.output不能擅自添加task.output.summary字段。这保证了当后端Agent升级为支持Markdown输出时前端只需修改PdfParseResult接口无需改组件逻辑——这就是Paperclip“契约即文档”的威力。4.4 错误边界与用户体验Paperclip Error Code驱动的UI策略Paperclip的error.code不是技术细节而是用户体验指令。PdfParseDisplay组件根据code决定UI行为error.codeUI响应用户感知FILE_NOT_FOUND显示“文件不存在请检查路径” 重新上传按钮无挫败感明确指引INPUT_VALIDATION_ERROR高亮输入框显示“PDF路径格式错误”即时反馈减少等待LLM_RATE_LIMIT显示“服务繁忙请1分钟后重试” 自动倒计时管理预期避免反复点击这种策略直接回答了“ai react框架和其他框架的区别”传统框架把错误当异常处理Paperclip框架把错误当状态分支。usePaperclipTask的error状态不是string而是{ code: string; message: string }组件用switch而非if处理确保每个错误码都有专属UI。实操避坑切勿在React组件里直接console.error(task.error)。Paperclip要求所有错误必须通过eventBus.publish({ type: system:health:alert, data: task.error })上报供运维平台聚合分析。我在某项目中见过因前端私自吞掉LLM_RATE_LIMIT错误导致运维无法发现配额瓶颈最终服务雪崩。5. OpenClaw集成Paperclip契约在生产环境的完整落地链路OpenClaw不是Paperclip的竞争对手而是它最成熟的企业级实现载体。理解OpenClaw就是理解Paperclip如何从理论契约变成可运维系统。我以“openclaw部署”为线索还原从源码编译到生产上线的完整链路揭示每个步骤背后的Paperclip设计意图。5.1 源码结构解密OpenClaw的Paperclip基因图谱OpenClaw仓库目录清晰体现Paperclip分层思想openclaw/ ├── core/ # Paperclip契约核心TypeScript定义 │ ├── contracts.ts # 2.1节讲的12个interface │ └── utils.ts # 工具函数如generateCorrelationId() ├── gateway/ # Node.js网关实现Express Redis │ ├── server.ts # 主服务含路由、校验、事件总线 │ └── agent-manager.ts # Agent注册、心跳、健康检查 ├── agents/ # 官方Agent集合Python/Go/JS │ ├── pdf-parser/ # 实现document:parse契约 │ │ ├── main.py # 必须导出execute(input)函数 │ │ └── contract.py # 声明supports [document:parse] │ └── text-summarizer/ # 实现document:summarize契约 ├── web/ # React前端Vite TypeScript │ ├── src/ │ │ ├── hooks/ # usePaperclipTask等契约Hook │ │ ├── providers/ # PaperclipProvider │ │ └── components/ # 契约化UI组件 └── deploy/ # 部署脚本Docker/K8s/Helm关键洞察agents/目录下的每个Agent都必须有一个contract.py或contract.ts文件显式声明supports数组。这是OpenClaw自动发现Agent能力的依据也是Paperclip“能力即契约”的体现。当你执行openclaw local deploy脚本会扫描此数组生成网关的初始注册表。5.2 本地一键部署Paperclip契约的自动化验证openclaw local deploy命令背后是精密的契约验证流水线# 步骤1构建所有Agent镜像 docker build -t openclaw/pdf-parser ./agents/pdf-parser/ # 步骤2启动网关自动加载core/contracts.ts docker run -d --name gateway \ -p 3000:3000 \ -v $(pwd)/core:/app/core \ openclaw/gateway # 步骤3启动Agent自动向网关注册 docker run -d --name pdf-parser \ --network host \ -e GATEWAY_URLhttp://localhost:3000 \ openclaw/pdf-parser # 步骤4契约验证关键 curl http://localhost:3000/api/health # 返回 { status: ok, agents: [{ id: ..., capabilities: [document:parse] }] }这个验证步骤解释了为什么“openclaw安装教程”强调curl检查——它不是测试网关是否启动而是验证Paperclip契约是否生效agents数组必须包含capabilities证明Agent已按契约注册。如果为空说明contract.py文件缺失或supports格式错误。5.3 生产环境部署Paperclip对基础设施的硬性要求OpenClaw生产部署暴露了Paperclip的底层依赖组件Paperclip要求实际配置方案热搜词关联Node.js网关必须支持WebSocket Redis Pub/Sub使用Node.js 18.20.4 LTS长期稳定 ioredis库node.js 18.20.4 lts版本下载Agent运行时必须能执行/registerHTTP请求Python Agent用requests库Go Agent用net/httpcentos 7.9 node.js安装部署因CentOS 7.9默认Python 2.7需升级前端构建必须打包paperclip/core类型定义Vite配置resolve.alias指向core/contracts.tsreact 面试题考模块解析原理监控系统必须采集task:status:update事件Prometheus Grafana指标名paperclip_task_duration_secondsopenclaw配置阿里云服务器免费试用需开通云监控服务特别注意centos 7.9问题该系统默认Python 2.7而Paperclip Agent要求Python 3.8。教程里“node.js安装”步骤实则是为网关服务Agent环境需单独配置pyenv。这是新手最容易卡住的环节——以为装好Node.js就万事大吉结果Agent注册失败。5.4 Microsoft Teams接入Paperclip契约的跨平台扩展openclaw 如何接入microsoft teams的本质是把Teams Bot的Activity对象翻译成Paperclip契约// Teams Bot接收消息 server.post(/api/messages, async (req, res) { const activity req.body as Activity; // 提取Teams消息映射为Paperclip TaskDefinition let taskDef: TaskDefinition; if (activity.text.startsWith(/summarize)) { taskDef { type: document:summarize, input: { url: extractUrlFromText(activity.text), format: markdown } }; } else if (activity.text.startsWith(/parse)) { taskDef { type: document:parse, input: { path: activity.attachments?.[0]?.contentUrl || } }; } // 调用Paperclip网关 const result await fetch(http://gateway:3000/api/task/execute, { method: POST, body: JSON.stringify(taskDef) }); // 将Paperclip TaskResult映射回Teams消息 const paperclipResult await result.json() as TaskResult; const teamsResponse mapToTeamsMessage(paperclipResult); await context.sendActivity(teamsResponse); });mapToTeamsMessage函数是关键桥梁。它把task.status completed转为Teams卡片把task.error?.code FILE_NOT_FOUND转为Teams提示气泡。这证明Paperclip不是封闭生态而是开放协议——只要能翻译任何平台都能接入。最后分享一个小技巧在OpenClaw部署后用curl -N http://localhost:3000/events长连接直接观察Paperclip事件流。你会看到实时滚动的agent:state:update和task:status:update事件这是理解整个系统脉搏的最快方式。比看日志直观十倍也比调试前端代码更接近真相。
阅读完成 · 觉得有帮助?