OpenClaw 插件开发避坑指南

Hi,大家好 👋 今天来聊聊 OpenClaw 插件开发中那些让人头秃的坑 😅

作为过来人,我把踩过的坑整理成这份避坑指南,建议先收藏再看,防止以后用到时找不着 📌


1. outbound.sendText vs deliver 回调:别再傻傻分不清 🔀

😵 问题现象

这两个货太容易搞混了!用 sendText 回复用户消息、用 deliver 推送系统通知......结果消息跑错路,业务逻辑全乱套,调试调到头秃。

🔍 问题根源

它俩是两条完全不同的消息发送通道,井水不犯河水:

特性 deliver 回调 outbound.sendText 方法
触发时机 用户主动发消息 → 我来响应 系统主动推消息 → 不用等用户操作
典型场景 回复用户提问、反馈操作结果 系统通知、告警提示、定时推送
messageId 直接用用户原消息 ID,别自己造 手动生成,建议 Date.now().toString(36)
能力 支持工具调用、流式输出、多轮对话 只能发一次性文本,不支持流式

✅ 解决方案

一句话记住:用户主动找你的 → deliver;系统自己要推的 → sendText。

typescript 复制代码
// 🚀 sendText:主动推送(系统通知、告警等)
outbound: {
    sendText: async ({ to, text, accountId }) => {
         // 手动生成唯一ID
        const messageId = Date.now().toString(36);

        // 写到文件日志,方便排查
        const fs = require('fs');
        const logPath = path.join(__dirname, 'outbound.log');
        fs.appendFileSync(logPath,
            `[${new Date().toLocaleString()}] messageId: ${messageId} | 发给: ${to}\n`);

        await wsClient.send({ type: 'push', payload: { text, messageId, to } });
        return { channel: 'yeizi', ok: true, messageId };
    }
}
typescript 复制代码
// 🚀 deliver:被动响应(回复用户消息)
if (message.type === 'message' && message.text) {
    // 复用用户的ID,别自己造!
    const originalMessageId = message.messageId; 

    await runtime.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
        ctx: finalized,
        cfg,
        dispatcherOptions: {
            deliver: async (payload, info) => {
                await wsClient.send({
                    type: 'response',
                    payload: { content: payload.content, messageId: originalMessageId }
                });
            }
        }
    });
}

2. sendText 日志「消失」怎么办 📝

😵 问题现象

在 sendText 里写了 console.log,控制台毛都没有......函数到底有没有跑?参数对不对?完全不知道,排查问题全靠猜。

🔍 问题根源

sendText 是出站子系统异步调用的,跟主进程控制台不连通,console.log 相当于「对牛弹琴」。

✅ 解决方案

放弃 console.log,写文件日志才是正道:

typescript 复制代码
const fs = require('fs');
const logPath = path.join(__dirname, 'sendText-debug.log');

const log = (msg) => {
    fs.appendFileSync(logPath,
        `[${new Date().toISOString()}] ${msg}\n`, { encoding: 'utf8' });
};

log(`发送成功 | messageId: ${messageId} | 内容: ${text}`);

想看日志?type sendText-debug.log 或者直接打开文件,一目了然 👀


3. deliver 回调里抓取 AI 干活的全过程 🤖

😵 问题现象

想知道 AI 到底调用了哪个工具、返回了什么结果,可 deliver 回调触发好几次(工具调用、流式文本、最终回复全混在一起),根本分不清谁是谁。

🔍 问题根源

  1. 没开详细日志,OpenClaw 默认不输出工具调用信息
  2. deliver 回调返回的消息分三种类型,得靠 info.kind 来区分

✅ 解决方案

第一步:开启完整日志(在 openclaw.json 里)

json 复制代码
{
    "agents": {
        // 开启后能看到 AI 调工具的全过程
        "verboseDefault": "full" 
    }
}

第二步:靠 info.kind 区分消息类型

info.kind 意思是 能拿到的信息
tool AI 调用工具了 payload.content(工具返回结果)或 payload.error(报错信息)
block AI 正在「打字」中 payload.content(这波输出的文本片段)
final AI 终于说完了 payload.content(完整回复)

代码示例:

typescript 复制代码
deliver: async (payload, info) => {
    if (!info) return;

    switch (info.kind) {
        case 'tool':
            console.log('🛠️ 工具调用/返回:', payload.content || payload.error);
            break;
        case 'block':
            console.log('💬 AI 正在打字:', payload.content);
            break;
        case 'final':
            console.log('✅ AI 最终回复:', payload.content);
            break;
    }
}

💡 小技巧 :日志会同时写入 /var/log/openclaw/runtime.log,两个地方都可以看。


4. 多账户配置:鉴权鉴到怀疑人生怎么破 🔐

😵 问题现象

配置了 N 个账户,结果每个账户都独立鉴权、WebSocket 连接也各自建一个......服务器压力山大,连接还可能不稳定,鉴权失败的错误弹到麻木。

🔍 问题根源

没搞复用!每个账户都「自立门户」,鉴权、连接全重做了一遍。

✅ 解决方案

方案一:单账户走天下(推荐)

如果业务不需要严格区分用户身份,一个账户就能服务所有人,省心又省力。

方案二:共享连接池(多账户必用)

typescript 复制代码
// 全局就一个 WebSocket 连接,大家共用
let sharedWsConnection = null;

const getSharedConnection = async () => {
    if (!sharedWsConnection) {
        // 只有第一次需要鉴权创建连接
        sharedWsConnection = await createAndAuthenticateWs();
    }
    return sharedWsConnection;
};

gateway.startAccount = async (accountId) => {
    // 复用!不再重复鉴权
    const ws = await getSharedConnection();
    // 业务逻辑继续...
};

5. 插件依赖:别再重复安装内置包啦 📦

😵 问题现象

插件启动失败、版本冲突、包体积膨胀......一查,哦豁,dependencies 里写了 openclaw 和 typescript,好家伙,这两个 OpenClaw 运行环境里已经有了!

🔍 问题根源

OpenClaw 内置的包(openclaw、typescript 等),别再 npm install 了,否则版本冲突没商量。

✅ 解决方案

❌ 错误示范(别学):

json 复制代码
{
    "dependencies": {
        // 千万别装!
        "openclaw": ">=2026.3.12",   
         // 千万别装!
        "typescript": "^5.0.0"       
    }
}

✅ 正确姿势:

json 复制代码
{
    "name": "@myorg/openclaw-yeizi",
    "version": "1.0.0",
    "type": "module",
    "peerDependencies": {
        // 声明版本即可,不实际安装
        "openclaw": ">=2026.3.13"    
    },
    "dependencies": {
        // 只装业务真正需要的
        "ws": "^8.16.0"              
    },
    "devDependencies": {
        // 开发用类型定义,可选
        "@types/ws": "^8.5.10"       
    }
}

验证方法:

  • node_modules 里没有 openclaw、typescript 文件夹 ✓
  • npm run start 能正常启动插件 ✓

🎉 祝大家插件开发顺利,少掉头发,多掉 bug(然后快速修好)💪

如果觉得有帮助,点个赞 👍 支持一下,也欢迎在评论区分享你的踩坑经历!

相关推荐
miss14 分钟前
AI 改完不敢上线?OpenSpec 规约驱动开发实战
前端·代码规范·前端工程化
AiMan1 小时前
腾讯ima还是不可以导出左侧所有对话,只能单条导出,方法不对吗
前端
去伪存真1 小时前
并发量一高就装死?Agent 全链路“防猝死”手册
前端·agent
Moment1 小时前
AI 编程时代,聊聊我的 Mac mini 双屏桌面
前端·后端
AiMan1 小时前
先确认能不能一次把Microsofat Copilot所有内容打包一个文件导出
前端
总要学点什么1 小时前
第97篇:三维数据加载-城市建筑白模自定义着色器——让一条光带沿高度扫上去,快慢却由帧率说了算
前端·gis·webgl·cesium·着色器·空间分析·白模
上海懒图科技2 小时前
工厂厂区电子地图定位导航:厂区人车精准定位,安全合规双保障
前端·arcgis·vue
西安小哥2 小时前
端侧跑大模型,真正的门槛不在参数量:一次 596 秒到 10 秒的压缩路径复盘
前端·人工智能
陈随易2 小时前
Cloudflare 出钱,Vite 团队挑人,44 个维护者分 30 万美元
前端·后端·程序员
恋猫de小郭3 小时前
Flutter THLC 的 WebView 滚动终于从 70 FPS 提升到 120 FPS
android·前端·flutter