跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像AI 生图工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
TypeScriptNode.jsAI大前端

OpenClaw WebSocket 通道开发实战:构建自定义 AI 通信通道

基于 OpenClaw 框架开发 WebSocket 通道的完整流程。内容包括项目架构设计,涉及 Python 服务端、Vue 前端及 Node.js 通道插件。详细阐述了插件初始化、配置适配器、状态管理及网关适配器的实现细节,重点讲解了如何通过 WebSocket 实现用户与 AI 的双向通信。提供了从环境搭建到代码实现的步骤,帮助开发者快速掌握 OpenClaw 插件开发技能。

小熊软糖发布于 2026/4/5更新于 2026/7/1948 浏览
OpenClaw WebSocket 通道开发实战:构建自定义 AI 通信通道

项目背景

技术栈
前端层:Vue 3 + WebSocket ↓ 服务端:Python + aiohttp + uv ↓ 通道层:Node.js + ws + OpenClaw Plugin SDK ↓ AI 层:OpenClaw Gateway + LLM Provider 

快速开始

项目结构
openclaw-websocket-channel/
├── websocket-service/ # Python WebSocket 服务端
│   ├── app.py # aiohttp 主程序
│   └── requirements.txt # Python 依赖
├── websocket-web/ # Vue 3 前端
│   ├── src/
│   │   └── App.vue # 主界面
│   └── package.json
└── websocket-channel/ # OpenClaw 通道插件
    ├── index.ts # 插件主逻辑
    └── openclaw.plugin.json
1. 启动 Python WebSocket 服务端
# 进入服务端目录
cd websocket-service
# 使用 uv 安装依赖
uv sync
# 启动服务端
python app.py
# 默认监听:ws://localhost:8765
2. 启动 Vue 前端
# 进入前端目录
cd websocket-web
# 安装依赖
npm install
# 开发模式运行
npm run dev
# 访问:http://localhost:3000

前端界面功能:

  • 实时聊天窗口
  • 连接状态显示
  • 消息收发日志
3. 安装 WebSocket Channel
# 进入通道插件目录
cd websocket-channel
# 安装到 OpenClaw
openclaw plugins install .
# 验证安装
openclaw plugins list
# 应该看到:websocket-channel
4. 配置 OpenClaw

编辑 ~/.openclaw/config.json(或通过 Web UI):

{"channels":{"websocket-channel":{"enabled":true,"config":{"enabled":true,"wsUrl":"ws://localhost:8765/openclaw"}}}}

配置说明:

  • enabled: 启用通道
  • wsUrl: WebSocket 服务端地址
  • 无需 groupPolicy:默认就是开放模式
5. 重启 OpenClaw Gateway
# 如果使用 macOS 应用
# 点击菜单栏 OpenClaw → Restart Gateway
# 或命令行重启
pkill -f openclaw-gateway openclaw gateway run
6. 测试
  1. 打开浏览器访问前端:http://localhost:3000
  2. 点击'连接'按钮
  3. 发送消息:'你好,请介绍一下自己'
  4. 等待 AI 回复…

预期效果:

你:你好,请介绍一下自己
AI:你好!我是你的个人 AI 助手,基于 OpenClaw 框架运行。
我可以帮助你回答问题、编写代码、分析数据等。
有什么我可以帮你的吗?😊

程序架构

整体架构图
┌─────────────────┐
│ 用户浏览器 │
│ (Vue 前端) │
└────────┬────────┘
│ WebSocket │
│ ws://localhost:8765 ▼
┌─────────────────┐
│ Python 服务端 │
│ (aiohttp) │
└────────┬────────┘
│ WebSocket │ 长连接 ▼
┌─────────────────┐
│ Node.js 通道 │
│ (ws 库) │
└────────┬────────┘
│ OpenClaw Plugin API ▼
┌─────────────────┐
│ OpenClaw │
│ Gateway │
└────────┬────────┘
│ ▼
┌─────────────────┐
│ AI Provider │
│ (Qwen/Bailian) │
└─────────────────┘
数据流详解
入站消息(前端 → AI)
1. 用户在 Vue 界面输入消息 ↓
2. 前端通过 WebSocket 发送到 Python 服务端 ↓
3. Python 服务端转发给 Node.js 通道 ↓
4. Node.js 通道的 ws.on("message") 接收 ↓
5. 标准化消息格式 ↓
6. 调用 OpenClaw 框架 API ↓
7. Gateway 调用 AI Provider 生成回复
出站消息(AI → 前端)
1. AI 生成回复文本 ↓
2. OpenClaw 调用 deliver 回调 ↓
3. Node.js 通道通过 WebSocket 发送给 Python 服务端 ↓
4. Python 服务端转发给 Vue 前端 ↓
5. 前端界面显示 AI 回复

Channel 开发详解

1. 项目初始化
# 创建插件目录
mkdir -p openclaw-websocket-channel/websocket-channel
cd openclaw-websocket-channel/websocket-channel
# 创建基础文件
touch index.ts openclaw.plugin.json package.json
2. 定义插件元数据

index.ts:

import type { ReplyPayload } from "openclaw/auto-reply/types";
import type { ChannelPlugin, OpenClawConfig } from "openclaw/plugin-sdk";
import { createDefaultChannelRuntimeState } from "openclaw/plugin-sdk";
interface WebSocketChannelConnection {
  ws: any;
  accountId: string;
}
interface WebSocketChannelAccount {
  accountId: string;
  wsUrl: string;
  enabled?: boolean;
  configured?: boolean;
  dmPolicy?: "pairing" | "allowlist" | "open" | "disabled";
}
const connections = new Map<string, WebSocketChannelConnection>();
let pluginRuntime: any = null;
const WebSocketChannel: ChannelPlugin<WebSocketChannelAccount> = {
  id: "websocket-channel",
  meta: {
    id: "websocket-channel",
    label: "Websocket Channel",
    selectionLabel: "Websocket Channel (Custom)",
    docsPath: "/channels/websocket-channel",
    blurb: "WebSocket based messaging channel.",
    aliases: ["ws"],
  },
// ... 其他配置
};
3. 实现配置适配器
config: {
  /**
   * 列出所有配置的账户 ID
   * @returns 固定返回 ["default"]
   */
  listAccountIds: (cfg: OpenClawConfig) => {
    return ["default"];
  },
  /**
   * 解析账户配置
   */
  resolveAccount: (cfg: OpenClawConfig, accountId: string) => {
    const channelCfg = cfg.channels?.["websocket-channel"];
    if (!channelCfg || !channelCfg.config) {
      return undefined;
    }
    const config = channelCfg.config as any;
    return {
      accountId: "default",
      wsUrl: config.wsUrl || "ws://localhost:8765/openclaw",
      enabled: config.enabled !== false,
    };
  },
  /**
   * 检查账户是否已配置
   */
  isConfigured: async (account, cfg) => {
    return Boolean(account.wsUrl && account.wsUrl.trim() !== "");
  },
}
4. 实现状态管理适配器 ⭐关键
status: {
  /**
   * 默认运行时状态模板
   * ⚠️ 必须实现这个方法,否则 UI 会显示 "0/1 connected"
   */
  defaultRuntime: createDefaultChannelRuntimeState("default", {
    wsUrl: null,
    connected: false,
    groupPolicy: null,
  }),
  /**
   * 构建通道摘要(用于 UI 显示)
   */
  buildChannelSummary: ({ snapshot }) => ({
    wsUrl: snapshot.wsUrl ?? null,
    connected: snapshot.connected ?? null,
    groupPolicy: snapshot.groupPolicy ?? null,
  }),
  /**
   * 构建账户完整快照
   */
  buildAccountSnapshot: ({ account, runtime }) => ({
    accountId: account.accountId,
    enabled: account.enabled,
    configured: account.configured,
    wsUrl: account.wsUrl,
    running: runtime?.running ?? false,
    connected: runtime?.connected ?? false,
    groupPolicy: runtime?.groupPolicy ?? null,
    lastStartAt: runtime?.lastStartAt ?? null,
    lastStopAt: runtime?.lastStopAt ?? null,
    lastError: runtime?.lastError ?? null,
  }),
}

为什么需要 defaultRuntime?

OpenClaw 的 UI 通过读取通道的 defaultRuntime 来知道要跟踪哪些状态字段。如果没有这个配置:

  • UI 不知道要显示 connected 字段
  • 即使你在 startAccount 中设置了 connected: true
  • UI 也只会显示'0/1 connected'

正确做法:

  1. 在 defaultRuntime 中声明要跟踪的字段(包括 connected: false)
  2. 在 startAccount 开始时调用 ctx.setStatus({ connected: true })
  3. UI 就会正确显示'1/1 connected'
5. 实现网关适配器(核心)
gateway: {
  /**
   * 启动 WebSocket 账户连接
   */
  startAccount: async (ctx) => {
    const { log, account, abortSignal, cfg } = ctx;
    log?.info(`[websocket-channel] Starting WebSocket Channel for ${account.accountId}`);
    // 获取 runtime API
    const runtime = pluginRuntime;
    // ⭐ 关键:设置初始状态为已连接
    ctx.setStatus({
      accountId: account.accountId,
      wsUrl: account.wsUrl,
      running: true,
      connected: true,
    });
    log?.info(`[websocket-channel] Status set: connected=true, running=true`);
    // 创建 WebSocket 连接
    const WebSocketLib = await import("ws");
    const ws = new (WebSocketLib.default as any)(account.wsUrl);
    // 存储连接
    connections.set(account.accountId, { ws, accountId: account.accountId });
    // 监听消息事件
    ws.on("message", async (data: Buffer) => {
      try {
        // 1. 解析原始消息
        const rawData = data.toString();
        const eventData = JSON.parse(rawData);
        const innerData = eventData.data || {};
        // 2. 标准化消息
        const normalizedMessage = {
          id: `${eventData.source || "websocket"}-${Date.now()}`,
          channel: "websocket-channel",
          accountId: account.accountId,
          senderId: innerData.source || eventData.source || "unknown",
          senderName: innerData.source || eventData.source || "Unknown",
          text: innerData.content || innerData.text || "",
          timestamp: innerData.timestamp || Date.now().toISOString(),
          isGroup: false,
          groupId: undefined,
          attachments: [],
          metadata: {},
        };
        log?.info(`[websocket-channel] 📨 Received: "${normalizedMessage.text}" from ${normalizedMessage.senderId}`,);
        // 3. 解析路由
        const route = runtime.channel.routing.resolveAgentRoute({
          cfg,
          channel: "websocket-channel",
          accountId: account.accountId,
          peer: { kind: "direct", id: normalizedMessage.senderId },
        });
        // 4. 构建消息上下文
        const ctxPayload = runtime.channel.reply.finalizeInboundContext({
          Body: normalizedMessage.text,
          BodyForAgent: normalizedMessage.text,
          From: normalizedMessage.senderId,
          To: undefined,
          SessionKey: route.sessionKey,
          AccountId: route.accountId,
          ChatType: "direct",
          SenderName: normalizedMessage.senderName,
          SenderId: normalizedMessage.senderId,
          Provider: "websocket-channel",
          Surface: "websocket-channel",
          MessageSid: normalizedMessage.id,
          Timestamp: Date.now(),
        });
        // 5. 调用框架调度器
        await runtime.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
          ctx: ctxPayload,
          cfg: cfg,
          dispatcherOptions: {
            deliver: async (payload: ReplyPayload, { kind }) => {
              log?.info(`[websocket-channel] Delivering ${kind} reply via WebSocket...`);
              const currentConn = connections.get(account.accountId);
              if (!currentConn || !currentConn.ws || currentConn.ws.readyState !== 1) {
                throw new Error("No WebSocket connection available");
              }
              // 发送 AI 回复
              currentConn.ws.send(JSON.stringify({ type: "reply", content: payload.text || "", kind }));
            },
            onError: (err, { kind }) => {
              log?.error(`[websocket-channel] Delivery error for ${kind}: ${err.message}`);
            },
          },
        });
        log?.info(`[websocket-channel] Message dispatched successfully`);
      } catch (err) {
        log?.error(`[websocket-channel] Failed to process message: ${err.message}`);
      }
    });
    // 监听错误和关闭
    ws.on("error", (err: Error) => {
      log?.error(`[websocket-channel] ❌ WebSocket error: ${err.message}`);
      connections.delete(account.accountId);
      reject(err);
    });
    ws.on("close", () => {
      log?.info(`[websocket-channel] 🔴 Connection closed`);
      connections.delete(account.accountId);
      resolve();
    });
    // 监听中止信号
    abortSignal.addEventListener("abort", () => {
      log?.info(`[websocket-channel] ⏹️ Abort requested`);
      ws.close();
      resolve();
    });
    // 保持连接运行
    await Promise.race([
      connectionPromise,
      new Promise<void>((resolve) => {
        abortSignal.addEventListener("abort", () => resolve());
      }),
    ]);
    connections.delete(account.accountId);
  },
}
6. 注册插件入口
/**
 * 注册插件入口
 * @param api - 插件 API
 */
export default function register(api: any) {
  console.log("[websocket-channel] Registering WebSocket Channel plugin");
  pluginRuntime = api.runtime;
  api.registerChannel({ plugin: WebSocketChannel });
}

参考链接

官方文档
  • OpenClaw 官方文档
  • Plugin SDK 源码
  • 通道开发指南
  • Channel 插件开发指导手册

目录

  1. 项目背景
  2. 技术栈
  3. 快速开始
  4. 项目结构
  5. 1. 启动 Python WebSocket 服务端
  6. 进入服务端目录
  7. 使用 uv 安装依赖
  8. 启动服务端
  9. 默认监听:ws://localhost:8765
  10. 2. 启动 Vue 前端
  11. 进入前端目录
  12. 安装依赖
  13. 开发模式运行
  14. 访问:http://localhost:3000
  15. 3. 安装 WebSocket Channel
  16. 进入通道插件目录
  17. 安装到 OpenClaw
  18. 验证安装
  19. 应该看到:websocket-channel
  20. 4. 配置 OpenClaw
  21. 5. 重启 OpenClaw Gateway
  22. 如果使用 macOS 应用
  23. 点击菜单栏 OpenClaw → Restart Gateway
  24. 或命令行重启
  25. 6. 测试
  26. 程序架构
  27. 整体架构图
  28. 数据流详解
  29. 入站消息(前端 → AI)
  30. 出站消息(AI → 前端)
  31. Channel 开发详解
  32. 1. 项目初始化
  33. 创建插件目录
  34. 创建基础文件
  35. 2. 定义插件元数据
  36. 3. 实现配置适配器
  37. 4. 实现状态管理适配器 ⭐关键
  38. 5. 实现网关适配器(核心)
  39. 6. 注册插件入口
  40. 参考链接
  41. 官方文档
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • C++ 高性能订单簿(Order Book)核心实现
  • 大语言模型发展历程、构建与使用指南
  • 使用文心一言设计智能体工作流提示词的技巧
  • AIGC 内容创作方法论:从爆款短片《牌子》解析技术路径
  • 2024 年转行 AI 产品经理:行业现状与能力准备指南
  • 解密 Copilot:如何打造高效的 AI 原生应用
  • LeetCode 239 滑动窗口最大值:单调队列解法
  • ComfyUI-Manager 插件安装与管理指南
  • GLM-5 与 Qwen3.5 大模型 API 接入实战指南
  • AI 代码助手横评:CodeGeex、RooCode 与 GitHub Copilot
  • 开源智能排产系统 JVS-APS:算法驱动与低代码融合实践
  • Lostlife2.0 整合 LLama-Factory 引擎重塑 NPC 对话逻辑
  • HashMap、ArrayMap 与 SparseArray 对比及选型指南
  • OpenClaw 部署与飞书机器人接入实战指南
  • Midjourney 第三方 API 服务技术原理与合规实践
  • OpenAI Codex 与 GitHub Copilot:2025 年开发者选型对比
  • Qclaw AI 智能体深度测评:本地执行与跨端能力实战
  • AI 双重突破:全自动科研系统与 AIGC 电影技术解析
  • Windows 安装 OpenCode AI 编码助理实战指南
  • 文心一言 4.5 开源版本地化部署实测与性能分析

相关免费在线工具

  • RSA密钥对生成器

    生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online

  • Mermaid 预览与可视化编辑

    基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online

  • 随机西班牙地址生成器

    随机生成西班牙地址(支持马德里、加泰罗尼亚、安达卢西亚、瓦伦西亚筛选),支持数量快捷选择、显示全部与下载。 在线工具,随机西班牙地址生成器在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online