DSH HUB
HomePlugin StoreRankingsPublish Guide
Plugin source
Back to catalog

fengs2021 /

dsh-feishu-bridge

Verified

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

★ 0 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHub
READMESource: main@e5b96b4f

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

Capability Description
Direct chat messages to the bot reach DSH with multi-turn context
Group @ respond when @-mentioned in groups (needs botOpenId)
Streaming cards CardKit 2.0 cards update in real time: reasoning + body + tool chain sections, token-level typewriter; native collapsible panels (collapsed when done)
Multi-turn context same 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
Reconnect consume subprocess auto-backoff reconnects (2s → 30s cap)
Allowlist only specified open_ids may chat
Idempotent dedup per message_id dedup against event replays
Channel direct-send registers 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_id,content 为交互卡片 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,确认消息对应会话的 sessionId 与 state.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

相关项目

  • DeepSeek Harness — 本插件运行的宿主
  • @larksuite/cli — 飞书开放平台 CLI(事件订阅 / 消息收发 / 卡片更新)
—/ 5

No ratings yet

Verified DSH bundle

Commit e5b96b4f114a

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

APIPublish GuideAbout