← Back to home@superSizzzz

dsh-feishu-bridge

Two-way bridge between DeepSeek Harness (dsh) and a Feishu / Lark bot: stage conclusions out, messages in, work summary at the end.

Stars
0
Language
TypeScript
Created
Sep 26, 2026
Updated
Oct 1, 2026
GitHub repo

Introduction

dsh-feishu-bridge

English | 中文

用 dsh 干活的时候,人得守在电脑前。一离开工位,它干到哪儿了、要不要你拍板, 全都看不见;等回来一看,可能早就卡在某个问题上等了半天。

这个插件在 dsh 和一个飞书机器人之间搭了条线:它在飞书上跟你汇报,你在飞书上 回它,消息直接进会话,接着干。

目录

安装

dsh plugin --profile web add github:superSizzzz/dsh-feishu-bridge

一条命令装好。dsh 会读包里的 dsh.bundle.patch 自动并入当前 profile, 不用你手写配置。

装完还差一步:在飞书里给机器人发一句话。

它会从这条消息里认出你、记住你的 open_id,之后只服务你一个人。飞书的 open_id 是按应用维度隔离的,从别处抄不到,所以让它自己认比手填靠谱。

项要求
dsh≥ 0.1.7-rc.2
Node≥ 22
lark-cli全局装一份,见下
飞书应用自己建一个,需要 im:message + im:message:send_as_bot

还没建过飞书应用的话:

npm i -g @larksuite/cli
lark-cli config init --new --name dsh-bridge

浏览器会带你走一遍建应用流程。在开放平台给它开两个权限 (im:message、im:message:send_as_bot),事件订阅里加上 im.message.receive_v1,选长连接模式——这种模式不需要公网地址,也不用开端口。

想用卡片按钮就再加 im:message:readonly 和 card.action.trigger。不加也能用, 只是按钮点不动。

每个 profile 各装一次,换 profile 就换 --profile 后面的名字。

也可以让 agent 帮你装

把下面这段丢给你的 dsh agent:

帮我安装 dsh-feishu-bridge 插件(飞书 ↔ dsh 双向桥)。

仓库地址:https://github.com/superSizzzz/dsh-feishu-bridge

1. 用这一条命令装进 web profile:
     dsh plugin --profile web add github:superSizzzz/dsh-feishu-bridge
   它会转发给 pnpm 去装,同时自动把插件的 bundle 层登记进 profile,
   所以不用 clone 仓库,也不用手改任何配置文件。
2. 检查前置条件:lark-cli 已安装,且 `lark-cli --profile dsh-bridge whoami`
   能返回 appId。如果这个 profile 还不存在,先停下来告诉我 ——
   建飞书应用得我自己在浏览器里点。
3. 重启 dsh 让插件生效。
4. 报告三件事:插件有没有加载成功(看 ~/.dsh/dsh-feishu-bridge/boot.log)、
   设置页里有没有出现「飞书桥」这一栏、我接下来要在飞书里做什么。

它什么时候说话

默认只在三个时刻出声,中间那段是安静的。

你一给它派活,先说一句打算怎么干。 这跟判断无关,是固定动作,每次都说。

干出一个阶段成果,报一次。 这个是攒着来的:思考先存着,攒到有东西可说了, 才问模型一句「这一阶段实际做成了什么」。回答 NONE 就继续攒。

之所以这么绕,是因为按句报会变噪音。一段一段地报「我在看这个文件」「我去改那个 函数」,等于把终端日志搬到飞书。所以标准定得死:只报已经做完的事, 「我打算…」「下一步…」不算数。没干成什么就先不说,等后面一起讲。

活儿干完,给一份工作汇报。 做了什么、动了哪些文件、加了多少行,都列上。

想让它多说话,配置里把 turnPush 改成 always,就是每轮都报。

想临时安静一会儿,飞书上发 /mute;想让它闭嘴更彻底,把配置里的 enabled 改成 false。

长什么样

飞书那头收到的是卡片。下面是真实跑出来的结构(正文每次都不一样,路径和内容 做了泛化):

┌────────────────────────────────────────────┐
│ 这一阶段干完了什么 · 第 12 轮               │
│ 大肥鲸来报                                  │
├────────────────────────────────────────────┤
│ 在哪干活   ~/projects/my-app                │
│ 会话       #A3F2 修图片上传链路 · turn 12   │
├────────────────────────────────────────────┤
│ 图片链路三个 bug 都定位到了:数据其实在      │
│ data.messages 而不是 items;资源键是嵌在     │
│ 文本里的 [Image: img_v3_...];--type 是必需  │
│ 参数但 help 里没列出来。下载那步已实测通过。  │
└────────────────────────────────────────────┘

收尾那份会多一栏改动清单:

┌────────────────────────────────────────────┐
│ 工作汇报                                    │
│ 大肥鲸汇报                                  │
├────────────────────────────────────────────┤
│ 工作区   ~/projects/my-app                  │
│ 会话     #A3F2 修图片上传链路                │
│ 过程     17:35 → 17:50 · 6 turn · 216 次工具调用│
├────────────────────────────────────────────┤
│ 这轮把图片上传链路跑通了。路径记错了、资源键  │
│ 藏在文本里、参数是必需但文档没写 —— 三个问题  │
│ 都修完并实测过下载那一步。                   │
│                                            │
│ 这轮动的文件(2 文件  +111 −0)              │
│   src/upload.ts           +58 −0            │
│   src/types.ts            +53 −0            │
└────────────────────────────────────────────┘

会话那行的短码(#A3F2)是路由用的身份,/use 靠它切目标;后面跟的是标题, 一眼认出这是哪件事。标题优先取 dsh 的标题服务,取不到就用你第一句话截一段。

配置

两个入口,读写的是同一份东西,用哪个都行。

dsh 设置页(推荐):打开设置,左边有一栏「飞书桥」。能改四项——用哪个机器人、 绑定谁、闲聊用哪个模型、以及说话的人设。

浏览器直接开:

http://127.0.0.1:3080/feishu-bridge/config

端口就是 dsh web 的端口,改过就跟着换。

改完保存就生效,不用重启。行为开关(turnPush、thinkingJudge 这些)没放进 界面——它们是装的时候定一次的东西。真要调就在你自己的 patch 里写:

- id: feishu-bridge
  config:
    turnPush: always
    thinkingJudge: false
字段默认值含义
enabledtrue总开关
profiledsh-bridge用哪个 lark-cli profile,也可以直接填 app id
userId''投递目标。留空就行,第一条消息会自动认领
promptSectiontrue往系统提示里写一段「你可以用 feishu_notify」
openPushtrue开工先说一句,固定动作
turnPushjudge每轮策略:judge 让模型判断 / always / changes / off
thinkingJudgetrue每段思考后判断这一阶段有没有成果
pendingMinChars400攒够多少字才值得问模型一次
summaryPushtrue收尾的工作汇报
writerEnabledtrue文案由模型现写,不套模板
persona女高中生说话的口吻

飞书指令

在飞书上发这些,会被当命令处理。

指令作用
/help看指令列表
/status当前绑定、投递目标、模式
/list见过的会话,带短码和标题
/use <短码>换默认投递到哪个会话
/stop打断正在跑的 agent
/mute /unmute这段先别说话 / 恢复
/chat /work切闲聊模式 / 切回来
/polish <文字>用人设把一段话改写一遍
/config看或改运行时配置
/forget清掉闲聊记录

不是命令的普通消息直接进会话,跟你在电脑上打字一样。

发图片也行——它会下载下来交给附件服务,模型能看见。

闲聊模式

/chat 之后,飞书那头就是个普通聊天窗口:只跟模型说话,完全不碰 dsh, 不起会话、不用工具、不动你的工作区。/work 切回来。

用的还是 dsh 的 key,模型可以在设置页改。

聊天记录存在本地,但模式不存——重启 dsh 会回到工作模式。这是故意的,免得你 在不知情的情况下跟一个不会干活的机器人聊半天。

提问桥

dsh 要问你问题时(ask_user_question),插件同时做两件事:把问题做成卡片推到 飞书,然后网页那边照常等着。

谁先答用谁。飞书答了,网页的弹窗会被收掉;网页答了,飞书那张卡会改成 「已在网页作答」。

这么做是为了两头都不耽误:在电脑前直接答最顺手,不在电脑前手机上也能拍板。 两边都不理它(默认 5 分钟),就当没答,接着往下走,不会把 agent 卡死。

卡片上的选项做成了可点按钮,点一下答案就送回去了。

权限

飞书权限只申请 im:message 和 im:message:send_as_bot
飞书事件只订阅 im.message.receive_v1(和可选的 card.action.trigger)
网络监听不新开端口。配置页复用 dsh 自己的 HTTP 服务
出站连接只有飞书,和你自己配的模型 provider
磁盘只写 $DSH_HOME/dsh-feishu-bridge/ 下的状态文件和日志
遥测没有。仓库里没有任何统计代码

有一条得说明白:推给你的每句话(开工报告、阶段总结、工作汇报、闲聊)都是模型 现写的,所以会话内容会随着这些请求发给你的模型 provider。装之前知道这一点。

飞书那边不碰通讯录、云文档、日历;open_id 按应用隔离,插件也只可能看见给这个 机器人发过消息的人。

卸载

dsh plugin --profile web remove dsh-feishu-bridge

状态文件在 $DSH_HOME/dsh-feishu-bridge/,想清干净就把那个目录删掉。飞书那边的 应用和权限要你自己去开放平台处理,插件管不着。

常见问题

装完没动静? 先给机器人发一句话。没认领之前它不知道消息该投给谁,所以是安静的。

发消息没反应? 看看 $DSH_HOME/dsh-feishu-bridge/boot.log,启动自检都写在里头。 另外同一台机器上跑两个 dsh 实例时,只有一个会收飞书消息(用 pid 锁选的),另一个 只管本地会话的播报。

桌面版会弹黑窗? 老版本会,已经修了。原因不在 dsh——lark-cli 的 scripts/run.js 只是个转发脚本,它内部去调原生二进制时没带 windowsHide。 终端里跑看不出来,桌面版没有控制台,那个子进程只能自己新建一个。现在改成直接调 原生二进制了,升级到最新版就好。

改了插件代码没生效? 插件源码不会热重载,重启 dsh。

想给机器人改名? 去开放平台改,插件启动时会自己把新名字问回来填进提示词。

自己改

src/
├── index.ts        插件入口:配置、装配、指令路由、生命周期
├── config.ts       schemastery 配置 schema
├── config-page.ts  配置页(无构建的本地 HTML 表单)
├── lark-cli.ts     lark-cli 封装(发消息、下载、查身份)
├── inbox.ts        通用 NDJSON 事件消费(给任意 EventKey 用)
├── outbox.ts       节流、幂等、原地改卡
├── render.ts       飞书卡片 2.0 构造
├── reporter.ts     会话事件监听、积累素材、写汇报
├── questions.ts    提问桥(双通道竞速)
├── writer.ts       用模型把素材写成文案
├── chat.ts         闲聊模式
└── tools.ts        feishu_notify / feishu_summary / feishu_silence
lib/client.js       客户端那半:dsh 设置页里的「飞书桥」。手写,没有打包步骤
tools/              几个单独跑的冒烟脚本

下面这些是我们踩过的坑,改之前扫一眼能省不少时间。

dsh 用 strip-only 模式加载 TS,不支持 TypeScript 参数属性 (constructor(private readonly x: T))。得显式声明字段,不然插件静默不加载。

提示词变量名必须匹配 /^[a-z][a-z0-9_]*$/。我写过一个 feishuBotName, 里面两个大写字母,注册当场抛错——那个错会冒到 apply() 外面,整个插件都不激活, 不只是提示词没了。

系统提示里留一个没注册的 {{变量}} 会让组装直接抛错。那不只是本插件失效, 是所有会话都拿不到系统提示。要么保证变量一定注册得上,要么注册时就静态替换掉。

取服务不是同步的。apply() 里 ctx.get('webServer') 经常拿到 undefined, 加载顺序不保证。要用 ctx.inject(['webServer'], cb) 等它就绪。

客户端那半必须能按包名解析。dsh-client-modules 是按包名扫 Loader 条目、读 package.json 里的 dsh.client 和 exports["./client"] 的,file:/// 形式它不认。

推理模型要留余量。deepseek-flash 会先花一批 token 思考,maxTokens 给小了会 出现「思考吃满、正文一个字没写」的空回复,看着像模型坏了。给 3000 起步。

别用 PowerShell 的 Get-Content/Set-Content 批量改 UTF-8 文件。PS 5.1 默认按 ANSI 读,中文读进来就已经是乱码了,-Encoding UTF8 只保证写入侧,救不回来。我这么 毁过一次 README。用编辑器或读写文件的工具。

许可

MIT,见 LICENSE。