@vclaw/vrv
v0.1.2
Published
OpenClaw VRV channel plugin for inbound HTTP messaging and outbound pushApi replies
Readme
@vclaw/vrv
OpenClaw vrv 渠道插件,用来把你自己的业务系统通过 HTTP 或 WebSocket 接到 OpenClaw。
它做的事情很简单:
- 你的系统可以调用
POST /vrv/message,也可以通过插件主动建立的 websocket connector 长连接推送入站消息 - 插件把请求转成 OpenClaw 入站消息
- Agent 生成回复
- 回复按“websocket connector > SSE > pushApi”的顺序自动选路
能力概览
- 单一
pushApi回调地址 - 可选的 websocket connector,可同时用于入站消息和优先回推回复
- 自动 SSE 选路
- 兼容传统文本回调和结构化消息回调
- 多账户支持
- 自动 webhook 验签
- OpenClaw
status/security/actions/streaming/threading/setupEntry
兼容性
openclaw >= 2026.2.2openclaw <= 2026.3.13
安装
openclaw plugins install @vclaw/vrv配置
选择VRV渠道配置:
openclaw config --section channelswebsocket URL和pushApi至少配置一个,webhook不配置签名时不强制验签
配置完需要重启网关:
openclaw gateway restart核心点
1. 回复通道自动选路
插件会自动按下面的顺序选回复通道:
- 如果配置了
channels.vrv.websocket.url,并且外部长连接已连上:优先走 websocket connector - 如果当前会话已经订阅了 SSE:走
/vrv/stream - 其他情况:回退到
pushApi
2. 结构化能力由 capabilities 决定
现在不需要任何协议版本配置。
是否支持 richer message 能力,只看 capabilities:
{
"capabilities": {
"media": true,
"threads": true,
"reactions": true,
"edit": true,
"unsend": true,
"polls": true,
"cards": true,
"mentions": true
}
}capabilities 表示“外部 VRV 客户端支持哪些结构化能力”。
它的作用是告诉插件:
- 哪些 OpenClaw action 可以对外暴露
- 哪些回复可以安全地按结构化消息形态发出去
- 哪些能力需要退回成普通文本处理
字段说明:
media支持图片、文件等媒体消息。threads支持线程回复。reactions支持表情反应。edit支持编辑已发送消息。unsend支持撤回或删除已发送消息。polls支持投票消息。cards支持卡片、按钮等富消息。mentions支持提及用户等元数据。
两种消息形态
vrv 对外只需要理解两种消息形态:
- 文本消息形态
- 结构化消息形态
一句话理解
- 文本消息形态适合“发一段文本,收一段文本”
- 结构化消息形态适合“除了文本,还要表达线程、媒体、reaction、edit、poll 这些结构化语义”
入站区别
文本消息形态的入站是扁平文本:
{
"text": "你好",
"to": "user-001",
"sessionKey": "session-001"
}结构化消息形态的入站是带 message envelope 的结构化请求:
{
"eventType": "message.created",
"message": {
"messageId": "msg-001",
"conversationId": "group-001",
"threadId": "thread-001",
"chatType": "thread",
"from": {
"id": "user-001",
"name": "Alice"
},
"to": {
"id": "group-001",
"name": "Project Group"
},
"text": "你好",
"media": [],
"mentions": [],
"replyToMessageId": "msg-000"
}
}其中 message 是结构化消息的主体对象,用来表达一条完整消息,而不仅仅是一段文本。
最核心的区别是:
- 文本消息形态只有
text / to / sessionKey - 结构化消息形态有完整的
message对象,可以表达消息 ID、会话 ID、线程、媒体、mentions、replyTo
出站区别
文本消息形态的出站是文本回调:
{
"reply": "Agent reply text",
"to": "user-001",
"sessionKey": "session-001",
"end": true
}结构化消息形态的出站是结构化 action:
{
"action": "send",
"accountId": "tenant-a",
"conversationId": "group-001",
"threadId": "thread-001",
"message": {
"text": "这里是回复",
"media": [
{
"url": "https://example.com/file.png"
}
],
"replyToMessageId": "msg-000"
}
}什么时候使用结构化消息形态
只要出现下面这些需求之一,就应该使用结构化消息形态:
- 需要
conversationId:要表达稳定会话 ID - 需要
threadId:要表达线程 ID - 需要
messageId:要表达消息唯一 ID - 需要
replyToMessageId:要表达“回复哪一条消息” - 需要
media:要发送图片、文件等媒体内容 - 需要
mentions:要表达提及的用户或对象 - 需要
poll:要发送投票消息 - 需要
react:要对消息添加或移除表情反应 - 需要
edit:要编辑已发送消息 - 需要
unsend:要撤回或删除已发送消息
自动判断规则
插件现在的判断方式是:
- 入站请求里有
message对象:按结构化消息形态解析 - 入站请求里没有
message对象:按文本消息形态解析 - 出站如果只是普通文本:优先发文本消息形态的回调 payload
- 出站如果需要结构化表达:发结构化消息形态的 action payload
外部客户端应该怎么选
如果你的客户端只会:
- 发文本
- 收文本
那按文本消息形态接入就够了。
如果你的客户端需要支持下面任意能力:
- 线程
- 图片或其他媒体
- reaction
- 编辑消息
- 删除消息
- poll
那就应该按结构化消息形态接入。
配置
最小配置
最小可用配置:
openclaw config set channels.vrv '{
"enabled": true,
"pushApi": "http://127.0.0.1:19090/callback"
}' --json建议生产环境至少加上签名密钥:
openclaw config set channels.vrv '{
"enabled": true,
"pushApi": "http://127.0.0.1:19090/callback",
"webhookSecret": "replace-with-a-long-random-secret"
}' --jsonwebsocket connector 配置
如果你的外部 VRV 服务提供了 WebSocket 长连接入口,推荐这样配:
openclaw config set channels.vrv '{
"enabled": true,
"websocket": {
"enabled": true,
"url": "ws://127.0.0.1:19091/ws"
},
"webhookSecret": "replace-with-a-long-random-secret"
}' --json说明:
websocket.url:外部 VRV 服务的 WebSocket 地址websocket.enabled:是否启用这条外连 connectorwebhookSecret:推荐,用于保护POST /vrv/message
这是一种更接近 OpenClaw 官方渠道的接法:
- 插件主动连接外部 VRV WebSocket 服务
- 外部服务可以通过这条长连接把消息推回插件,作为入站消息
- 外部服务也会优先通过这条长连接接收插件回复
纯 SSE 配置
如果你希望主要通过 SSE 收回复,channels.vrv 最少可以只配:
openclaw config set channels.vrv '{
"enabled": true
}' --json更推荐:
openclaw config set channels.vrv '{
"enabled": true,
"webhookSecret": "replace-with-a-long-random-secret"
}' --json说明:
enabled: 必填webhookSecret: 推荐,开启自动验签pushApi: 纯 SSE 场景可不配capabilities: 只有需要结构化能力时才需要配
注意:
- 没有
pushApi和websocket时,只有“当前会话已经有活跃 SSE 订阅”才能正常收回复 - 如果没有活跃 SSE 订阅,请求会被视为当前账户未配置完成
带结构化能力的配置
如果你的外部 VRV 客户端支持媒体、线程、reaction、edit、poll 等能力,可以这样配:
openclaw config set channels.vrv '{
"enabled": true,
"pushApi": "https://example.com/vrv/callback",
"capabilities": {
"media": true,
"threads": true,
"reactions": true,
"edit": true,
"unsend": true,
"polls": true,
"cards": true,
"mentions": true
},
"webhookSecret": "replace-with-a-long-random-secret"
}' --json含义:
- 普通文本回复仍可能走传统文本 payload
- 真的需要结构化表达时,插件会优先尝试走结构化 action
- 如果当前回复走的是
pushApi,结构化内容会发成 action payload - 如果当前回复走的是 websocket connector,普通 Agent 文本回复仍然是 websocket reply/final;
sendMedia、react、edit、poll这类显式结构化动作才会发成 websocket action
多账户配置
{
"enabled": true,
"webhookSecret": "replace-with-a-long-random-secret",
"defaultAccount": "tenant-a",
"accounts": {
"tenant-a": {
"enabled": true,
"name": "Tenant A"
},
"tenant-b": {
"enabled": true,
"name": "Tenant B",
"pushApi": "https://b.example.com/callback",
"capabilities": {
"media": true,
"threads": true
}
}
}
}入站请求可通过 accountId 或 channelAccountId 指定账户;不传时使用 defaultAccount。
入站 API
插件会注册:
POST /vrv/message
GET /vrv/stream文本消息形态入站
{
"text": "你好",
"to": "user-001",
"sessionKey": "session-001",
"accountId": "tenant-a"
}字段:
text: 必填to: 目标用户或会话 ID,可选sessionKey: 会话键,可选,默认等于toaccountId: 多账户场景可选
结构化消息形态入站
当请求里带 message 对象时,插件会按结构化消息解析:
{
"eventType": "message.created",
"accountId": "tenant-a",
"capabilities": {
"media": true,
"threads": true
},
"message": {
"messageId": "msg-001",
"conversationId": "group-001",
"threadId": "thread-001",
"chatType": "thread",
"from": {
"id": "user-001",
"name": "Alice"
},
"to": {
"id": "group-001",
"name": "Project Group"
},
"text": "你好",
"media": [
{
"url": "https://example.com/file.png",
"mimeType": "image/png",
"filename": "file.png"
}
],
"mentions": ["user-002"],
"replyToMessageId": "msg-000"
}
}关键字段:
messageId: 当前消息的唯一 IDconversationId: 稳定会话 IDthreadId: 线程 IDchatType:direct/group/threadfrom: 发送方信息to: 目标用户或目标会话信息text: 文本内容media[]: 媒体列表mentions[]: 被提及对象replyToMessageId: 回复目标消息
出站规则
出站只有这 3 条规则:
- 当前账户的 websocket connector 已连接:优先走 websocket connector
- 没有 websocket connector,但当前
sessionKey有活跃 SSE 订阅:走 SSE - 其他情况:走
pushApi
文本消息形态回调 payload
{
"reply": "Agent reply text",
"to": "user-001",
"sessionKey": "session-001",
"end": true
}结构化消息形态 action payload
{
"action": "send",
"accountId": "tenant-a",
"conversationId": "group-001",
"threadId": "thread-001",
"message": {
"text": "这里是回复",
"media": [
{
"url": "https://example.com/file.png"
}
],
"replyToMessageId": "msg-000"
}
}当前会发出的 action:
sendpollreacteditunsend
websocket connector 消息格式
当 websocket connector 建立后:
- 外部服务可以通过这条长连接向插件发送入站消息,不必再调用
POST /vrv/message - 当回复走 websocket connector 时,插件也会向外部 WebSocket 服务发送 JSON 消息
文本回复示例:
{
"type": "reply",
"transport": "websocket",
"accountId": "tenant-a",
"payload": {
"reply": "Agent reply text",
"to": "user-001",
"sessionKey": "session-001",
"end": false
}
}最终回复示例:
{
"type": "final",
"transport": "websocket",
"accountId": "tenant-a",
"payload": {
"reply": "最终回复",
"to": "user-001",
"sessionKey": "session-001",
"end": true
}
}结构化 action 示例:
{
"type": "action",
"transport": "websocket",
"accountId": "tenant-a",
"payload": {
"action": "send",
"accountId": "tenant-a",
"conversationId": "group-001",
"threadId": "thread-001",
"message": {
"text": "这里是回复"
}
}
}SSE 协议
GET /vrv/stream?sessionKey=session-001[&accountId=tenant-a]
Content-Type: text/event-stream事件:
readyreplyfinal
示例:
---- sse event ready ----
data:
{"sessionKey":"session-001","accountId":"default"}
---- sse event reply ----
data:
{"reply":"第一段回复","to":"user-001","sessionKey":"session-001","end":false}
---- sse event final ----
data:
{"reply":"最终回复","to":"user-001","sessionKey":"session-001","end":true}入站鉴权
规则很简单:
- 没配
webhookSecret:不验签 - 配了
webhookSecret:自动强制验签
请求头:
x-vrv-timestampx-vrv-signature
签名算法:
hex(HMAC_SHA256(webhookSecret, timestamp + "." + rawBody))默认接受 5 分钟内的请求。
OpenClaw 标准适配面
当前已实现:
onboarding安装后可以通过引导页面配置pushApi、签名密钥和结构化能力。status可以查看渠道是否启用、是否配置了回调地址、是否只依赖 SSE 等状态信息。security可以查看当前 webhook 是否开启了签名校验。actions.send支持从 OpenClaw 主动发送消息到 VRV 客户端。actions.poll支持发送投票消息。actions.react支持对消息添加或移除表情反应。actions.edit支持编辑已发送消息。actions.unsend支持撤回或删除已发送消息。outbound.sendText支持发送文本消息。outbound.sendMedia支持发送媒体消息。outbound.sendPoll支持发送投票消息。threading支持线程上下文,比如在线程里回复、回复某条已有消息。streaming支持流式回复输出,在没有 websocket connector 时优先通过 SSE 发送。setupEntry支持在安装、修复、引导配置时走轻量入口,不提前加载完整运行逻辑。
当前仍不支持:
nativeCommands
本地调试
统一使用:
npm run debug:vrv脚本会让你选两次:
- 消息形态:文本 / 结构化
- 回复模式:
pushApi/SSE stream/websocket connector
回车默认:
- 文本消息形态
pushApi- 发送
scripts/vrv-debug-config.json里配置的默认文本
覆盖链路:
- 文本消息形态 +
pushApi:验证文本回调 payload - 结构化消息形态 +
pushApi:验证结构化 action 回调 - 两种消息形态 +
SSE stream:验证自动 SSE - 两种消息形态 +
websocket connector:验证插件主动连接外部 WebSocket 服务后的回流
交互方式:
- 直接回车:发送配置文件里的默认文本
- 输入一段文本再回车:发送你刚输入的文本
调试配置文件:
scripts/vrv-debug-config.json常见调试流程:
- 配好 OpenClaw
channels.vrv openclaw gateway restart- 运行
npm run debug:vrv - 选择要测的模式
快速验证
查看日志:
openclaw logs --follow发送一条最小请求:
curl -X POST "http://127.0.0.1:18789/vrv/message" \
-H "Content-Type: application/json" \
-d '{"text":"你好","to":"user-001","sessionKey":"session-001"}'本地开发
常用命令:
npm test
npm run test:unit
npm run build
npm run debug:vrv
npm run release:check本地安装调试:
openclaw plugins uninstall vrv --force
openclaw plugins install -l /Users/yalejian/IdeaProjects/Work/ai/vrv-claw-plugin
openclaw gateway restart项目结构
dist/:发布产物src/core/:账户、能力、常量src/http/:HTTP 入站、SSE、监控日志src/outbound/:channel 声明、actions、出站逻辑src/status.ts/src/security.ts/src/onboarding.ts:标准 adaptertests/:单测和 e2e
发布包内容
发布到 npm 的内容包括:
dist/openclaw.plugin.jsonvrv.channel.schema.json- 文档
设计与许可
- 设计说明见 DESIGN.md
- 更新日志见 CHANGELOG.md
- 协议见 LICENSE
