Skip to content
dsh.fish
Bundle

dsh-feishu-remote

Control your DeepSeek Harness agent from Feishu/Lark: send tasks via DM, receive results back, approve tool calls from mobile cards. Powered by lark-cli.

Source
ShiXiangYu2
stars
1 stars
License
MIT
Updated
Updated 14 days ago

Readme

# ๐Ÿ“ฑ DSH Feishu Remote

> Control your [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) agent from **Feishu / Lark** on your phone. Send a task by DM โ€” get the result back in chat. **Fully working closed loop.**

[![dsh-plugin](https://img.shields.io/badge/dsh-plugin-4d7fff)](https://github.com/topics/dsh-plugin) [![DeepSeek Harness](https://img.shields.io/badge/deepseek--harness-plugin-4d7fff)](https://github.com/deepseek-ai/deepseek-harness) [![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)

## โœจ What it does

- ๐Ÿ“จ **Feishu โ†’ Agent โ†’ Feishu**: DM your bot a task (e.g. `2+3็ญ‰ไบŽๅ‡ ๏ผŸ`), a DSH agent runs it with your configured LLM, and the **answer comes back to the chat**.
- ๐Ÿ–ผ๏ธ **Image understanding**: send a screenshot or photo โ€” it's downloaded and analyzed by a vision model (Qwen3-VL), then the agent replies with what it sees.
- ๐ŸŽจ **Image generation round-trip**: the agent can generate an image (`generate_image`), download it locally (`feishu_download`), and send the actual image back to your Feishu chat (`feishu_send_image`) โ€” not just a link.
- ๐Ÿ“ค **Agent โ†’ Feishu**: the model gets `feishu_send` / `feishu_send_image` / `feishu_download` tools to push results, files, and generated images to any user or chat.
- ๐Ÿงน **Retention cleanup**: downloaded images are kept for 7 days (configurable via `FEISHU_IMG_RETENTION_DAYS`), then auto-deleted โ€” no unbounded disk growth.
- ๐Ÿค– **Long connection**: uses `lark-cli`'s WebSocket event bus โ€” **no public webhook server needed**, works on localhost/LAN/private servers.
- ๐Ÿ”’ **Secure**: reuses `lark-cli`'s OS-keychain credential storage and permission system; event listener runs unsandboxed by design (it must hold the WebSocket).

## ๐Ÿš€ Install

### 0. Prerequisites

1. A Feishu/Lark **self-built app** with:
   - **Bot** ability enabled
   - Event subscription `im.message.receive_v1` (**long-connection** mode)
   - Permissions: `im:message`, `im:message:send_as_bot`, `im:message.p2p_msg:readonly`, `im:chat:read`, `im:resource`
   - A published version
   - (Setup in the [Feishu developer console](https://open.feishu.cn/app) โ€” the CLI can enable the bot ability via API, but events/permissions need the console.)

2. `lark-cli` installed & authenticated once:

```sh
npm i -g @larksuite/cli
lark-cli config init            # paste your App ID / Secret
lark-cli auth login --recommend # scan QR to authorize
```

### 1. Install the plugin

```sh
dsh plugin --profile demo add github:ShiXiangYu2/dsh-feishu-remote
```

The bundle contains two pieces:
- `index.js` โ€” the Cordis plugin: registers the `feishu_send` model tool and attempts an in-process event listener.
- `feishu-resident.mjs` โ€” the **recommended resident launcher**: boots the web profile and runs the long-connection event loop in a detached process (see below).

### 2. Configure the model

The DSH profile must have a working LLM route (e.g. DeepSeek via SiliconFlow):

```yaml
# profile cordis.patch.yml
- id: llm-deepseek
  config:
    apiKeyEnv: SILICONFLOW_API_KEY
    baseURL: https://api.siliconflow.cn/v1
    thinking: disabled
    reasoningEffort: off
    models:
      - id: deepseek-ai/DeepSeek-V3.2
        name: DeepSeek-V3.2 (via SiliconFlow)
        contextWindow: 65536
        maxTokens: 8192

- id: agent-default-model
  config:
    provider: deepseek-official
    model: deepseek-ai/DeepSeek-V3.2
```

### 3. Run the resident (recommended)

The closed loop must live in a **long-lived process**. Use the included resident launcher:

```sh
# Adjust the absolute paths in feishu-resident.mjs (LARK_HOME, CLI) to your setup.
DSH_HOME=~/.dsh SILICONFLOW_API_KEY=sk-... \
  node --import tsx/esm feishu-resident.mjs
```

It boots the `web` profile, spawns `lark-cli event consume` as a detached process (holding stdin open via a `tail -f /dev/null` pipe so the listener never exits on EOF), and for each inbound DM: **ack โ†’ create agent โ†’ run task โ†’ extract final text โ†’ reply**.

### 4. Use it

DM your Feishu bot anything, e.g. `ๅธฎๆˆ‘ๆ€ป็ป“ไธ€ไธ‹ ~/projects ็š„ README` โ€” the agent runs and the result comes back to the chat.

## ๐Ÿ›  Tools

| Tool | Description |
|---|---|
| `feishu_send` | Model-facing: send a message to a Feishu user (`ou_`) or chat (`oc_`). |
| `feishu_send_image` | Model-facing: send a local image file to a Feishu user or chat. |
| `feishu_download` | Model-facing: download a URL to a local file (so generated images can be sent via `feishu_send_image`). |

## ๐Ÿ”Œ How it works

```
Feishu DM โ”€โ”€โ–บ lark-cli event consume (WebSocket long-connection, detached process)
                  โ”‚  NDJSON event on stdout
                  โ–ผ
        feishu-resident.mjs (long-lived process)
                  โ”‚  image? โ†’ download (messages-resources-download)
                  โ”‚          โ†’ vision describe (Qwen3-VL via SiliconFlow)
                  โ”‚  agents.create + followup(task) + whenIdle()
                  โ–ผ
        final assistant text (ev.data.message.content)
                  โ”‚  lark-cli im +messages-send
                  โ–ผ
        Feishu chat reply
```

### Image handling

- The event content for an image arrives as `[Image: img_v3_xxx]`.
- The resident detects that pattern, downloads the resource via
  `lark-cli im +messages-resources-download` (using the event's real `message_id`
  + the `image_key`), then asks a SiliconFlow vision model
  (`Qwen/Qwen3-VL-8B-Instruct`, overridable with `FEISHU_VISION_MODEL`) to
  describe the picture.
- The description is prepended to the user's message and fed to the DSH agent,
  so the agent can reason about the image and reply in Feishu.
- Downloaded images land in the `IMG_DIR` (`/root/dsh /feishu-images` by
  default; the download command requires a **relative** `--output` path, so the
  resident `cd`s into that directory first).

## โš ๏ธ Notes

- **Why a resident process?** DSH's shell service binds background processes to the calling plugin fiber; a listener started inside a plugin's `apply()` is killed when the fiber settles. The resident launcher owns the listener in its own process, so it survives.
- **Text extraction**: the final answer is read from the session log's `assistant/message` events (`ev.data.message.content`, mirroring the official headless `summarize()`).
- Long tasks: replies are truncated to the final text block; very long runs may exceed Feishu message limits.
- lark-cli event output streams as NDJSON on **stdout** (stderr carries `[event]` log lines) โ€” both are parsed.

## ๐Ÿ“„ License

MIT

Install

dsh plugin --profile web add github:ShiXiangYu2/dsh-feishu-remote

Profile: web

  • This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.
Source