Tools
Yeizi Openclaw
yeizi-openclaw-plugin
Install
npm install
npm
Configuration Example
{
"type": "message",
"text": "用户消息内容",
"from": "user_xxx",
"to": "yeizi",
"messageId": "1700000000000",
"chatType": "dm"
}
README
# Yeizi Channel 插件开发需求文档
## 1. 项目概述
### 1.1 项目名称
Yeizi Channel 插件
### 1.2 项目目标
开发一个基于 Web 的 OpenClaw 插件系统,包含:
- **Web 端**:提供用户对话界面和后端服务(鉴权、WebSocket 通信)
- **OpenClaw 插件**:作为 OpenClaw 的 channel 插件,与 Web 后端建立连接并处理消息
### 1.3 项目范围
**当前版本:单智能体、单账户、单用户**
- **单智能体**:只支持一个智能体(默认 `main`)
- **单账户**:只支持一个账户(默认 `default`)
- **单用户**:用户无需登录,前端自动生成用户标识
### 1.4 核心功能
用户通过 Web 界面输入消息,消息通过 WebSocket 传递给 OpenClaw 插件,插件再与 OpenClaw 进行通信,最终将 AI 回复返回给用户。
## 2. 系统架构
### 2.1 整体架构图
```
┌────────────────────────────────────────────────────────────────┐
│ 用户浏览器 │
├────────────────────────────────────────────────────────────────┤
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Web 前端(对话页面) │ │
│ │ • 用户输入消息 │ │
│ │ • 显示 AI 回复 │ │
│ │ • WebSocket 实时通信 │ │
│ │ • 插件配置显示 │ │
│ └──────────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────────┘
▲ │
│ │ WebSocket
AI 回复 │ 用户消息
│ ▼
└────────────────────────────────────────────────►
│
▼
┌────────────────────────────────────────────────────────────────┐
│ Web 后端服务 │
├────────────────────────────────────────────────────────────────┤
│ • 鉴权服务(AppKey + AppSecret) │
│ • WebSocket 服务 │
│ • 消息路由 │
│ • 配置查询 API (/api/config) │
└────────────────────────────────────────────────────────────────┘
▲ │
│ │ WebSocket
AI 回复 │ 消息转发
│ ▼
└────────────────────────────────────────────────►
│
▼
┌────────────────────────────────────────────────────────────────┐
│ OpenClaw 插件 │
├────────────────────────────────────────────────────────────────┤
│ • WebSocket 客户端(接收/发送消息) │
│ • dispatchReplyWithBufferedBlockDispatcher │
│ • ChannelDock 配置 │
│ • YeiziDock 定义插件能力 │
└────────────────────────────────────────────────────────────────┘
│
AI 回复
(OpenClaw API)
```
### 2.2 消息流程
#### 消息发送流程(用户 → OpenClaw)
```
用户输入消息
↓
前端通过 WebSocket 发送消息到后端
↓
后端转发消息到 OpenClaw 插件
↓
插件构建 ctxPayload
↓
调用 finalizeInboundContext
↓
调用 dispatchReplyWithBufferedBlockDispatcher 触发 AI 处理
```
#### 消息回复流程(OpenClaw → 用户)
```
OpenClaw AI 处理完成
↓
dispatchReplyWithBufferedBlockDispatcher 的 deliver 回调触发
↓
插件通过 WebSocket 发送回复到后端
↓
后端通过 WebSocket 推送到前端
↓
前端显示 AI 回复
```
## 3. 安装与配置
### 3.1 环境要求
- **Node.js**:>= 18.0.0
- **OpenClaw**:>= 2026.3.12
### 3.2 安装步骤
#### 步骤 1:构建插件
```bash
cd yeizi-plugin
npm install
npm run build
```
#### 步骤 2:安装插件
```bash
cd yeizi-plugin/scripts
node setup.mjs <app_key> <app_secret> [base_url] [websocket_url]
```
示例:
```bash
node setup.mjs yeizi-app-key-2026 yeizi-app-secret-2026 http://localhost:3000 ws://localhost:3000
```
#### 步骤 3:重启 OpenClaw
安装完成后需要重启 OpenClaw Gateway 使配置生效。
### 3.3 后端配置
后端使用环境变量配置(.env 文件):
```bash
# 应用配置
APP_KEY=yeizi-app-key-2026
APP_SECRET=yeizi-app-secret-2026
# 服务地址配置
HOST=localhost
PORT=3000
PROTOCOL=http
# 服务地址
BASE_URL=http://localhost:3000
WEBSOCKET_URL=ws://localhost:3000
```
## 4. 项目结构
```
yeizi/
├── web-channel/ # Web 端项目
│ ├── frontend/ # 前端项目(Vue 3 + TypeScript)
│ │ ├── src/
│ │ │ ├── components/ # Vue 组件
│ │ │ │ ├── ChatInput.vue # 消息输入组件
│ │ │ │ ├── ChatMessage.vue # 消息显示组件
│ │ │ │ ├── ChatWindow.vue # 聊天窗口组件
│ │ │ │ ├── ConnectionStatus.vue # 连接状态组件
│ │ │ │ └── SettingsPanel.vue # 插件配置页面
│ │ │ ├── composables/
│ │ │ │ └── useWebSocket.ts # WebSocket 钩子
│ │ │ ├── stores/
│ │ │ │ └── chat.ts # 聊天状态管理
│ │ │ └── App.vue # 主应用组件
│ │ └── package.json
│ ├── backend/ # 后端项目(Express.js)
│ │ ├── src/
│ │ │ ├── routes/
│ │ │ │ ├── auth.ts # 鉴权路由
│ │ │ │ └── config.ts # 配置查询路由
│ │ │ ├── services/
│ │ │ │ ├── auth.ts # 鉴权服务
│ │ │ │ ├── config.ts # 配置服务
│ │ │ │ └── websocket.ts # WebSocket 管理
│ │ │ └── index.ts # 服务入口
│ │ ├── .env # 环境变量配置
│ │ └── package.json
│ └── package.json
│
└── yeizi-plugin/ # OpenClaw 插件项目
├── src/
│ ├── accounts.ts # 账户管理工具
│ ├── channel.ts # Channel Plugin 实现
│ ├── config-schema.ts # 配置 Schema 定义
│ ├── runtime.ts # 运行时存储管理
│ ├── types.ts # 类型定义
│ └── websocket-client.ts # WebSocket 客户端封装
├── scripts/
│ ├── setup.mjs # 安装脚本
│ └── README.md # 安装说明
├── index.ts # 插件运行时入口
├── openclaw.plugin.json # 插件元数据
├── package.json
└── tsconfig.json
```
## 5. 插件核心实现
### 5.1 插件入口 (index.ts)
```typescript
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
import { plugin, yeiziDock } from "./src/channel.js";
import { setRuntime } from "./src/runtime.js";
export { plugin, yeiziDock } from "./src/channel.js";
const yeizi: any = {
id: "yeizi",
name: "Yeizi",
description: "Yeizi Channel 插件",
configSchema: emptyPluginConfigSchema(),
register(api: OpenClawPluginApi) {
setRuntime(api.runtime);
api.registerChannel({ plugin, dock: yeiziDock });
},
};
export function register(api: OpenClawPluginApi) {
yeizi.register(api);
}
export function activate(api: OpenClawPluginApi) {
register(api);
}
export default yeizi;
```
### 5.2 ChannelDock 定义
```typescript
export const yeiziDock: ChannelDock = {
id: "yeizi",
capabilities: {
chatTypes: ["direct"],
blockStreaming: true,
},
};
```
### 5.3 消息处理流程
```typescript
// 1. 启动账户时建立 WebSocket 连接
startAccount: async (ctx) => {
// HTTP 鉴权获取 token
const authResponse = await fetch(`${account.config.baseUrl}/api/auth/token`, {
method: 'POST',
body: JSON.stringify({
appKey: account.config.appKey,
appSecret: account.config.appSecret,
}),
});
// 建立 WebSocket 连接
const wsClient = new WebSocketClient({
url: `${account.config.websocketUrl}/ws/plugin`,
token: authData.token,
onMessage: handleMessage,
});
wsClient.connect();
// 等待 abort 信号
return new Promise((resolve) => {
ctx.abortSignal?.addEventListener('abort', () => {
wsClient.disconnect();
resolve();
});
});
}
// 2. 收到消息时处理
onMessage: (message: WebSocketMessage) => {
// 构建 ctxPayload
const ctxPayload = {
Body: message.text,
BodyForAgent: message.text,
RawBody: JSON.stringify(message),
From: message.from ?? 'unknown',
To: message.to ?? 'yeizi',
ChatType: 'dm',
Provider: 'yeizi',
Surface: 'yeizi',
AgentId: agentId,
Timestamp: Date.now(),
AccountId: accountId,
MessageSid: message.messageId ?? Date.now().toString(),
OriginatingChannel: 'yeizi',
OriginatingTo: message.to ?? 'yeizi',
};
const finalized = runtime.channel.reply.finalizeInboundContext(ctxPayload);
// 分发消息到 AI 处理
await runtime.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
ctx: finalized,
cfg,
dispatcherOptions: {
deliver: async (payload) => {
const textOut = String(payload.text ?? payload.body ?? "");
const target = message.from;
if (!target || !textOut.trim()) {
return;
}
wsClient.send({
type: 'response',
payload: {
content: textOut,
messageId: message.messageId,
to: target,
},
});
},
},
});
}
```
### 5.4 出站消息配置
```typescript
outbound: {
deliveryMode: 'direct',
chunker: (text: string, _limit: number) => [text],
textChunkLimit: 4096,
sendText: async ({ to, text, accountId }) => {
const wsClient = accountConnections.get(accountId ?? 'default');
const messageId = Date.now().toString();
if (!wsClient || !wsClient.isConnected()) {
return { channel: 'yeizi', ok: false, messageId };
}
const sent = wsClient.send({
type: 'response',
payload: { content: text, messageId, to },
});
return { channel: 'yeizi', ok: sent, messageId };
},
},
```
## 6. WebSocket 消息格式
### 6.1 前端 → 插件(用户消息)
```json
{
"type": "message",
"text": "用户消息内容",
"from": "user_xxx",
"to": "yeizi",
"messageId": "17000000
... (truncated)
tools
Comments
Sign in to leave a comment