OpenClaw 插件开发避坑指南

23 阅读4分钟

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

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


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

😵 问题现象

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

🔍 问题根源

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

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

✅ 解决方案

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

// 🚀 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 };
    }
}
// 🚀 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,写文件日志才是正道:

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 里)

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

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

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

代码示例:

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 连接也各自建一个……服务器压力山大,连接还可能不稳定,鉴权失败的错误弹到麻木。

🔍 问题根源

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

✅ 解决方案

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

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

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

// 全局就一个 WebSocket 连接,大家共用
let sharedWsConnection = null;

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

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

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

😵 问题现象

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

🔍 问题根源

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

✅ 解决方案

❌ 错误示范(别学):

{
    "dependencies": {
        // 千万别装!
        "openclaw": ">=2026.3.12",   
         // 千万别装!
        "typescript": "^5.0.0"       
    }
}

✅ 正确姿势:

{
    "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(然后快速修好)💪

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