English | 简体中文
把 OpenCode 接进飞书:一个飞书话题 = 一个 OpenCode 会话,权限审批直接在飞书卡片上点按钮。
- 只支持 OpenCode V2(
@opencode/plugin,Plugin.define),不依赖任何 V1 包。 - 纯长连接(WebSocket)收发事件与卡片回调:不监听端口、不需要公网地址。
| 说明 | |
|---|---|
| 🔐 最小权限 | 只要 2 个 scope,不申请任何群权限,机器人物理上收不到群消息 |
| 💬 话题 = 会话 | 一个飞书话题对应一个 OpenCode 会话;主聊天流只做管理,互不串台 |
| 🚀 一键建会话 | /new 一张表单(目录 + 模型 + 权限档位)一次填完,提交即建会话并自动开话题 |
| ✅ 卡片审批 | 权限请求变飞书卡片:允许一次 / 始终允许 / 本会话内允许 / 拒绝,自签 token 防伪防重放 |
| 📊 实时可见 | 「思考中」回执 → 工具调用实时上卡 → 文本流式更新,页脚显示当前模型 |
| ⏹ 可控可停 | 每张回复卡带「强制停止」;看门狗自动中断卡死会话;忙时新消息默认插队(可配置为排队),/steer /now 随时可用 |
| 📎 图片 / 文件 | 飞书里的图片 / 文件自动下载并挂进会话,支持视觉 / 文件的模型直接看图、读文件 |
| 🧭 AI 会话管理(主聊天流) | 主聊天流发任务文本或建会话/管理类命令(/new /sessions /use /resume 等),都先交给 AI 承接意图、找好工作目录;目录拿不准时直接在对话里追问,确认后一键建会话并开始处理 |
| 🚫 无端口 | 全程长连接,服务器无需开放任何入站端口 |
-
打开 飞书开放平台 → 创建企业自建应用。
-
添加应用能力 → 机器人。
-
权限管理 → API 权限:先开这两个必开 scope:
im:message.p2p_msg:readonly—— 读取用户发给机器人的单聊消息im:message:send_as_bot—— 以应用身份发消息(也用于更新卡片)
接收图片 / 文件需要再加开一个(不需要该功能可跳过):
im:message:readonly—— 获取消息中的资源文件(图片 / 文件下载的必要条件)
⚠️ 未开通im:message:readonly时:图片/文件不会被下载,消息照常送达 AI,但只带占位文本("…下载失败:…")。开通后要重新创建版本并发布才生效。 -
事件与回调 → 事件配置:订阅方式选**「使用长连接接收事件」**(不要选 Webhook),添加事件
im.message.receive_v1。 -
事件与回调 → 回调配置:订阅方式同样选长连接,添加回调
card.action.trigger(零权限要求)。 -
版本管理与发布:可用范围 = 仅本人,创建版本并发布。
⚠️ 不发布就是开发态,长连接连不上,机器人不会有任何反应。 -
记下 App ID(
cli_…)与 App Secret。
为什么不申请群权限? 本插件是"一个人的遥控台"。不申请群权限,机器人物理上收不到群消息,单人边界由平台 scope 层保证,而不是只靠代码判断。
# 方式 A:CLI(推荐)
opencode plugin add opencode-feishu-plugin插件入口是自包含的 dist/index.js(已打包飞书 SDK),运行时无需手动 npm install。
新建 ~/.config/opencode/plugins/feishu.json(configDir = OPENCODE_CONFIG_DIR 或 ~/.config/opencode):
install -m 600 /dev/null ~/.config/opencode/plugins/feishu.json
cat > ~/.config/opencode/plugins/feishu.json <<'JSON'
{
"appId": "cli_xxxxxxxx",
"appSecret": "xxxxxxxx",
"logFile": true
}
JSON- 凭证优先级:
plugins[].options>feishu.json> 环境变量。也可在值里用{env:NAME}/${NAME}占位符从环境变量取值。 logFile: true建议开启:服务模式下 stderr 会被丢弃,开着才有日志可查。
opencode reload
tail -f ~/.config/opencode/plugins/feishu.log # 应看到「飞书长连接已启动(WSClient)」然后在飞书里给机器人发一条消息。第一次发消息的人会被自动绑定为 owner,机器人回复卡片即安装成功;之后其他人会被静默忽略。
升级插件或改用全局插件目录加载后,需要
opencode service restart才会重新 import。
主聊天流只做管理,普通文本不会进入任何会话。建会话 / 管理类命令(下表)与普通文本一样,先交给 AI 承接意图,再向下推进(AI 拿不准时会直接在对话里追问你):
| 命令 | 作用 |
|---|---|
/new [标题] |
发建会话表单,提交即建会话并自动开话题(与 /form 等价) |
/sessions(/ls) |
全部会话列表(含本机所有 opencode 会话),可翻页、进话题、新建 |
/resume [序号] |
对最近(或列表第 N 个)会话发恢复卡,回复该卡即续聊 |
/current、/stop |
查看当前会话 / 中断当前任务 |
/steer <文本>、/now |
立即插队发消息 / 把排队消息改为立即执行 |
/dir、/model、/perm |
为建会话表单预填工作目录 / 模型 / 权限档位 |
/cancel、/help |
放弃未提交的表单 / 命令列表 |
主聊天流发普通文本或建会话 / 管理类命令(/new /form /dir /model /perm /sessions /use /resume),AI 会判断意图并直接处理:
-
建会话(目录优先):AI 先把工作目录定下来,再就地给出预填表单——确认或微调后点「✅ 创建会话」即可,自动开话题开工:
你:帮我修一下 zlib 的下载 bug,用高风险审批 → 📝 建会话表单(目录 `/Users/code/zlib` ✓ 匹配历史目录;权限「高风险审批」) [✅ 创建会话] 你:股票研究 → 📝 建会话表单(目录 `/Users/code/stock-research` ➕ AI 新建,不存在时会在创建时自动创建) [✅ 创建会话]目录决策顺序:① 你明确给的路径 → ② 语义匹配现成目录(AI 会先看允许根目录的一级子目录,再看最近使用 / 历史会话目录)→ ③ 都不匹配则按主题在允许根目录下新建(
<允许根目录>/<英文短横线主题>)→ ④ 兜底允许根目录。表单永远带目录,不会出现空目录。 -
目录拿不准 → 对话追问:当 AI 无法确定用哪个目录(表述含糊 / 既可能用现成也可能要新建)时,它不擅自替你选,而是发一条纯文本追问(列出候选或询问是否允许新建);你直接回复即可,AI 接着把流程走完(下一条消息即使是
/路径也当作回答):你:帮我搞个股票项目 → 你想用哪个目录?回复目录名,或回复「新建 stock-research」 你:新建 stock-research → 📝 建会话表单(目录 `/Users/code/stock-research` ➕ AI 新建) [✅ 创建会话] -
列会话 / 进入会话:说「我有哪些会话」→ 会话列表卡;说「继续上次那个」/
/resume→ 直接进话题; -
闲聊 / 其它:照旧回管理台提示卡。
防误伤:AI 新造的路径必须落在允许范围(allowedRoots)内、模型必须命中可选列表;你明确指定但越界的路径不会静默替换——表单不预填并给出警示,由你修改。最终一定经过表单确认。quickNew: false 可整体关闭(回到纯命令矩阵)。
一个话题 = 一个会话,发普通文本就是给 AI 下指令。
| 命令 | 作用 |
|---|---|
/model |
切换本会话模型(只影响后续回复) |
/perm |
修改本会话权限档位 |
/cd <路径> |
迁移本会话工作目录 |
/steer <文本>、/now |
插队 / 立即执行排队消息 |
/current、/stop、/help |
同主聊天流,作用于本话题会话 |
| 档位 | 含义 |
|---|---|
| 🔒 只读 | 只看不改(禁止 edit / shell) |
| ✏️ 可编辑 | 改文件免审批,跑命令要问 |
| 改文件 / 跑命令 / 越目录都逐次审批 | |
| 🔓 完全信任 | 什么都不问 |
权限请求会变成审批卡:✅ 允许一次 / 🔓 始终允许 / ✅ 本会话内允许该工具 / ❌ 拒绝。操作后卡片自动撤回(超出飞书撤回时限才降级为结果卡),不残留影响查看;换档(/perm)会清除本会话「本会话内允许」授权。
agent 反问时表单会变成飞书卡片:可自由输入的字段会直接在卡里渲染输入框,填好点「✅ 提交」即可;也能点选项按钮,或直接在话题里发文字作答(无需先点按钮)。作答后卡片自动撤回。纯选项题直接回序号 / 字母即可;若发的是其它内容,会当作普通消息交给 AI。
<configDir>/plugins/feishu.json(或 OpenCode plugins[].options),支持 {env:NAME} / ${NAME} 展开。
| 字段 | 类型 | 默认 | 说明 |
|---|---|---|---|
appId |
string | — | 飞书 App ID(必填,缺失则禁用插件) |
appSecret |
string | — | 飞书 App Secret(必填,永不写入日志) |
domain |
feishu|lark |
feishu |
飞书 / Lark 国际版 |
allowUsers |
string[] | [] |
open_id 白名单;空 = 仅 owner |
permissionGate |
off|notify|gate|lockdown |
gate |
全局审批门档位 |
allowTools |
string[] | ["read","glob","grep","webfetch"] |
免审批白名单,支持 prefix* |
denyTools |
string[] | [] |
强制拒绝(优先于白名单) |
allowedRoots |
string[] | [用户家目录] |
允许的工作目录根;越界 / 系统目录拒绝 |
stream |
boolean | true |
流式回填回复 |
threadRouting |
boolean | true |
话题路由总开关 |
logLevel |
debug|info|warn|error |
info |
日志级别 |
logFile |
string | boolean | — | true = 写 <configDir>/plugins/feishu.log,服务模式建议开启 |
approvalTtlMs |
number | 600000 |
审批 token / 卡片有效期 |
staleExecutionMs |
number | 300000 |
看门狗阈值(0–60 分钟;0 = 关闭看门狗;待答表单 / 未决审批期间不判卡死) |
quickNew |
boolean | true |
主聊天流「AI 会话管理」:AI 承接普通文本与建会话/管理类命令,判意图 + 找目录(拿不准时对话追问);false = 回到纯命令矩阵 |
busyDelivery |
steer|queue |
steer |
忙时新消息投递方式:steer = 立即插队打断当前步骤;queue = 原生排队(长命令场景更温和) |
messageBatchMs |
number | 1500 |
消息缓冲窗口:同一会话该窗口内连发的消息合并成一次 prompt / 一张回执卡(发图片/文件被拆成多条、连发多张图时不再刷屏);0 = 关闭 |
gatewayLocation |
string | — | 只在该 location(及其子目录)启动网关;留空 = 任意 location 生效 |
完整配置(含 cardMaxTables、topicStatus*、resumeSummary*、keepalive*、gatewayMatchGraceMs 等进阶项)见 docs/advanced.md。
| 现象 | 处理 |
|---|---|
| 发消息没反应 | ① 应用是否已发布、可用范围是否勾了你;② 订阅是否选了长连接(不是 Webhook);③ 是否开通 im:message.p2p_msg:readonly |
| 图片 / 文件收不到(只显示占位 / “下载失败”) | 开通 im:message:readonly(权限管理 → API 权限,搜“获取消息中的资源文件”)→ 重新创建版本并发布;具体失败原因看 feishu.log 的「附件下载失败」 |
改了 feishu.json 不生效 |
确认路径,然后 opencode reload |
| 插件完全没被加载 | npm 方式确认包名在 plugins 数组;目录方式确认 plugins/<名>/index.js 存在 |
| 审批卡收不到 | 该会话不是从飞书发起的(无映射),插件按设计不接管 |
| 点按钮提示凭证无效 | token 过期(默认 10 分钟)或点击者不在白名单 |
| 会话像卡死、只排队 | 看门狗默认 5 分钟后自动中断(待答表单 / 未决审批期间不中断;staleExecutionMs: 0 可关闭);也可点「⏹ 强制停止」或发 /stop |
| 空闲约 1 小时后失联 | opencode 每 60 分钟会回收空闲 location(无法从插件侧阻止);插件在被回收后秒级自动重建(v0.2.20,含单 location 场景),见 docs/advanced.md |
| 插件频繁重载 / 长连接反复重连 | logFile 落在了 opencode 配置目录内(写日志会被当成配置变更 → 每次写日志都触发重载)。改用默认值,或把日志移到配置目录之外(默认 ~/.local/state/opencode/feishu-plugin.log) |
| 看不到插件日志 | 服务模式下 stderr 被丢弃,设 logFile: true(默认写 ~/.local/state/opencode/feishu-plugin.log) |
| 多个长连接 / 重复回复 | gatewayLocation 只能收敛同一进程内的多个 location;插件假定一台机器只跑一个 opencode server。多进程(如 TUI + opencode serve)会各起一条长连接,审批回调可能落到不持有该请求的实例 → 卡片现在会显示「❌ 审批未生效」并可重试(不再假成功)。彻底避免请只保留一个实例 |
| 审批卡显示「❌ 审批未生效」/ 提示「审批未生效」 | permission.reply 没能送达:多为多实例(请求在另一个进程)或请求已过期。点卡片上的「🔁 重试」;仍失败就回到该会话重新触发一次操作,或只保留一个 opencode 实例 |
- 图片 / 文件消息会下载到本地并挂进会话(需开
im:message:readonly);音频 / 视频 / 表情包仍只给文字占位。 - 只接管从飞书发起的会话的审批;本地 TUI 会话不受影响。
- 建会话最终都经表单卡确认(AI 预填目录 / 模型 / 权限):
/new、/form或直接描述任务都会走到这张卡。 - 表单为 JSON 2.0,老客户端对
select_static有最低版本要求(≥ V3.7.0)。 - 话题首条消息可能不带
thread_id:插件会靠root_id兜底路由;新话题敲命令落到主聊天流时,直接进话题发消息即可。 - 生态里另有
opencode-feishu(V1 插件),与本插件不兼容、不共用代码。
按真实使用反馈迭代,当前规划:
- 接收图片 / 文件:已支持——自动下载到会话工作目录下的
.opencode/temp/opencode-feishu-plugin/(内置.gitignore,不污染git status;可用attachmentsDir覆盖;单附件默认 ≤20MB)。 - 忙时新消息默认插队:已支持——忙碌时新消息默认直接插队(打断当前步骤优先执行);
busyDelivery: "queue"可切回原生排队(长命令场景更温和)。
| 版本 | 亮点 |
|---|---|
| v0.2.23 | 消息缓冲:同一会话内窗口(messageBatchMs,默认 1500ms)连发的多条消息合并成一次 prompt / 一张回执卡——发图片/文件被拆成多条、连发多张图不再各出一张卡刷屏;回执卡仍在首条消息时立即发出,prompt 在最后一条消息静默该窗口后合并提交。messageBatchMs: 0 关闭 |
| v0.2.22 | 修复 issue #4「审批放行被静默吞掉」:permission.reply 失败不再假成功/误撤回卡片,改为显示红色「❌ 审批未生效」卡 + 🔁 重试(重签 token);reply 失败自动走本机 HTTP 兜底(跨实例也能投递到持有请求的 location);回调落到未跟踪实例时发可见失败提示;点击回执改为「已提交,正在处理…」 |
| v0.2.21 | 表单/提问卡片:可自由输入的字段直接在卡里渲染输入框 + 「✅ 提交」,不再要求用户到话题里发文字作答;仍兼容「点选项按钮 / 话题内直接发文字」 |
| v0.2.20 | 位置驱逐治理:实测确认驱逐无法从插件侧阻止(硬编码 60 分钟 TTL + 续期通道无效)→ 改为被驱逐后秒级复活(dispose 时安排 +1s/+5s/+20s 探针),空窗从 20 分钟压到 ~10 秒 |
| v0.2.19 | 主聊天流全面 AI 化:建会话 / 管理类命令(/new /form /dir /model /perm /sessions /use /resume)不再直接执行,改为和普通文本一样先交给 AI 承接意图再向下推进;AI 拿不准工作目录时不再擅自选定,改为在对话里直接追问(列候选或询问是否允许新建),下一条消息即当作回答;移除机器人自定义菜单(输入框上方的 /new /sessions 快捷按钮);审批卡操作后自动撤回(超飞书撤回时限才降级结果卡) |
| v0.2.18 | 修复日志触发的插件重载风暴:默认日志改为 ~/.local/state/opencode/feishu-plugin.log(opencode 监听整个配置目录,在其中写文件会被当成配置变更 → 每次写日志都重载插件、长连接反复重连);显式把日志放进配置目录会给出告警 |
| v0.2.17 | 位置保活重做:探针改用 GET /api/plugin(实测唯一能续期 / 重建 location 的通道);进程级看门狗持独立日志 sink,单 location / headless 被回收后一个心跳间隔内自愈(无需外部 cron) |
| v0.2.16 | 建会话目录优先:AI 先定好工作目录再预填表单,表单永不空目录;目录候选新增「允许根目录的一级子目录」,先复用现成目录,找不到才按主题新建 |
| v0.2.15 | AI 会话管理(主聊天流由 AI 判意图,直接给预填表单 / 会话列表);忙时默认插队(busyDelivery: "queue" 可切回排队);修复 opencode-go 下临时生成必失败 |
| v0.2.14 | 一句话建会话;修复流式文本重复 / 跨段重叠 |
| v0.2.13 | 修复排队回执卡终态后永久「等待中」;补充执行事件诊断日志 |
| v0.2.12 | 修复看门狗误杀(子代理 / 待答 / 未决审批期间不再判卡死);看门狗可关闭 |
| v0.2.11 | 机器人自定义菜单(输入框快捷按钮 /new、/sessions);兼容 SDK 拍平的事件形状 |
| v0.2.10 | 附件默认落盘到会话工作目录 .opencode/temp/opencode-feishu-plugin/(内置 .gitignore) |
| v0.2.9 | 接收图片 / 文件(自动下载并挂进会话,需 im:message:readonly) |
| v0.2.8 | 事件订阅自动重连(SSE 断流按指数退避重连,不再永久失明);进历史会话自动开话题;摘要截取兜底 |
完整历史见 GitHub Releases。
安全设计、位置保活、卡片守卫、会话恢复、多实例网关选举、完整配置项与开发架构见 docs/advanced.md。
MIT


