Skip to content

About

opencode v2 Feishu/Lark plugin: minimal single-user p2p chat + in-card permission approval via long connection (zero public endpoint)

Topics

Resources

Stars

4 stars

Watchers

0 watching

Forks

Repository files navigation

opencode-feishu-plugin

English | 简体中文

把 OpenCode 接进飞书:一个飞书话题 = 一个 OpenCode 会话,权限审批直接在飞书卡片上点按钮。

  • 只支持 OpenCode V2(@opencode/plugin,Plugin.define),不依赖任何 V1 包。
  • 纯长连接(WebSocket)收发事件与卡片回调:不监听端口、不需要公网地址。

效果预览

飞书话题内的完整会话:工具调用、权限审批卡与强制停止

亮点

说明
🔐 最小权限 只要 2 个 scope,不申请任何群权限,机器人物理上收不到群消息
💬 话题 = 会话 一个飞书话题对应一个 OpenCode 会话;主聊天流只做管理,互不串台
🚀 一键建会话 /new 一张表单(目录 + 模型 + 权限档位)一次填完,提交即建会话并自动开话题
✅ 卡片审批 权限请求变飞书卡片:允许一次 / 始终允许 / 本会话内允许 / 拒绝,自签 token 防伪防重放
📊 实时可见 「思考中」回执 → 工具调用实时上卡 → 文本流式更新,页脚显示当前模型
⏹ 可控可停 每张回复卡带「强制停止」;看门狗自动中断卡死会话;忙时新消息默认插队(可配置为排队),/steer /now 随时可用
📎 图片 / 文件 飞书里的图片 / 文件自动下载并挂进会话,支持视觉 / 文件的模型直接看图、读文件
🧭 AI 会话管理(主聊天流) 主聊天流发任务文本或建会话/管理类命令(/new /sessions /use /resume 等),都先交给 AI 承接意图、找好工作目录;目录拿不准时直接在对话里追问,确认后一键建会话并开始处理
🚫 无端口 全程长连接,服务器无需开放任何入站端口

一、飞书后台配置(约 3 分钟)

  1. 打开 飞书开放平台 → 创建企业自建应用。

  2. 添加应用能力 → 机器人。

  3. 权限管理 → API 权限:先开这两个必开 scope:

    • im:message.p2p_msg:readonly —— 读取用户发给机器人的单聊消息
    • im:message:send_as_bot —— 以应用身份发消息(也用于更新卡片)

    接收图片 / 文件需要再加开一个(不需要该功能可跳过):

    • im:message:readonly —— 获取消息中的资源文件(图片 / 文件下载的必要条件)

    ⚠️ 未开通 im:message:readonly 时:图片/文件不会被下载,消息照常送达 AI,但只带占位文本("…下载失败:…")。开通后要重新创建版本并发布才生效。

  4. 事件与回调 → 事件配置:订阅方式选**「使用长连接接收事件」**(不要选 Webhook),添加事件 im.message.receive_v1。

  5. 事件与回调 → 回调配置:订阅方式同样选长连接,添加回调 card.action.trigger(零权限要求)。

  6. 版本管理与发布:可用范围 = 仅本人,创建版本并发布。⚠️ 不发布就是开发态,长连接连不上,机器人不会有任何反应。

  7. 记下 App ID(cli_…)与 App Secret。

为什么不申请群权限? 本插件是"一个人的遥控台"。不申请群权限,机器人物理上收不到群消息,单人边界由平台 scope 层保证,而不是只靠代码判断。

二、安装

1. 安装插件

# 方式 A:CLI(推荐)
opencode plugin add opencode-feishu-plugin
// 方式 B:手写配置(追加到已有 plugins 数组,别覆盖整个文件)
{ "plugins": ["opencode-feishu-plugin"] }

插件入口是自包含的 dist/index.js(已打包飞书 SDK),运行时无需手动 npm install。

2. 写配置

新建 ~/.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 会被丢弃,开着才有日志可查。

3. 生效与验证

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 放弃未提交的表单 / 命令列表

建会话表单卡:目录、模型、权限一次填完 会话列表卡片:翻页、进入/再开、新建

AI 会话管理(主聊天流)

主聊天流发普通文本或建会话 / 管理类命令(/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)会清除本会话「本会话内允许」授权。

表单提问(question 工具)

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 插件),与本插件不兼容、不共用代码。

七、下一步规划(Roadmap)

按真实使用反馈迭代,当前规划:

  • 接收图片 / 文件:已支持——自动下载到会话工作目录下的 .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

About

opencode v2 Feishu/Lark plugin: minimal single-user p2p chat + in-card permission approval via long connection (zero public endpoint)

Topics

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages