npm package: @openduo/channel-feishu
Description: Feishu / Lark channel adapter for [duoduo](https://www.npmjs.com/package/@openduo/duoduo).
Latest version: 0.2.14

# @openduo/channel-feishu

Feishu / Lark channel adapter for [duoduo](https://www.npmjs.com/package/@openduo/duoduo).

Connects duoduo to Feishu via the Feishu Open Platform WebSocket long-poll API. Requires no public URL — the gateway initiates the connection outbound.

> The Feishu messaging layer in this package was written by duoduo itself, referencing [openclaw](https://github.com/openclaw/openclaw) as prior art (with respect). The implementation was designed from scratch to fit duoduo's channel protocol — session routing, media bridging, delivery cursors, and gateway command handling are native to this runtime.

## How It Works

The Feishu gateway is an independent process that sits between the Feishu Open Platform and the duoduo daemon. It translates Feishu events into the duoduo standard protocol and vice versa.

**Inbound**: Feishu messages arrive via WebSocket event subscription. The gateway handles deduplication, access policy, media download, @mention normalization, and dispatch to the daemon.

**Outbound**: The gateway maintains a pull subscription per active chat. When the daemon produces output, the gateway renders it (plain text or interactive card), handles file upload, and sends it back to Feishu.

**Media bridging**: Images, files, audio, and video are downloaded from Feishu and uploaded to the daemon's file store before ingress. On egress, daemon-produced files are downloaded and re-uploaded to Feishu.

**Typing indicators**: Feishu has no native typing API. The gateway uses emoji reactions as staged presence signals (received → thinking → working → typing), cleaned up after each response.

**Access control**: Configurable DM policy (open, allowlist) and group policy (open, allowlist, disabled). Groups can require a bot @mention before the message is forwarded to the daemon.

**Commands**: Gateway control commands (`/status`, `/cancel`, `/cd`, `/clear`, `/stats`) are handled directly without routing through the agent session.

## Requirements

- Node.js 20+
- A running duoduo daemon (`@openduo/duoduo`)
- A Feishu app with WebSocket long-poll enabled

## Installation

### Via duoduo CLI (recommended)

```bash
duoduo channel install @openduo/channel-feishu
```

### Manual

```bash
npm install -g @openduo/channel-feishu
```

## Feishu App Setup

In the Feishu Developer Console:

1. Create an app (or use an existing one).
2. Enable **WebSocket long-poll** under Events & Callbacks → Connection mode.
3. Subscribe to the `im.message.receive_v1` event.
4. Grant the following permissions:

| Permission                         | Purpose                                 |
| ---------------------------------- | --------------------------------------- |
| `im:message`                       | Send and receive messages               |
| `im:message.p2p_msg:readonly`      | Receive direct messages                 |
| `im:message.group_at_msg:readonly` | Receive @bot messages in groups         |
| `im:message:send_as_bot`           | Send messages as bot                    |
| `im:resource`                      | Upload and download media               |
| `contact:user.base:readonly`       | Resolve sender display names (optional) |

## Configuration

All configuration is via environment variables:

| Variable                 | Default                  | Description                                      |
| ------------------------ | ------------------------ | ------------------------------------------------ |
| `FEISHU_APP_ID`          | —                        | **Required.** Feishu app ID                      |
| `FEISHU_APP_SECRET`      | —                        | **Required.** Feishu app secret                  |
| `FEISHU_DOMAIN`          | `feishu`                 | `feishu`, `lark`, or custom domain URL           |
| `ALADUO_DAEMON_URL`      | `http://127.0.0.1:20233` | duoduo daemon address                            |
| `FEISHU_DM_POLICY`       | `open`                   | DM access: `open` or `allowlist`                 |
| `FEISHU_GROUP_POLICY`    | `allowlist`              | Group access: `open`, `allowlist`, or `disabled` |
| `FEISHU_REQUIRE_MENTION` | `true`                   | Require @bot mention in groups                   |
| `FEISHU_ALLOW_FROM`      | —                        | Comma-separated open_id allowlist                |
| `FEISHU_ALLOW_GROUPS`    | —                        | Comma-separated chat_id allowlist                |
| `FEISHU_RENDER_MODE`     | `auto`                   | Output rendering: `auto`, `raw`, or `card`       |
| `FEISHU_BOT_OPEN_ID`     | —                        | Bot's own open_id (for mention detection)        |
| `FEISHU_LOG_LEVEL`       | `info`                   | Log level                                        |

For `FEISHU_GROUP_POLICY=allowlist`, `FEISHU_ALLOW_FROM` and `FEISHU_ALLOW_GROUPS` are peer entity allowlists:

- A listed `open_id` may talk to the bot in any group.
- A listed `chat_id` allows any member of that group to talk to the bot.
- If neither list matches, the group message is denied.

**Render modes:**

- `auto` — plain text by default; switches to interactive card when output contains code blocks or tables.
- `raw` — always plain text.
- `card` — always interactive card.

## Usage

### Via duoduo CLI

```bash
# Start (managed by duoduo)
duoduo channel feishu start

# Check status
duoduo channel feishu status

# View logs
duoduo channel feishu logs

# Stop
duoduo channel feishu stop
```

In host mode, `duoduo channel feishu start` auto-loads `~/.config/duoduo/.env` before launching
the managed channel process. Put `FEISHU_APP_ID`, `FEISHU_APP_SECRET`, and optional
`ALADUO_DAEMON_URL` there if you want daemon and channel startup to behave consistently.

### Standalone

```bash
FEISHU_APP_ID=cli_xxx \
FEISHU_APP_SECRET=xxx \
ALADUO_DAEMON_URL=http://127.0.0.1:20233 \
duoduo-feishu
```

When you launch `duoduo-feishu` directly in host mode, the process logs a warning that
`~/.config/duoduo/.env` is not auto-loaded. That warning is intentional: direct standalone
launch expects you to export the environment yourself.

## License

MIT