mcpField
B
builderbot-code-skill

builderbot-code-skill

github.com/leifermendez开发4184 次安装32 周前更新
代码仓库

关于 builderbot-code-skill

BuilderBot Code Skill 为使用 BuilderBot 编写聊天机器人流程的开发者提供实现规范与检查清单。内容涵盖流程控制、状态管理、模块导入和 WhatsApp 消息体验。

主要功能

  1. 01规范流程控制的 return 与 await
  2. 02提供状态和回调类型参考
  3. 03说明模块导入与循环依赖处理
  4. 04提供对话 UX 审查清单
  5. 05涵盖 WhatsApp 格式与消息规范

适用场景

  1. 01实现包含输入捕获的多步骤流程
  2. 02排查流程跳转和状态更新问题
  3. 03组织可组合的流程模块
  4. 04检查 WhatsApp 消息的可读性

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/leifermendez/skill-builderbot-code --skill builderbot-code-skill

安装后在 Claude Code 中输入/builderbot-code-skill确认已加载。

附带文件patterns.mdproviders.md

详细介绍

BuilderBot Code Skill

操作

  1. 识别技术栈 — 编写代码前,确认提供方包、数据库适配器和流程入口。
  2. 正确指定回调签名:
    • ctx: BotContext → { body, from, name, host, ...platform fields }
    • methods: BotMethods → { flowDynamic, gotoFlow, endFlow, fallBack, state, globalState, blacklist, provider, database, extensions }
  3. 遵循流程控制语义 — 以下三项必须使用 return:
    • return gotoFlow(flow) · return endFlow('msg') · return fallBack('msg')
    • 以下各项必须使用 await:await flowDynamic(...) · await state.update(...) · await globalState.update(...)
  4. 优先使用小型、可组合的流程 — 每个流程文件只负责一项职责。
  5. 将密钥保存在环境变量中 — 切勿将令牌、密钥或凭据硬编码。
  6. 编写任何流程后,都要完成下方的 UX 审查清单 — 模拟最终用户进行对话,并在认为流程完成前逐项核验。
  7. 结束前务必验证 lint — 运行 eslint . --no-ignore,并在交付代码前修复所有错误。

关键规则(常见错误)

错误错误写法正确写法
流程控制缺少 returngotoFlow(flow)return gotoFlow(flow)
状态更新或动态消息缺少 awaitstate.update({...})await state.update({...})
flowDynamic 使用原始字符串数组flowDynamic(['a','b','c']) — 发送 3 条独立消息flowDynamic([{ body: ['a','b','c'].join('\n'), delay: rnd() }])
在同一回调中使用 flowDynamic 和 endFlowawait flowDynamic(...); return endFlow()拆分成两个链式 addAction — 第一个发送 flowDynamic,第二个调用 return endFlow(...)
不必要的动态导入不存在循环依赖时使用 await import('./flow')顶层 import { flow } from './flow' — 仅在流程 A → B 且 B → A 时使用动态导入
ESM 循环导入在相互引用的流程之间使用顶层 import在回调中使用动态导入 const { xFlow } = await import('./x.flow')
idle 未搭配 capture{ idle: 5000 }{ capture: true, idle: 5000 }
在 ESM 中使用 require()require('./flow')await import('./flow') — 项目为 "type": "module",无法使用 require
使用按钮{ buttons: [...] }基于文本的编号菜单 + capture: true
检查 idleFallBackif (ctx?.idleFallBack) { ... }切勿使用 idleFallBack — 只需设置 { capture: true, idle: N },让流程自动过期

流程链

addKeyword(keywords, options?)        // ActionPropertiesKeyword: capture, idle, media, delay, regex, sensitive  ← NEVER use `buttons`
  .addAnswer(message, options?, cb?, childFlows?)
  .addAction(cb)
// cb: async (ctx: BotContext, methods: BotMethods) => void

状态快速参考

// Per-user
await state.update({ key: value })
state.get('key')            // dot notation supported: 'user.profile.name'
state.getMyState()
state.clear()

// Global (shared across all users)
await globalState.update({ key: value })
globalState.get('key')
globalState.getAllState()

UX 审查清单

构建任何流程后,请从最终用户的角度在脑海中走一遍对话,并核对以下每一项。交付代码前,修复所有未通过的项目。

#问题检查/修复内容
1每条提示是否都明确告诉用户要输入什么?每个带有 capture: true 的 addAnswer 都必须说明预期输入(例如“回复 1、2 或 3”。)。
2是否处理了所有无效输入?每个捕获输入的步骤都必须有用于处理错误输入的 fallBack('...') 分支。
3长时间等待输入时是否设置了闲置超时?添加 { capture: true, idle: 60000 } — 流程将在超时后自动过期。
4用户是否始终可以退出?提供取消关键词(例如“cancel”、“salir”),或在输入捕获中处理取消,并调用 return endFlow('...')。
5消息是否简短、适合手机阅读,且最多包含 3-4 个气泡?不要写大段文字,也不要刷屏。将多行内容用 \n 合并到一个 FlowDynamicMessage.body 中。flowDynamic([...]) 数组中的每个字符串都会成为一条单独的 WhatsApp 消息 — 切勿将长列表拆开逐条发送。每个气泡都添加随机延迟。
6是否在合适的地方使用了用户姓名?问候和确认消息应在 ctx.name 可用时提及用户姓名。
7菜单是否使用编号文本列表(而非 buttons)?使用 ['Option 1', '1. Foo', '2. Bar'] + capture: true + fallBack。
8每个多步骤流程是否都在执行操作前进行确认?对于不可逆操作(下单、付款、删除),先显示摘要并询问“确认?yes / no”。
9流程结束时是否有明确的结束消息?最后一步必须告知用户发生了什么以及接下来该做什么(或道别)。
10错误消息是否能指导用户采取行动?切勿只说“错误”。应说明出了什么问题以及如何修复:return fallBack('That doesn\'t look like a valid email. Try again:')

WhatsApp 文本格式

WhatsApp 使用自己的 Markdown — 消息字符串中始终应用该格式。

样式语法示例
粗体*text**Pepperoni*
斜体_text__Tomate y mozzarella_
删除线~text~~$15~
等宽字体```text``````CODE123```
项目符号列表• 或 -• Opción 1
规则
  • 使用 *bold* 标记产品名称、总计、章节标题,以及用户必须执行的操作。
  • 使用 _italic_ 标记描述、提示和次要信息。
  • 列表项使用 •(而非 -)— 在手机上显示更清晰。
  • 在同一个气泡的不同章节之间使用 ━━━━━━━(或 ---)作为视觉分隔符。
  • 切勿使用 HTML 标签(<b>、<br> 等)— WhatsApp 会忽略它们。
  • 切勿使用标准 Markdown(**bold**、## heading)— 不受支持。
示例 — 格式良好的气泡
const body = [
    '*🍕 Tu pedido*',
    '━━━━━━━━━━━━━━',
    `• Pizza: *Pepperoni*`,
    `• Tamaño: *Mediana (30 cm)*`,
    `• Cantidad: *2*`,
    '',
    `💰 Total: *$26 USD*`,
    '',
    '_Responde *sí* para confirmar o *no* para cancelar._',
].join('\n')

WhatsApp 消息规范

这些规则适用于每个流程。违反规则会让机器人显得像在发送垃圾消息。

关键区别 — flowDynamic 与使用数组的 addAnswer:

调用数组行为
flowDynamic(['a','b','c'])⚠️ 每个字符串都会成为一条单独的 WhatsApp 消息
addAnswer(['a','b','c'])✅ 合并成一条消息,各行之间用换行符分隔

调用 flowDynamic 时,始终在 body 中使用带有 .join('\n') 的 FlowDynamicMessage[]。切勿传入原始字符串数组。

规则错误示例正确示例
每轮最多 3-4 个气泡flowDynamic(['line1','line2','line3',...]) → 每个字符串都会成为一个气泡flowDynamic([{ body: ['line1','line2','line3'].join('\n'), delay: rnd() }])
始终使用随机延迟气泡之间没有延迟每个气泡都设置 delay: Math.floor(Math.random() * 800) + 500
切勿拆开发送列表项flowDynamic([...items.map(...)])flowDynamic([{ body: items.map(...).join('\n'), delay: rnd() }])
随机延迟辅助函数(每个文件定义一次)
const rnd = () => Math.floor(Math.random() * 800) + 500
正确模式 — 最多 3 个气泡
await flowDynamic([
    {
        body: [
            '*Título*',
            '',
            items.map(i => `• ${i.name} — $${i.price}`).join('\n'),
        ].join('\n'),
        delay: rnd(),
    },
    {
        body: '*Sección 2*\nLínea A\nLínea B',
        delay: rnd(),
    },
])
// addAnswer o addAction siguiente = bubble 3

在线状态更新(仅限 Baileys / Sherpa)

在发送消息前模拟“正在输入……”或“正在录音……”。让机器人表现得更像真人。仅适用于 BaileysProvider 和 SherpaProvider。

type WAPresence = 'unavailable' | 'available' | 'composing' | 'recording' | 'paused'
// composing = typing bubble   recording = audio bubble
模式 — 每条消息前显示输入状态
const waitT = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))

.addAction(async (ctx, { provider, flowDynamic }) => {
    await provider.vendor.sendPresenceUpdate('composing', ctx.key.remoteJid)
    await waitT(1500)
    await flowDynamic([{ body: 'Mensaje que parece escrito por humano', delay: rnd() }])
    await provider.vendor.sendPresenceUpdate('paused', ctx.key.remoteJid)
})
规则
  • 发送后始终调用 sendPresenceUpdate('paused', ...),以清除状态指示。
  • 与 rnd() 延迟结合使用:更新状态 → 等待 → flowDynamic → paused。
  • 文本回复使用 composing,音频场景使用 recording。
  • 切勿在非 Baileys/Sherpa 服务提供方上使用——运行时会抛出错误。

调试清单

  • 流程未切换 → return gotoFlow(...)
  • 会话未结束 → return endFlow(...)
  • 回退未重复 → return fallBack(...)
  • 状态未保存 → await state.update(...)
  • 空闲事件未触发 → 在 idle 旁添加 capture: true(切勿检查 idleFallBack)
  • EVENTS 流程未触发 → 确认服务提供方是否将事件负载映射到 EVENTS.*
  • 循环导入导致崩溃 → 在回调中使用动态 await import()(切勿使用 require())
  • 语言服务器在动态导入时显示“Cannot find module” → 检查是否确实存在循环依赖;如果没有,则替换为顶层静态 import
  • 修改后出现 ESLint 错误 → 运行 eslint . --no-ignore 并在完成前修复错误
  • 出现 builderbot/func-prefix-endflow-flowdynamic 错误 → flowDynamic 和 endFlow 位于同一个回调中;拆分为两个串联的 addAction:
    .addAction(async (_, { flowDynamic }) => { await flowDynamic(lines) })
    .addAction(async (_, { endFlow }) => { return endFlow('bye') })
    

模块系统

本项目使用 ESM(package.json 中的 "type": "module",tsconfig 中的 "module": "ES2022")。

  • 绝不可使用 require()——ESM 中不可用。
  • 默认使用顶层静态导入。 不存在循环依赖时,动态导入会导致 TypeScript 语言服务器报错。
  • **使用 await import() 前,**先绘制依赖关系图。只有当流程 A 调用流程 B,且流程 B 调用流程 A(确实存在循环)时,才有理由使用动态导入。
✅ Static (no cycle):  welcome → menu → order → payment
   import { orderFlow } from './order.flow'   ← top of file

✅ Dynamic (real cycle): welcome ↔ order
   const { orderFlow } = await import('./order.flow')  ← inside callback

当流程相互引用(形成循环)时,在回调中使用动态 import():

.addAction(async (ctx, { gotoFlow }) => {
    const { targetFlow } = await import('./target.flow')
    return gotoFlow(targetFlow)
})

模块化结构(推荐)

建议将流程整理到 src/flows/ 目录中,并使用导出已组装流程的 barrel index.ts:

src/
├── app.ts
├── flows/
│   ├── index.ts            # createFlow([...all flows])
│   ├── welcome.flow.ts
│   └── order.flow.ts
└── services/
// src/flows/index.ts
import { createFlow } from '@builderbot/bot'
import { welcomeFlow } from './welcome.flow'
import { orderFlow } from './order.flow'

export const flow = createFlow([welcomeFlow, orderFlow])
// src/app.ts
import { createBot, createProvider } from '@builderbot/bot'
import { MemoryDB as Database } from '@builderbot/bot'
import { BaileysProvider as Provider } from '@builderbot/provider-baileys'
import { flow } from './flows'

const main = async () => {
    const provider = createProvider(Provider)
    const database = new Database()
    await createBot({ flow, provider, database })
    provider.initHttpServer(+(process.env.PORT ?? 3008))
}
main()

参考资料

  • 代码模式(脚手架、capture、gotoFlow、媒体、idle、flowDynamic、fallBack、REST、EVENTS、UX 模式):patterns.md
  • 服务提供方配置、数据库配置、TypeScript 类型:providers.md

评论

支持 Markdown