fengs2021 / dsh-feishu-bridge

Listed

DSH 飞书机器人桥接插件:飞书消息进 DSH 会话,流式交互卡片实时回复(思维链/正文/工具链分区,打字机效果)

mainOther View source

Installation

npx -y @deepseek-ai/dsh plugin --profile web add github:fengs2021/dsh-feishu-bridge

This installation command is an unverified starting point generated from the GitHub repository address.

README

Maintainer-authored documentation snapshot.

View on GitHub ↗
Commit e5b96b4Synced Aug 18, 2026

dsh-feishu-bridge

Chat with DeepSeek Harness (DSH) through a Feishu (Lark) bot. Messages you send the bot enter DSH sessions; replies come back as live streaming interactive cards — reasoning, body text, and tool chain in separate sections with typewriter updates. No public webhook needed (uses the Feishu Open Platform long connection).

通过飞书机器人与 DeepSeek Harness(DSH)对话的桥接插件。回复以流式交互卡片实时发回(思维链/正文/工具链分区、打字机效果),无需公网 webhook


English

Features

CapabilityDescription
Direct chatmessages to the bot reach DSH with multi-turn context
Group @respond when @-mentioned in groups (needs botOpenId)
Streaming cardsCardKit 2.0 cards update in real time: reasoning + body + tool chain sections, token-level typewriter; native collapsible panels (collapsed when done)
Multi-turn contextsame Feishu chat reuses the same DSH session; auto-resume after DSH restart (agents.resume)
Slash commands/help, /reset, /status
Long-reply chunking>8000 chars auto-splits into follow-up text messages
Reconnectconsume subprocess auto-backoff reconnects (2s → 30s cap)
Allowlistonly specified open_ids may chat
Idempotent dedupper message_id dedup against event replays
Channel direct-sendregisters a feishu channel in DSH's global registry (globalThis.__dshChannelNotify), so de_channel_send / de_notify can push text/images/files to Feishu (defaults to the most recent chat; target: 'p2p:oc_xxx' supported)

Architecture

Feishu App ──long connection──> lark-cli event consume im.message.receive_v1 --as bot (child process)
                                   │ NDJSON (chat_id / sender_id / content / mentions …)
                                   ▼
                     feishu-bridge plugin (DSH host process, cordis)
                                   │ agents.create / agents.resume (chat_id → sessionId)
                                   ▼
                           DSH agent session (same models/tools as GUI)
                                   │ llm/stream waterfall (per sessionId, token deltas)
                                   ▼
                    streaming accumulator (text / reasoning / tool-call deltas)
                                   │ throttled PATCH (default ≥1.2s)
                                   ▼
               lark-cli api patch im/v1/messages/:id ──▶ interactive card updates
                                   └── turn end ──▶ final card (✅ done) + sessions.flush
  • Event listening: lark-cli event consume im.message.receive_v1 --as bot (long-connection WebSocket, no public network required)
  • Streaming hook: DSH's llm/stream waterfall events, matched per sessionId
  • Card updates: PATCH /open-apis/im/v1/messages/:message_id with the card JSON string
  • Session mapping: <stateFile> (default ~/.dsh/plugins/dsh-feishu-bridge/state.json), chat_id → sessionId

Prerequisites

  • DeepSeek Harness running dsh web (plugin runs in the host process)
  • lark-cli installed + Feishu app configured (~/.lark-cli/config.json), bot identity available
  • A Feishu custom app with bot capability, scope covering target users/groups
npm install -g @larksuite/cli
lark-cli auth login          # choose bot identity (tenant_access_token)
lark-cli auth status         # bot: ready

The app needs the im:message permission; events come via long-connection subscription — no callback URL needed.

Install

git clone https://github.com/fengs2021/dsh-feishu-bridge.git ~/.dsh/plugins/dsh-feishu-bridge
cd ~/.dsh/plugins/dsh-feishu-bridge && npm install

# register into the web profile:
#   dependencies += "dsh-feishu-bridge": "link:/root/.dsh/plugins/dsh-feishu-bridge"
#   dsh.profile.bundles += "dsh-feishu-bridge"
cd ~/.dsh/profiles/web && pnpm install

systemctl restart dsh-web

Send the bot a message in Feishu — a card reply means success.

Config (all optional, cordis.patch.yml)

- id: feishu-bridge
  config:
    botOpenId: 'ou_xxxxxx'     # bot open_id (group @ detection)
    allowlist: ['ou_xxxxxx']   # open_id allowlist; empty = everyone
    cwd: '~'                   # working dir for new sessions
    enableGroup: true          # respond to group @mentions
    maxReplyChars: 3500        # text-mode reply cap (chunked beyond)
    typingHint: true           # "thinking" hint in text mode
    replyMarkdown: true        # markdown formatting in text mode
    streamCard: true           # streaming card mode (default on)
    cardPollMs: 600            # stream poll interval (turn-end fallback)
    cardMinIntervalMs: 1200    # min card update interval (rate-limit guard)
    maxTurnMs: 600000          # max wait per turn
    larkBin: 'lark-cli'        # lark-cli executable
    stateFile: '~/.dsh/plugins/dsh-feishu-bridge/state.json'

Getting botOpenId: auto-probed at startup (/open-apis/bot/v3/info); or @ the bot in a group and read the log (learned botOpenId=ou_xxx from group mention); or check the Feishu console → app → bot.

Usage

  1. Open the bot chat in Feishu (or add the bot to a group)
  2. Message it directly; in groups, @ the bot
  3. Commands: /help, /reset, /status

Channel direct-send (DSH → Feishu): de_channel_send channels=feishu content=...; attachments via attachments=[{kind:'image'|'file', path|url|base64, fileName?}]; target defaults to the most recent Feishu chat, or explicit target: 'p2p:oc_xxx'. Implemented in lib/channel-registry.js (standalone, zero deps on the notification module).

FAQ highlights

  • Card stuck at "thinking" → check bridge.log: patchCard failed = app permission; target=miss = session mapping
  • stream is not async iterable → known bug of old versions; upgrade to 1.0.0 (⚠️ the listener affects ALL LLM calls — do not revert to async)
  • Group @ not responding → check botOpenId + app scope
  • Card update 230001/230099 → must use PATCH /open-apis/im/v1/messages/:id with card JSON body (never PUT + msg_type)
  • Context lost after restart → plugin auto-agents.resume; failed resumes rebuild on next message

Development

npm test          # pure-function unit tests
npm run check     # static checks

Design notes for contributors: all pure functions are exported (collectReply / buildCard / createStreamAccumulator / toolSummary / splitChunks / cleanContent / shouldHandle); llm/stream is a cordis waterfall event — the listener must be a sync function returning an AsyncIterable (yield chunks through, never swallow exceptions); all lark-cli calls go through runLark() (timeout + output capture, degraded paths); concurrent resume on the same session is coordinated by a per-session promise lock.

中文

通过飞书机器人与 DeepSeek Harness(DSH) 对话的桥接插件。

在飞书里给机器人发消息 → 消息进入 DSH 会话由 AI 处理 → 回复以流式交互卡片实时发回:思维链、正文、工具链分区展示,打字机效果逐字更新。无需公网 webhook(使用飞书开放平台长连接)。

效果示例(交互卡片,随生成实时更新):

┌──────────────────────────────────┐
│ 🤖 DSH 助手                        │
├──────────────────────────────────┤
│ ▶ 🧠 思维链 · 212 字(点击展开)    │
│ 用户想查 /root/dsh 目录…            │
│ ──────────────────────────────── │
│ 好的,我来查看一下目录内容…          │
│ ──────────────────────────────── │
│ 🔧 工具链(2)                      │
│ - ✅ bash  ls -la /root/dsh        │
│ - ✅ memory 写入今日日志            │
│ ──────────────────────────────── │
│ ✅ 已完成                          │
└──────────────────────────────────┘

功能特性

能力说明
私聊对话与机器人单聊,消息直达 DSH,多轮上下文连续
群聊 @群聊中 @ 机器人即响应(需配置 botOpenId
流式卡片CardKit 2.0 交互卡片实时更新:思维链 + 正文 + 工具链分区,token 级打字机效果;思维链/工具链为原生折叠面板(完结后默认收起,点击展开)
多轮上下文同一飞书会话固定复用同一 DSH 会话;DSH 重启后自动恢复(agents.resume
斜杠命令/help 帮助、/reset 清空上下文、/status 会话状态
长回复分段超过卡片预览上限(8000 字)自动补发文本消息
断线重连consume 子进程异常退出自动退避重连(2s 起、30s 封顶)
白名单只允许指定 open_id 对话
幂等去重按 message_id 去重,避免事件重放导致重复处理
渠道直发注册 feishu 渠道到 DSH 全局注册表(globalThis.__dshChannelNotify),de_channel_send / de_notify 可直接把文本/图片/文件发到飞书(默认发往最近交互的会话,也支持 target: 'p2p:oc_xxx' 显式指定)

架构原理

飞书 App ──长连接──> lark-cli event consume im.message.receive_v1 --as bot(子进程)
                          │ NDJSON(chat_id / sender_id / content / mentions …)
                          ▼
              feishu-bridge 插件(DSH host 进程内,cordis 插件)
                          │ agents.create / agents.resume(chat_id → sessionId 映射)
                          ▼
                    DSH agent 会话(模型、工具与 GUI 同款)
                          │ llm/stream waterfall(按 sessionId 匹配,token 级 delta)
                          ▼
              流式累积器(text-delta / reasoning-delta / tool-call-delta)
                          │ 节流 PATCH(默认 ≥1.2s 一次)
                          ▼
        lark-cli api patch im/v1/messages/:id ──> 交互卡片实时更新(打字机效果)
                          │
                          └── turn 结束 ──> 最终态卡片(✅ 已完成)+ sessions.flush 持久化
  • 事件监听lark-cli event consume im.message.receive_v1 --as bot(飞书长连接 WebSocket,无公网要求)
  • 流式接入:DSH 的 llm/stream waterfall 事件(每次模型调用都会经过),按请求携带的 sessionId 匹配到飞书 turn,拦截增量 chunk
  • 卡片更新PATCH /open-apis/im/v1/messages/:message_idcontent 为交互卡片 JSON 字符串
  • 会话映射<stateFile>(默认 ~/.dsh/plugins/dsh-feishu-bridge/state.json),记录 chat_id → sessionId

前置条件

依赖说明
DeepSeek Harness已安装并运行 dsh web(插件运行在 host 进程内)
lark-cli已安装并完成飞书应用配置(~/.lark-cli/config.json),bot 身份可用
飞书自建应用具备机器人能力,可用范围包含目标用户/群

lark-cli 安装与授权:

npm install -g @larksuite/cli
lark-cli auth login          # 选择 bot 身份(tenant_access_token)
lark-cli auth status         # bot: ready

bot 身份要求应用具备 im:message(收发消息)权限;消息事件走长连接订阅,无需在开放平台配置回调地址。

安装

# 1. 获取插件源码(二选一)
git clone https://github.com/fengs2021/dsh-feishu-bridge.git ~/.dsh/plugins/dsh-feishu-bridge
# 或手动放置到 ~/.dsh/plugins/dsh-feishu-bridge/

# 2. 安装插件依赖(@deepseek-ai/dsh-agent 等)
cd ~/.dsh/plugins/dsh-feishu-bridge
npm install

# 3. 注册进 web profile
cd ~/.dsh/profiles/web
# 编辑 package.json:
#   - dependencies 增加  "dsh-feishu-bridge": "link:/root/.dsh/plugins/dsh-feishu-bridge"
#   - dsh.profile.bundles 数组增加 "dsh-feishu-bridge"
pnpm install

# 4. 重启 dsh web(插件在 host 进程内启动 consume 子进程)
systemctl restart dsh-web      # systemd 托管
# 或手动重启你的 dsh web 进程

安装成功后,在飞书中给机器人发一条消息测试;回复以卡片形式出现即成功。 若 ~/.dsh/plugins/ 下已有其他插件(如 dsh-novel-studio),参照其安装方式即可。

配置

~/.dsh/profiles/web/cordis.patch.yml 中覆盖配置(全部可选,均有默认值):

- id: feishu-bridge
  config:
    botOpenId: 'ou_xxxxxx'     # 机器人 open_id(群聊 @ 判断用;见下方获取方式)
    allowlist: ['ou_xxxxxx']   # open_id 白名单;空数组 = 允许所有人
    cwd: '~'                   # 新会话工作目录(默认用户主目录)
    enableGroup: true          # 是否响应群聊中 @ 机器人的消息
    maxReplyChars: 3500        # 文本模式单条回复上限(超出分段)
    typingHint: true           # 文本模式下收到先回「思考中」提示
    replyMarkdown: true        # 文本模式回复使用 markdown 排版
    streamCard: true           # 流式卡片模式(默认开;关闭则退回文本分段回复)
    cardPollMs: 600            # 流式轮询间隔(毫秒,兜底检测 turn 结束)
    cardMinIntervalMs: 1200    # 卡片更新最小间隔(毫秒,飞书接口限频保护)
    maxTurnMs: 600000          # 单轮最长等待(毫秒),超时停止更新并提示
    larkBin: 'lark-cli'        # lark-cli 可执行文件路径
    stateFile: '~/.dsh/plugins/dsh-feishu-bridge/state.json'

botOpenId 获取方式

  1. 插件启动时自动探测(/open-apis/bot/v3/info;部分应用权限下返回为空);
  2. 在任意群里 @ 机器人发一条消息,插件日志会打印 learned botOpenId=ou_xxx from group mention,填入配置即可;
  3. 飞书开放平台后台 → 应用 → 机器人,查看机器人 open_id。

使用

  1. 在飞书中搜索并打开机器人会话(或让管理员把机器人拉进群聊);
  2. 直接发消息即可对话;群聊中需 @ 机器人;

渠道直发(DSH → 飞书)

插件在 apply 时把主动发送能力登记到 DSH 渠道注册表(与 dsh-memory-evolve 通知模块的 globalThis.__dshChannelNotify 约定一致),因此 DSH 的 de_channel_send / de_notify 工具可直接发到飞书:

  • 文本:de_channel_send channels=feishu content=...
  • 附件:attachments=[{kind:'image'|'file', path|url|base64, fileName?}](本地路径/base64 经临时目录 + 相对路径发送;图片走 --image,其余走 --file
  • 目标:缺省 = 最近交互的飞书会话(插件 state 记录);显式传 target: 'p2p:oc_xxx'
  • 实现:lib/channel-registry.js(独立模块,零依赖通知模块)
  1. 命令:
    • /help — 帮助
    • /reset — 清空当前对话上下文,重新开始
    • /status — 查看会话状态(sessionId / 模型 / 已处理消息数)

运维

事项说明
插件日志~/.dsh/plugins/dsh-feishu-bridge/bridge.log(事件接收 / 会话创建 / 流式匹配 / 错误)
会话映射state.json;删除某条映射并重启即与该飞书会话「断连」(也可在飞书里发 /reset
consume 异常子进程自动退避重连;kill -9 可能泄漏服务端订阅,勿用
卸载从 profile package.json 的 dependencies/bundles 移除,pnpm install 后重启
升级git -C ~/.dsh/plugins/dsh-feishu-bridge pull 后重启 dsh web

常见问题(FAQ)

Q:飞书里一直显示「思考中」,卡片不更新? 检查 bridge.log:若出现 patchCard failed,确认飞书应用权限与卡片消息是否可更新; 若出现 target=miss,确认消息对应会话的 sessionIdstate.json 一致(重启后会自动恢复)。

Q:收到「stream is not async iterable」? 历史版本的已知 bug(llm/stream listener 误用 async 函数),升级到 1.0.0 即可。 ⚠️ 该 listener 影响所有 LLM 调用,请勿改回 async 函数。

Q:群聊里 @ 机器人没反应? 确认 botOpenId 已配置正确(见上文获取方式),且应用已在群内、可用范围包含该群。

Q:卡片更新失败(230001 / 230099)? 卡片更新接口固定为 PATCH /open-apis/im/v1/messages/:id(body 为 {"content": "<卡片JSON字符串>"});不要改用 PUT + msg_type(会返回 230001)。

Q:重启 DSH 后上下文丢失? 重启后插件会 agents.resume 恢复持久化会话(日志可见 preloaded session ...)。 若 resume 失败(如会话文件损坏),映射会被保留,下次消息到来时自动重建。

开发

# 单元测试(纯函数:流式累积器 / 事件聚合 / 卡片构建 / 辅助函数)
npm test

# 静态检查
npm run check

目录结构

dsh-feishu-bridge/
├── lib/index.js            # 插件主体(cordis 插件)
├── cordis.patch.yml        # bundle patch(插入 web profile roster)
├── test/functions.test.mjs # 纯函数单元测试
├── package.json
├── README.md
├── CHANGELOG.md
└── LICENSE

设计要点(贡献者必读)

  • lib/index.js 导出全部纯函数(collectReply / buildCard / createStreamAccumulator / toolSummary / splitChunks / cleanContent / shouldHandle),便于测试与复用;
  • llm/stream 是 cordis waterfall 事件:listener 必须为同步函数且返回 AsyncIterable(包装流时逐 chunk yield 透传,不得吞异常);
  • 所有 lark-cli 子进程调用走 runLark()(超时 + 输出捕获),失败均有降级路径;
  • 同一持久化会话的并发 resume 通过 per-session promise 协调锁去重。

许可证

MIT

相关项目

Project files and signals

Shown items are public repository signals detected in the directory snapshot.

TestsDetected

Repository information

Language
JavaScript
License
MIT
Latest release
v1.0.0
Last updated
Aug 17, 2026, 3:25 AM

Install deliberately

Review source code, permissions, lifecycle hooks, dependencies and network access. Test untrusted plugins in an isolated environment.