dsh-feishu — Feishu (Lark) IM bridge for DeepSeek Harness
Pluggable cordis plugin wiring a Feishu self-built bot into a running
DeepSeek Harness (dsh) web
profile — installable via dsh plugin add.
┌────────┐ WS ┌──────────────┐ cordis ┌──────────┐ LLM ┌──────────┐
│ Feishu │ ──────> │ this plugin │ ──────────> │ dsh agent │ ────────> │ DeepSeek │
│ IM │ <────── │ │ <────────── │ │ <──────── │ │
└────────┘ API └──────────────┘ events └──────────┘ stream └──────────┘
dsh-feishu — DeepSeek Harness × Feishu IM bridge
Features
- One Feishu DM user ↔ one persistent dsh session (
feishu:<open_id>).
- Multi-turn conversations across reconnects.
- Streams assistant replies back into Feishu, chunked at 4000 chars.
- Filters DeepSeek
<|DSML|...> tool-call markers from outbound text.
- Interactive-question bridging.
ask_user_question payloads (with their options) are
rendered as plain chat text and the turn is unblocked immediately — otherwise the tool hangs
until the watchdog fires, because it only accepts answers from the dsh web browser client.
- Progress updates. Once a turn has run past 45s, newly produced assistant text is flushed
to the chat every 30s, so long jobs are no longer silent.
- Watchdog. A stalled turn is force-cancelled with a notice to the user; the timeout is
configurable (30 min default).
- Pure ESM, no TypeScript compile step.
- No telemetry, fully local.
Installation
1. Install dsh
npm install -g @deepseek-ai/dsh
dsh web --help
2. Install the plugin
dsh plugin add dsh-feishu
This installs the plugin into ~/.dsh/profiles/web/node_modules/dsh-feishu and
patches cordis.patch.yml automatically.
3. Configure your Feishu app
Create a Custom App at https://open.feishu.cn/app and copy appId + appSecret.
Under Event Subscriptions (事件与回调):
- Set mode to Receive events via persistent connection (使用长连接接收事件/回调).
- Add the event
im.message.receive_v1.
Under Permissions (权限), grant:
im:message
im:message.p2p_msg (required for DMs)
Publish a version (发布版本) — without this the bot cannot receive events.
Wait ~2 minutes after publishing.
4. Export env vars and run
export DEEPSEEK_API_KEY="sk-..."
export FEISHU_APP_ID="cli_..."
export FEISHU_APP_SECRET="..."
dsh web
You should see in logs:
[feishu] WebSocket started (appId=cli_xxx)
[feishu] FeishuBridgeService initialized
DM the bot anything — the agent will reply in the same conversation.
Environment variables
| Variable | Required | Default | Notes |
|---|
DEEPSEEK_API_KEY | yes (LLM) | — | https://platform.deepseek.com |
DEEPSEEK_BASE_URL | no | https://api.deepseek.com | For proxies |
FEISHU_APP_ID | yes | — | cli_xxx from app console |
FEISHU_APP_SECRET | yes | — | From app console — never commit |
DSH_FEISHU_WATCHDOG_TIMEOUT_MS | no | 1800000 (30 min) | Force-cancel threshold for a stalled turn |
DSH_FEISHU_PROGRESS_AFTER_MS | no | 45000 | How long a turn runs before progress updates start |
DSH_FEISHU_PROGRESS_INTERVAL_MS | no | 30000 | Minimum gap between two progress updates |
How a message flows
- User DMs the bot.
- Feishu SDK fires
im.message.receive_v1 → this plugin.
- Plugin loads/creates the agent for
feishu:<open_id>.
- Plugin calls
agent.followup(userMessage).
- When agent returns to
idle, plugin reads agent.session.events, strips
DSML noise, and posts the reply via larkClient.im.message.create(...).
- While the turn runs, a 2s polling watchdog does two jobs: an unsettled
ask_user_question call is bridged to the chat (question + options) and the turn is
cancelled to unblock it; a turn running past the threshold gets its new assistant text
flushed as a "progress" message. Both share one seq cursor with the final reply, so no
text is ever sent twice.
Limitations
- Text only. Image / file / card / post messages are not handled.
- No token-level streaming. Replies are still sent as whole blocks; during a long turn you
only get periodic progress updates (see above).
- No groups yet. DMs only.
- Interactive UI tools are degraded.
ask_user_question / exit_plan_mode only accept
answers from the dsh web client. Over IM the plugin renders the question as text and cancels
the turn, and injects an environment notice so the model prefers asking in prose.
License
MIT.
Acknowledgments
Author
itr-del — 13918029394@163.com
Built while integrating dsh with a self-hosted Feishu bot on Ubuntu 22.04.
📖 Open-sourcing story: PUBLISHING.md — how this repo got published and listed.
中文文档见 README.zh.md。