๐๏ธ dsh-lark-bridge
Run a full DeepSeek Harness coding agent inside Feishu / Lark
Native thinking process, approval cards, live goal/todo cards, subagent fan-out,
bilingual slash panel โ no public webhook URL needed.
ไธญๆ ยท Quick Start ยท Features ยท Slash Commands ยท Configuration ยท Architecture ยท Development
---
## What is this?
`dsh-lark-bridge` is a **Feishu/Lark IM channel for DeepSeek Harness** โ a plugin that makes
your coding agent work right inside a chat. Each conversation (DM or group) drives its own
dsh agent with:
- **Native thinking process** โ model reasoning renders as Feishu's own "thinking" message,
tool calls with icons, results as code blocks. No black box.
- **Interactive approval cards** โ operations needing confirmation become clickable
cards (Allow once / Deny), with the decider written back.
- **Live lifecycle reactions** โ `OK` โ `THINKING` โ `DONE` / `ERROR` on every message.
- **Live goal & todo cards** โ goal phase changes and todo snapshots update a card in the
chat, so a long-running task is never a silent gap.
- **WebSocket long connection** โ no public callback URL, no reverse proxy.
Feishu is the carrier; the work is still done by DeepSeek Harness itself.
## โจ Features
| | |
|---|---|
| ๐ง **Native thinking process** | `cot` renders reasoning as Feishu's native thinking message; older clients fall back to `stream` typewriter card |
| โ
**Live reactions** | `OK` โ `THINKING` โ `DONE`/`ERROR`, states replace each other, configurable |
| ๐๏ธ **One agent per conversation** | `sessionScope`: whole chat / topic thread / single sender; sessions persist across restarts |
| ๐ **Approval cards** | Host approval questions render as Allow-once / Deny cards; decision + decider written back |
| ๐ฏ **Goal cards** | Live goal phase (active/paused/blocked/complete) updates a card; `/goal` works, `autoResumeGoals` re-arms after restarts |
| โ
**Todo cards** | `todo_write` snapshots update a live card in the chat |
| ๐ง๐ป **Subagent fan-out** | Workflow runs stream as text lines: run start, child open, child end, run end |
| ๐ฆ **Compaction transparency** | "Compactingโฆ" โ summary text + released tokens; prunes report trimmed count |
| โฐ **Scheduled reminders** | `schedule_create/list/delete` tools + `/schedules` view (compose `@deepseek-ai/dsh-schedule` yourself; the plugin ships the full listener) |
| ๐ **Session history search** | `/sessions ` full-text searches this chat's stored history with snippets |
| โ๏ธ **Background job notifications** | `run_in_background` jobs and direct subagents announce their terminal outcome in the chat |
| โก **Full slash panel** | `/stop /help /preset /sessions /tools /schedules /jobs /feedback /context /audit /config` plus host commands (`goal`, `plan`, `compact`, `permission`) |
| ๐ **Bilingual commands** | Panel and `/help` follow the platform: English on international Lark, Chinese on domestic Feishu; `locale` overrides |
| ๐ผ๏ธ **Image input (opt-in)** | `attachImages` downloads chat images into the host attachment store |
| ๐ **File delivery** | Agent `send_file` delivers files with caption into the chat |
| ๐ **QR onboarding** | First boot prints a QR code; scanning creates the Feishu app (event subscription included), credentials persist |
| ๐ **Authorization narrowing** | `senderAllowlist` / `groupAllowlist` / `approvers` narrow further than the app's visibility scope |
| ๐งฉ **Deep dsh adaptation** | Everything goes through host service contracts: `agents`, `agentPresets`, `agentDefaultModel`, `settings`, `workspaceRegistry`, `loader`, `invariants`, `approval`, `goals` โ self-contained, no host source needed |
## ๐ Quick Start
```sh
npx @deepseek-ai/dsh plugin --profile web add github:moyu-good/dsh-lark-bridge \
&& npx @deepseek-ai/dsh web
```
The console prints a QR code โ scan with Feishu to create the app โ fill in your
DeepSeek API Key in Settings โ Models โ DM the bot or @ it in a group.
> Already using `dsh`? Drop the `npx @deepseek-ai/` prefix.
The package ships **prebuilt** (`lib/` is committed) โ no build step on install.
A `prepare` hook rebuilds automatically only when the compiled output is missing
(e.g. a source clone without the committed output).
## ๐ฌ Slash Commands
| Command | Description |
|---|---|
| `/stop` | Cancel the running turn |
| `/help` | Show this listing |
| `/preset` | View / switch agent preset (standard / code / minimal / cordis) |
| `/sessions` | List this chat's session history |
| `/tools` | View / deny / allow tools at runtime |
| `/schedules` | View this chat's scheduled reminders |
| `/jobs` | View this chat's background jobs |
| `/audit` | Operation audit summary for the session |
| `/context` | View current context token pressure |
| `/config` | View the bridge's live configuration |
| `/goal` | View / set the goal (host) |
| `/plan` | Enter / leave plan mode (host) |
| `/compact` | Compact older history (host) |
| `/feedback` | Rate the last answer: `positive`/`negative` + optional note |
| `/permission` | Switch permission preset (host) |
Panel descriptions are bilingual: **English** when the platform domain is
`open.larksuite.com` (international Lark), **Chinese** for `open.feishu.cn`
(domestic Feishu). Set `locale: zh|en` to force one.
## vs. other Feishu/Lark bridges
| Capability | **dsh-lark-bridge** | xmanrui/dsh-im | omdsh-dev/dsh-lark | AX1202/ax-feishu-bridge |
|---|---|---|---|---|
| Positioning | Deep Harness channel | Multi-platform gateway | Scan-to-use | Pi + DSH dual bridge |
| Native thinking process (Feishu CoT) | โ
| โ | โ | โ |
| Approval cards + decider trail | โ
| โ | โ | remote approve |
| Live goal/todo cards | โ
| โ | โ | โ |
| Workflow fan-out + phase/log lines | โ
| โ | โ | โ |
| Compaction transparency | โ
| โ | โ | โ |
| Goal auto-resume after restart | โ
| โ | crash-safe | โ |
| Bilingual slash panel sync | โ
| โ | โ | panel buttons |
## โ๏ธ Configuration
| Field | Default | Meaning |
|---|---|---|
| `appId`, `appSecret` | first-boot QR registration | Feishu/Lark app credentials |
| `domain` | Feishu | Open-platform domain; Lark: `https://open.larksuite.com` |
| `locale` | `auto` | Command language: `auto` (Larkโen, Feishuโzh) / `zh` / `en` |
| `cwd` | host process cwd | Absolute workspace directory for chat agents |
| `provider`, `model` | host `agentDefaultModel` | Model routing for chat agents |
| `preset` | roster default | Agent preset chat agents join |
| `sessionScope` | `chat` | `chat` / `chat-thread` / `chat-sender` |
| `output` | `cot` | `cot` (native thinking process) or `stream` (typewriter card) |
| `showProcess` | `true` | Show reasoning and tool calls |
| `reactionFeedback` | `true` | Live reaction feedback |
| `hideProcessWhenDone` | `false` | Hide finished process (`cot` only) |
| `attachImages` | `false` | Pass chat images to the model |
| `syncSlashCommands` | `true` | Publish commands to bot's `/` panel (reconciles: creates missing, removes stale, refreshes drifted descriptions) |
| `autoResumeGoals` | `false` | Re-arm an active goal when a session returns after a restart |
| `approvalReminderMs` | `0` | Nudge the chat when an approval card is unanswered this many ms (0 = off) |
| `denyTools` | `[]` | Tools chat agents may not call |
| `requireMention` | `true` | In groups, only respond when @-mentioned |
| `senderAllowlist` | `[]` | Open ids allowed to DM |
| `groupAllowlist` | `[]` | Only these `oc_โฆ` group chats when non-empty |
| `approvers` | `[]` | Open ids allowed to answer approvals |
| `outbound.allowedFileDirs` | unset โ file sending disabled | Directories `send_file` may read **local** paths from. Required for delivering generated artifacts (HTML reports, screenshots, documents). Example: `outbound: { allowedFileDirs: ['/home/user/work'] }` |
> โ ๏ธ **File delivery is default-deny.** Without `outbound.allowedFileDirs`,
> `send_file` with a local path fails with
> `local file source requires outbound.allowedFileDirs to be configured` โ
> the agent appears to send, nothing arrives. URLs and raw buffers always work.
Credentials resolve in three layers, later wins: bundle patch config โ settings
document plugin section โ first-boot QR registration.
## ๐ Required app permissions
A **newly created** Feishu app needs these scopes published before the panel
and messaging work. The QR onboarding flow grants them automatically; a
manually created app must add them in Developer Console โ Permissions, then
**create and publish a version** (scopes added after the last publish are not
visible to the API until a new version ships):
| Scope | Needed for |
|---|---|
| `application:app_slash_command` (read + write) | Slash command panel โ without it, `syncSlashPanel` fails with `99991672` and the `/` list stays empty |
| `im:message` | Send and receive messages |
| `im:message:readonly` | Read message content |
| `im:message.receive_v1` event | Receive message events (Events & Callbacks โ long connection) |
| `im:resource` | Upload / send images and files |
| `im:chat:read` | Group chat info (group scenarios) |
| `im:message.reactions:read` / `write_only` | Live reaction feedback |
Debug with the API directly โ the console page shows **granted**, the API shows
what the **published version** carries:
```sh
# 1. token
curl -s -X POST https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal \
-H 'Content-Type: application/json' \
-d "{\"app_id\":\"$APP_ID\",\"app_secret\":\"$APP_SECRET\"}" | jq -r .tenant_access_token
# 2. slash commands (should list your commands after sync)
curl -s "https://open.feishu.cn/open-apis/application/v7/app_slash_commands?page_size=50" \
-H "Authorization: Bearer $TOKEN"
# 3. published scopes (check application:app_slash_command is present)
curl -s "https://open.feishu.cn/open-apis/application/v6/applications/$APP_ID/app_versions?lang=zh_cn" \
-H "Authorization: Bearer $TOKEN"
```
The slash panel sync runs on session create/resume โ after granting the scope,
send the bot one message to trigger it.
## ๐งญ Architecture
```
Feishu / Lark โโ WebSocket ้ฟ่ฟๆฅ โโโบ dsh-lark-bridge (dsh ่ฟ็จๅ
็ feishu-channel ๆไปถ)
(่ๅคฉ/ๅฎกๆน/ๅพ็) โ
โผ
host ๆๅกๅฅ็บฆ: agents / sessions / tools / approval /
goal / workspace / settings / commands
โ
โผ
DeepSeek Harness ๆฌไฝ
```
The bridge runs **inside the dsh process** as the `feishu-channel` plugin โ it is
not a separate server. `npx @deepseek-ai/dsh web` (or `--profile chat`) boots dsh
with this plugin composed; the plugin opens the WebSocket long connection and
drives everything from there. Any launcher (shell script, systemd, supervisor)
can host it; it has no dependency on any other agent framework.
## ๐ ๏ธ Development
```sh
pnpm install
pnpm run build # clean + tsc + tsdown (emits into lib/, committed)
pnpm test # vitest (272 tests)
node plugin-contract-test.mjs # standalone contract tests
```
The repo is self-contained: only published packages
(`@deepseek-ai/cordis`, `@deepseek-ai/schemastery`, `@larksuite/channel`) are
dependencies, no host source needed.
**Packaging notes** (why `lib/` is committed):
- Git-dependency installs (`github:user/repo`) never ran a build, and without a
committed `lib/` the plugin failed at boot with `ERR_MODULE_NOT_FOUND` โ fixed
by committing the compiled output.
- The `prepare` hook is a safety net for source clones: it exits immediately
when `lib/` exists and only rebuilds when it is genuinely missing.
- `build` cleans `lib/` first (tsdown itself runs `clean: false` because its
entry points live inside the output dir).
## ๐ Known limitations
- Configuration is read once at startup; changes need a restart
- Events during a long-connection outage are not replayed (transport has no cursor)
- The Feishu app must use **long-connection** event subscription (self-built app);
webhook mode receives no events
- `schedule_create/list/delete` tools require composing `@deepseek-ai/dsh-schedule`
in your dsh profile (the bridge already listens for `schedule/change` and
renders `/schedules`; the tools are the model-side half)
## ๐ License
BSD-3-Clause. Architecture inspired by [dsh-lark](https://github.com/Roy-oss1/dsh-lark) (also BSD-3-Clause).