npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

feishu-card-callback-bridge

v0.1.17

Published

Bridges Feishu card button callbacks back into the chat session as visible bot messages

Readme

Feishu Card Callback Bridge

一个独立的 OpenClaw 插件,把飞书卡片按钮的点击回调、表单提交回调以机器人消息的形式注入回事件来源的飞书会话,让该会话里的 AI 能够"收到"这次交互。

与官方 @larksuite/openclaw-lark 飞书插件配合工作;不修改官方插件源码。


它解决什么问题

OpenClaw 官方飞书插件已经把 card.action.trigger 事件路由到了 SDK 的 interactive dispatch 管道,但没有内置的兜底 handler——如果你发的卡片按钮 value.action 没有任何业务插件认领,事件会被静默丢弃。

典型场景:

  1. 在 main session 里让 OpenClaw 跑 openclaw message send 把一张带按钮(或表单)的卡片发到群 B
  2. 群 B 用户点按钮 / 提交表单
  3. 想让群 B 当前 session 的 AI 收到这次交互并继续后续动作

只要把按钮(或表单提交按钮)的 value.action 写成 custom-button:<payload>,本插件就会:

  • 拦截到 card.action.trigger 事件
  • 调用 SDK 标准的 respond.followUp 让机器人在群 B 发出一条文本,例如:

    [card-callback] action=custom-button:close payload=close sender=ou_xxx formValue=

    表单场景:[card-callback] action=custom-button:submit payload=submit sender=ou_xxx formValue={"color":"red","note":"hi"}

  • 这条文本会被飞书插件自身的 inbound 流当作群里的新消息消费,群 B 的 session 里 AI 自然就"收到"了

工作原理

群 B 用户点按钮 / 提交表单
   │
   ▼
飞书开放平台推送 card.action.trigger 事件
   │
   ▼
官方 openclaw-lark 插件 (interactive-dispatch.ts)
   │  按 value.action 的 "<namespace>:<payload>" 找已注册的 handler
   ▼
本插件注册的 handler (namespace = "custom-button")
   │  从 ctx.rawEvent.action.form_value 提取表单数据(如有)
   │  调用 ctx.respond.followUp({ text: "[card-callback] ..." })
   ▼
机器人在群 B 发出一条普通消息
   │
   ▼
群 B 的 session 里 AI 收到消息 → 继续推理

详细看:


安装

前置:本机已安装并能运行 OpenClaw(Node.js >= 22)。

OpenClaw 的 plugins install 命令要求插件提供已编译的运行时产物(不接受 .ts 入口),所以本插件的 package.jsonopenclaw.extensions 指向 ./dist/index.js,仓库里已经包含了一份手写好的 dist/index.js,可直接安装。

方式一:本地路径安装(推荐用于开发/自用)

openclaw plugins install /Users/chenzeping/projects/github.com/openclaw-lark/custom-plugins/feishu-card-callback-bridge

如果你修改了 index.ts,需要重新生成 dist/index.js

cd custom-plugins/feishu-card-callback-bridge
npm install --no-package-lock --silent typescript@5
npx tsc -p tsconfig.json

或者直接手动同步修改到 dist/index.js(仓库当前提交的就是手写版本,逻辑等价于 index.ts,只是去掉了类型注解)。

方式二:发布到 npm 或 ClawHub

发布前注意:

  • package.jsonprivate: true 去掉
  • name 改成你的命名空间,例如 @your-org/feishu-card-callback-bridge
  • 注意:name 改了之后 OpenClaw 仍以 manifest 里的 idfeishu-card-callback-bridge)作为 config key,安装日志里会出现一条提示,这是正常的
# npm 发布
cd custom-plugins/feishu-card-callback-bridge
npm publish --access public

# 安装
openclaw plugins install @your-org/feishu-card-callback-bridge

或走 ClawHub:

clawhub package publish your-org/feishu-card-callback-bridge
openclaw plugins install clawhub:@your-org/feishu-card-callback-bridge

验证安装

openclaw plugins list

输出里应能看到 feishu-card-callback-bridge

启动 OpenClaw 后日志里也会出现该插件的注册痕迹(找类似 registered interactive handler namespace=custom-button channel=feishu 的记录)。


配置

可选;不配置时使用下面这套默认值:

| 字段 | 类型 | 默认值 | 说明 | | ----------------- | ------------------ | --------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | | namespaces | string[] | ["custom-button"] | 要拦截的 value.action 命名空间。卡片按钮里 value.action 必须以 <namespace>: 开头。 | | echoMode | "message" \| "card" \| "silent" | "message" | 处理方式:message = 在事件来源 chat 发文本(推荐);card = 替换原卡片;silent = 仅 toast。 | | messageTemplate | string | {botMention} [card-callback] action={action} payload={payload} sender={senderId} formValue={formValue} | echoMode = message 时的文本模板。 | | toastContent | string | 已收到 | 点击者看到的 toast 提示。 | | botOpenId | string | "" | 可选。机器人自身的 open_id(形如 ou_xxx)。默认留空,插件会通过 OpenClaw 全局配置中的 channels.feishu.accounts.<accountId>.appId/appSecret 自动调用飞书 /open-apis/bot/v3/info API 拿到并缓存,无需手填。仅当你想覆盖自动探测的结果或宿主访问不到飞书 API 时才需要显式配置。配置后,模板里 {botMention} 占位符会渲染为 <at user_id="ou_xxx">Bot</at>,让 followUp 出去的消息被群里识别为"@机器人"。 |

模板支持以下占位符:

  • {action} —— 完整 value.action,如 custom-button:close
  • {namespace} —— custom-button
  • {payload} —— 命名空间冒号之后的部分,如 close
  • {senderId} —— 点击者 open_id
  • {conversationId} —— 事件来源 chat_id
  • {messageId} —— 卡片消息的 message_id
  • {formValue} —— 表单提交时的表单值(JSON 字符串),普通按钮点击时为空字符串
  • {botMention} —— 默认走自动探测:插件首次收到事件时会调用一次飞书 /open-apis/bot/v3/info 拿到当前 account 的 bot open_id 并缓存,渲染为 <at user_id="ou_xxx">Bot</at>;显式配置 botOpenId 时优先使用该值;探测失败/无凭据时降级为空字符串

配置示例

把以下片段加到 OpenClaw 全局配置里(具体路径以你的环境为准):

{
  "plugins": {
    "feishu-card-callback-bridge": {
      "namespaces": ["custom-button", "ticket", "approval"],
      "echoMode": "message",
      "messageTemplate": "{botMention} 用户 {senderId} 在群 {conversationId} 点击了按钮: payload={payload} formValue={formValue}",
      "toastContent": "已记录"
    }
  }
}

关于 botOpenId:默认无需配置。插件首次收到回调时会用宿主已有的 appId/appSecret 自动调一次飞书 /open-apis/bot/v3/info 拿到 bot open_id 并按 accountId 缓存。日志会出现:

feishu-card-callback-bridge: auto-detected bot open_id=ou_xxx for account=<accountId>

仅在以下场景需要显式 "botOpenId": "ou_xxx"

  • 宿主网络受限调不到飞书 OpenAPI
  • 你想 mention 的对象不是当前消息所属机器人本身(少见)

使用一:发一张带回调按钮的卡片

在 main session 里让 OpenClaw 跑下面这条命令(机器人身份发到目标群):

openclaw message send --channel feishu \
  --target "chat:oc_360fb72810559756e6567fd11c61dc59" \
  --message '{
    "schema": "2.0",
    "config": { "wide_screen_mode": true, "update_multi": true },
    "body": {
      "elements": [
        { "tag": "markdown", "content": "请处理工单 #123" },
        {
          "tag": "column_set",
          "columns": [
            {
              "tag": "column",
              "elements": [{
                "tag": "button",
                "text": { "tag": "plain_text", "content": "关闭" },
                "type": "default",
                "behaviors": [{
                  "type": "callback",
                  "value": { "action": "custom-button:close", "ticket": "123" }
                }]
              }]
            },
            {
              "tag": "column",
              "elements": [{
                "tag": "button",
                "text": { "tag": "plain_text", "content": "派单" },
                "type": "primary",
                "behaviors": [{
                  "type": "callback",
                  "value": { "action": "custom-button:dispatch", "ticket": "123" }
                }]
              }]
            }
          ]
        }
      ]
    }
  }'

要点:

  • 顶层必须是 {"schema":"2.0", ...} 形式,不要外层再裹 {"card": ...}
  • behaviors[].type 必须是 callback(写成 open_url 不会触发回调)
  • value.action 必须是 <namespace>:<payload> 形式,且 namespace 是本插件注册的(默认 custom-button
  • value 里其他字段(如 ticket)会随事件透传,可在模板里用 {payload} 等读取或自行扩展 handler

使用二:发一张带表单提交的卡片

CardKit v2 的表单容器(tag: "form")支持把多个输入控件聚合后一次性提交。本插件会自动从事件里提取 action.form_value(也兼容顶层 form_value)并通过 {formValue} 占位符暴露出来。

openclaw message send --channel feishu \
  --target "chat:oc_360fb72810559756e6567fd11c61dc59" \
  --message '{
    "schema": "2.0",
    "config": { "wide_screen_mode": true, "update_multi": true },
    "body": {
      "elements": [
        { "tag": "markdown", "content": "请选择颜色并填写备注" },
        {
          "tag": "form",
          "name": "color_form",
          "elements": [
            {
              "tag": "select_static",
              "name": "color",
              "placeholder": { "tag": "plain_text", "content": "选择颜色" },
              "options": [
                { "text": { "tag": "plain_text", "content": "红色" }, "value": "red" },
                { "text": { "tag": "plain_text", "content": "蓝色" }, "value": "blue" }
              ]
            },
            {
              "tag": "input",
              "name": "note",
              "placeholder": { "tag": "plain_text", "content": "备注" }
            },
            {
              "tag": "button",
              "name": "submit_color_btn",
              "text": { "tag": "plain_text", "content": "提交" },
              "type": "primary",
              "form_action_type": "submit",
              "behaviors": [{
                "type": "callback",
                "value": { "action": "custom-button:submit-color" }
              }]
            }
          ]
        }
      ]
    }
  }'

要点:

  • 把要一次性提交的输入控件包到 tag: "form" 容器里,并给每个输入控件、提交按钮都设置唯一的 nameform 容器内所有交互组件都必须有 name,缺一个飞书客户端就会拒绝提交并报 code:200530
  • 提交按钮要标 form_action_type: "submit",且 behaviors[].type = "callback"value.action = "custom-button:<payload>"
  • 用户提交后,飞书会把容器里所有输入控件的当前值组装成 action.form_value(形如 {"color":"red","note":"hi"})随事件推送
  • 本插件把这个对象 JSON.stringify 后作为 {formValue} 注入到 messageTemplate;下游 AI 在群 B session 里会看到这条文本,可直接解析使用

发送时如何构造 JSON 参考同仓库下:send-lark-group-msg-as-bot/SKILL.md


故障排查

| 现象 | 排查方向 | | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | | 卡片发出去了但内容是 JSON 文本,不是卡片 | --message 顶层 JSON 必须含 "schema":"2.0"(参考 send-lark-group-msg-as-bot SKILL) | | 用户点按钮后群里没有"机器人 followUp 消息" | 1) 检查 value.action 是否以已注册命名空间开头 2) 启动日志里确认本插件已加载 3) 飞书后台查看「卡片回调地址」是否启用并指向 OpenClaw 的 WS 连接 | | 表单提交后 {formValue} 是空 | 1) 提交按钮要在 tag: "form" 容器内并标 form_action_type: "submit" 2) 每个输入控件都要有唯一的 name | | 表单提交时弹 出错了,请稍后重试 code:200530 | 绝大多数情况是卡片 JSON 不合法导致客户端本地校验失败:1) tag: "form" 容器内所有交互组件(包括 submit 按钮、input、select 等)都必须有唯一的 name —— 漏一个就会触发;2) submit 按钮必须有 behaviors[].value.action;3) 整个 form 不能嵌套在另一个 form 或 table 里,只能挂在卡片 root;4) 客户端确实没问题再排查服务端 3 秒响应——本插件已用 setImmediate 把 followUp/editMessage 甩到下一 tick 异步执行。 | | 点了按钮只看到 toast,没文本 | echoMode 被改成了 silentcard;改回 message | | AI 收到了消息但不会接续动作 | 群里 OpenClaw 默认要求被 @ 才会响应。推荐做法:在配置里填上机器人的 botOpenId,模板里保留 {botMention} 占位符(默认模板已包含),followUp 出去的消息会带 <at user_id="ou_xxx">Bot</at>,群里的 AI 直接被触发;或者修改群配置允许机器人消息触发 AI。 | | 启动报 Cannot find module 'openclaw/plugin-sdk/...' | 该子路径由宿主 openclaw 提供;确认本机 OpenClaw 版本满足 peerDependencies 要求(>= 2026.5.4) |


文件结构

feishu-card-callback-bridge/
├── README.md                   # 本文件
├── package.json                # 声明 openclaw.extensions = ["./dist/index.js"]
├── openclaw.plugin.json        # 插件清单 + 配置 schema
├── tsconfig.json               # 让 IDE 把这个子目录视为独立 TS 项目
├── index.ts                    # 核心实现(TS 源码)
└── dist/
    └── index.js                # 已编译的运行时入口(手写 ESM JS,逻辑等价于 index.ts)

与官方飞书插件的关系

  • 本插件不替代不修改官方 @larksuite/openclaw-lark
  • 它只是注册了一个 SDK 标准的 registerInteractiveHandler,认领 custom-button 命名空间
  • 升级官方飞书插件不会影响本插件
  • 想接其他业务命名空间(如 ticketapproval),把它们加到 namespaces 配置数组即可,handler 会用同一段逻辑共享处理

License

MIT