dsh-feishu
> Part of the DSH ecosystem — channel plugins built on the DeepSeek Harness (DSH) plugin architecture.
Feishu (Lark) IM channel bridge for the DeepSeek Harness (DSH) — turn dsh into a Feishu bot with one long-lived Agent per chat (true rolling memory, flushed to disk).
What it does
eventsmode (default): consumesim.message.receive_v1directly vialark-clion the same machine. Requires this machine to own the app's single global event-bus connection, andlark-cliauthenticated as the bot (lark-cli auth login).relaymode: long-polls a public HTTP job relay, so the DSH machine can sit behind NAT. A Feishu-side bot (e.g. an OpenClaw/Clawdbot agent, or any webhook forwarder) POSTs user tasks to the relay; this bridge claims jobs, answers with the host's default model, and POSTs replies back. Both sides connect outbound only.
Both modes share the same core: per-chat resident Agents, serialized turns per chat, event deduplication, exponential-backoff self-healing, group-chat @-mention gating (events mode), and reply-length capping.
Install
# 1. Put the bundle somewhere DSH can resolve it, e.g. into the runtime:
cp -R dsh-feishu ~/.dsh/runtime/node_modules/dsh-feishu
# (out-of-tree: any profile resolves bundles from the dsh install anchor,
# or expose it via ~/.dsh/profiles/node_modules/ symlink)
# 2. Create a profile ~/.dsh/profiles/feishu/package.json:
# {
# "name": "dsh-profile-feishu",
# "private": true,
# "dependencies": {},
# "dsh": { "profile": { "bundles": ["@deepseek-ai/dsh-base", "dsh-feishu"] } }
# }
# plus empty cordis.yml (`[]`) and pnpm-workspace.yaml.
# 3. Add your deployment config (see below) to
# ~/.dsh/profiles/feishu/cordis.patch.yml
# 4. Boot:
dsh --profile feishuConfigure (your profile's cordis.patch.yml)
The bundle itself is environment-agnostic — all deployment values live in your profile's user patch layer:
- id: feishu-bridge
config:
mode: events # or: relay
# --- relay mode ---
# relayBaseUrl: 'https://<your-relay-host>/openapi/relay'
# relayApiKeyEnv: DSH_FEISHU_RELAY_KEY # key read from this env var at boot
# --- shared ---
workspace: ~/.dsh/feishu-channel/workspace
mentionOnlyInGroups: true
minBackoffSeconds: 5
maxBackoffSeconds: 120
maxReplyChars: 3800The relay API key is never stored in config files — export it before boot:
export DSH_FEISHU_RELAY_KEY=... # then: dsh --profile feishuRelay server contract
relay mode needs a public HTTP endpoint implementing five routes (a reference implementation as a Miaoda/Feishu 妙搭 full_stack NestJS app is easy to assemble; auth via Authorization: Bearer <key> at the gateway):
| Route | Who calls | Purpose |
|---|---|---|
POST /ask {chat_key, text, wait_seconds} | Feishu-side bot | Submit a task, long-poll the reply |
GET /poll?wait=25 | this bridge | Claim pending jobs |
POST /reply {job_id, reply} | this bridge | Return the Agent's answer |
GET /result/{job_id} | Feishu-side bot | Re-check after an ask timeout |
GET /health | anyone | Liveness |
Events-mode prerequisites
1. A Feishu custom app with bot capability; subscribe event im.message.receive_v1; grant im:message.p2p_msg:readonly (+ send scopes for the bot). 2. lark-cli installed and authenticated (lark-cli auth login, bot identity ready). 3. Only ONE event bus may connect per app globally — stop any competing consumer (e.g. a cloud OpenClaw instance using the same app) first.
Media (v0.2, events mode)
- Inbound image/voice/video/file messages are downloaded (via
lark-cli im +messages-mget --download-resources) into <workspace>/media/ and the Agent is told the local path.
- Outbound, the Agent emits
[[fsfile:/absolute/path]]markers; the bridge
routes images as image messages, everything else as file attachments.
- Relay mode stays text-only in v0.2 (extend the relay job schema if you need
media there).
Behavior notes
- One Agent per
chat_id(events) /chat_key(relay), created lazily on
first message, memory lives for the process lifetime; sessions persist to the DSH session store (~/.dsh/sessions).
- Group chats are only answered when the message @-mentions the bot
(set mentionOnlyInGroups: false to answer everything).
- Model: whatever the host's default model selection is
(agent-default-model in ~/.dsh/settings.yaml).
License: MIT-style, do whatever you like; no warranty.