# Lark Agent Bridge

<sub>npm 包：`@bihangchi9/lark-agent-bridge` · git 仓库：`dsh-lark-bridge`（仓库名沿用，下方克隆路径不变）</sub>

> 把本地编码智能体接到**飞书 / Lark 群聊**——*一个群，一段对话，一条钉死的运行时*。可桥接 **dsh**（进程内插件）、**CLI** 智能体（`traex` / `codex`，由 daemon spawn）、**IDE** 窗口（socket attach）或**自研** agent，统一走一个网关。

[English README](./README.md)

在飞书里发一条消息，一个真正的编码智能体（带自己的工具、自己的项目目录、自己的持久对话）就在群里回你。每个群聊都是一个隔离的工作区，只钉一条运行时，所以团队可以并行跑多个项目、多个 agent，一个群一个。

---

## 它能做什么

- **飞书 ⇄ 你的智能体。** 飞书消息驱动一个活着的智能体；回复以「实时更新的飞书消息」流式返回。回的是本群钉住的那条运行时（dsh / CLI / IDE / 自研）。
- **四类宿主，一套契约。** 每条运行时都实现同一个 `AgentAdapter`：dsh 进程内当插件；CLI **spawn** `traex`/`codex`；IDE **attach** 正在跑的窗口；自研加载你自己的模块。线与线之间不互相顶替。
- **一个群，一段对话，一条运行时。** 每个 chat id 映射到一个固定目录（`<workspaceRoot>/<chatId>`）和一条用 `/agent` 选定的运行时。不同群互不干扰文件，一条消息不会广播给多个 agent。钉的运行时挂了（比如 IDE 窗口关了），这个群 fail-closed，不改绑。
- **按群持久会话。** 一个群的对话在重启后依然保留（按策略指纹门控的「恢复或新建」，`/new` 真正清空）。
- **收文件。** 文件直接发给机器人即可——bridge 下载到本群工作区的 `.attachments/<messageId>/` 目录并把路径交给智能体。限制：每条消息最多 5 个附件，图片 ≤10MB，其它文件 ≤20MB，超出会被明确拒绝；文件名自动消毒，7 天后自动清理。图片能否被识别取决于所选模型的视觉能力。
- **零配置启动。** 首次启动若没有凭证，会自动跑二维码注册向导——用飞书 App 一扫就自动连上，不用去开放平台后台一步步翻。
- **斜杠命令。** `/help`、`/new`、`/where`、`/models`、`/agent`、`/whoami` 在本群本地管理；owner 可用 `/agent`、`/model`、`/preset`、`/allow`、`/disallow`。`/agent` 把本群钉到一条已安装运行时上，断线不会改绑。

## 一张图看懂架构

```
①  飞书开放平台            ← 在这里注册机器人（自动二维码向导帮你搞定）
        │  给你: app_id + app_secret
        ▼
②  lark-agent-bridge 网关   ← 拿着钥匙，主动连飞书长连接，
        │                     把每条消息变成一个回合，
        │                     按 chat 路由到它钉住的运行时
        ▼
③  钉住的运行时            ← 四选一：
     • dsh    — 进程内 Cordis 插件（`dsh web`）
     • CLI    — daemon spawn traex / codex
     • IDE    — daemon attach 正在跑的窗口（socket）
     • 自研   — daemon 加载你自己的 AgentAdapter 模块
```

机器人**注册完全在飞书这一侧**。网关用 WebSocket **长连接**主动连飞书（所以不需要公网 IP、也不需要回调地址）。**dsh** 时网关就是 `dsh web` 加载的插件；**CLI / IDE / 自研** 时是一个**独立 daemon**（`node lib/daemon.js`），跟 dsh 宿主无关。

```bash
pnpm build
node lib/daemon.js
LARK_BRIDGE_RUNTIME=traex node lib/daemon.js
LARK_BRIDGE_IDE_SOCKET=/tmp/ide.sock node lib/daemon.js
LARK_BRIDGE_CUSTOM_ADAPTER=./examples/custom-adapter.mjs node lib/daemon.js
```

CLI **spawn** 二进制；IDE **attach** 当前用户拥有且组/其他用户不可写的 Unix socket JSONL sidecar（`chmod 600 /path/to.sock`；窗口关了这条线断）；自研加载 `AgentAdapter` 模块（见 `examples/custom-adapter.mjs`）。一个群仍然是一段对话，`/agent` 钉死，断线不改绑。

字节内部 overlay（SSO / bytecli / 扩展档位）在本地 `internal/`，已被 gitignore。不要推到这个 GitHub 仓库，走内部 skill 市场发布。

---

## 环境要求

- 一个能用 `dsh web` 启动的 **DeepSeek Harness（dsh）** 代码库。
- **Node.js** `^22.19.0 || >=24.0.0`。
- 一个 **DeepSeek API key**（设 `DEEPSEEK_API_KEY`，或配到 dsh 的凭证里）。
- 一个 **飞书账号** 用来扫码（向导会替你创建应用）。

## 安装

### 方式一：npm 包（普通用户推荐）

已发布的 npm 包包含编译后的 JavaScript、两个权限档位 preset、内置 `dsh-tool-lark-cli` 包和安装脚本。请安装在一个**稳定目录**中：注册 bundle 时 dsh 会链接到这个位置。

```bash
# macOS / Linux
mkdir -p ~/lark-agent-bridge && cd ~/lark-agent-bridge
npm init -y
npm install @bihangchi9/lark-agent-bridge
bash node_modules/@bihangchi9/lark-agent-bridge/scripts/setup.sh
```

```powershell
# Windows PowerShell
New-Item -ItemType Directory -Force -Path "$HOME\lark-agent-bridge" | Out-Null
cd "$HOME\lark-agent-bridge"
npm init -y
npm install "@bihangchi9/lark-agent-bridge"
powershell -ExecutionPolicy Bypass -File node_modules\@bihangchi9\lark-agent-bridge\scripts\setup.ps1
```

脚本会预检 Node、安装 `lark-workspace` / `lark-readonly` preset，并注册 bridge bundle 及其 `dsh-tool-lark-cli` 依赖。当 `dsh` 命令可用时，脚本直接走官方 `dsh plugin`，它会**自动初始化尚不存在的 `web` / `headless` profile**。安装完成后直接启动即可，**不需要 `--patch` 参数**：

```bash
# macOS / Linux
DSH_PERMISSION_MODE=danger-full-access dsh web

# Windows PowerShell
$env:DSH_PERMISSION_MODE = "danger-full-access"; dsh web
```

换 profile / 自定义 dsh 目录：

```bash
DSH_PROFILE=headless DSH_HOME=/path/.dsh bash node_modules/@bihangchi9/lark-agent-bridge/scripts/setup.sh
```

如果只使用独立 CLI daemon，不需要注册 dsh profile：

```bash
npx -p @bihangchi9/lark-agent-bridge lark-agent-register
npx -p @bihangchi9/lark-agent-bridge lark-agent-bridge
```

### 方式二：源码一键安装（贡献者）

> **先构建。** Git 仓库只提供 TypeScript 源码，编译产物 `lib/` 被 git 忽略——**全新 clone 没有构建产物**。插件入口是 `lib/index.js`，不构建就注册会让 dsh 拿到一个空包、**宿主加载失败**。`pnpm setup` 会替你构建。

```bash
git clone https://github.com/bihangchi9-creator/dsh-lark-bridge.git
cd dsh-lark-bridge
pnpm setup            # macOS / Linux（scripts/setup.sh）——构建 + 链接 + 注册
pnpm setup:win        # Windows（scripts/setup.ps1）
```

脚本会：预检 Node 版本 → **构建插件（构建失败会明确报错并中止）** → 安装权限档位 preset → 安装 `dsh-tool-lark-cli` → **注册 bridge bundle**。当 `dsh` 命令可用时，脚本直接走官方 `dsh plugin`，它会**自动初始化尚不存在的 `web` / `headless` profile**，无需先手动启动一次 `dsh web`。
安装完成后直接启动即可，**不需要 `--patch` 参数**：

```bash
# macOS / Linux
DSH_PERMISSION_MODE=danger-full-access dsh web

# Windows PowerShell
$env:DSH_PERMISSION_MODE = "danger-full-access"; dsh web
```

> 换 profile：`DSH_PROFILE=headless pnpm setup`；自定义 dsh 目录：`DSH_HOME=/path/.dsh pnpm setup`（Windows 同样支持这两个环境变量）。如果系统里找不到 `dsh` 命令，脚本只能走手动 fallback，此时要求目标 profile 已经初始化；若不存在，脚本会在构建和复制 preset 之前退出，不留下半安装状态。

### 方式三：从源码使用 dsh 官方命令（务必先构建！）

```bash
git clone https://github.com/bihangchi9-creator/dsh-lark-bridge.git
cd dsh-lark-bridge
pnpm install && pnpm build          # 必需——link 安装会从本目录拉取 lib/
# 然后从你的 dsh 代码库目录执行：
dsh plugin --profile web add link:/path/to/dsh-lark-bridge
```

`dsh plugin` 会在 profile 目录里执行 `pnpm add`，并**自动把声明了 `dsh.bundle` 的包加进 `dsh.profile.bundles`**。卸载/升级同样是官方命令：`dsh plugin --profile web remove @bihangchi9/lark-agent-bridge` / `dsh plugin --profile web update @bihangchi9/lark-agent-bridge`。

> ⚠️ `link:` 安装会把 profile 依赖指向**本目录**。之后若移动或删除本目录，下次 `dsh web` 无法解析 bundle 而**启动失败**。请保持 clone 位置不变，或改用方式一。

### 方式四：手动源码安装（贡献者 / 离线 fallback）

仅在 npm 安装脚本和官方 `dsh plugin` 命令都不适用时使用，需要和你的 dsh 代码库放在一起安装。

```bash
# 1. 克隆到 dsh 代码库旁边，安装并构建
git clone https://github.com/bihangchi9-creator/dsh-lark-bridge.git
cd dsh-lark-bridge
pnpm install
pnpm build            # 把 src/ 编译到 lib/（插件加载前必需）
```

然后把它注册成 dsh 的一个 **bundle**（装进 profile 即自动加载，无需 `--patch`）：

```bash
# 2. 链接进 profile 的 node_modules（bundle 解析锚点）
#    macOS / Linux：
mkdir -p ~/.dsh/profiles/web/node_modules/@bihangchi9
ln -s "$(pwd)" ~/.dsh/profiles/web/node_modules/@bihangchi9/lark-agent-bridge
#    Windows PowerShell（目录联接，无需管理员权限）：
#    New-Item -ItemType Directory -Force -Path "$env:USERPROFILE\.dsh\profiles\web\node_modules\@bihangchi9"
#    New-Item -ItemType Junction -Path "$env:USERPROFILE\.dsh\profiles\web\node_modules\@bihangchi9\lark-agent-bridge" -Target (Get-Location).Path

# 3. 在 ~/.dsh/profiles/web/package.json 的 dsh.profile.bundles 末尾加上包名：
#    "bundles": ["@deepseek-ai/dsh-base", "@deepseek-ai/dsh-web-app", "@bihangchi9/lark-agent-bridge"]
```

启动（**裸命令即可，插件随 bundle 自动加载**）：

```bash
# 在你的 dsh 代码库里
DSH_PERMISSION_MODE=danger-full-access dsh web
```

> `DSH_PERMISSION_MODE=danger-full-access` 会把智能体的审批策略设为 `never`。这是必需的，因为飞书用户没法点本地的审批弹窗。**请只在你信任的环境里使用。**

## 平台差异速查（Windows vs macOS/Linux）

| 事项 | macOS / Linux | Windows |
|---|---|---|
| npm 安装（用户） | `bash node_modules/@bihangchi9/lark-agent-bridge/scripts/setup.sh` | `powershell -ExecutionPolicy Bypass -File node_modules\@bihangchi9\lark-agent-bridge\scripts\setup.ps1` |
| 源码安装（贡献者） | `pnpm setup`（`scripts/setup.sh`） | `pnpm setup:win`（`scripts/setup.ps1`） |
| dsh 主目录 | `~/.dsh`（即 `$HOME/.dsh`） | `%USERPROFILE%\.dsh` |
| 目录链接 | `ln -s`（符号链接） | `New-Item -ItemType Junction`（目录联接，**无需管理员权限**） |
| 环境变量写法 | `DSH_PERMISSION_MODE=danger-full-access dsh web` | PowerShell：`$env:DSH_PERMISSION_MODE="danger-full-access"; dsh web`；cmd：`set DSH_PERMISSION_MODE=danger-full-access && dsh web` |
| 注册链接文件 | `~/.dsh-lark-bridge/register-url.txt` | `%USERPROFILE%\.dsh-lark-bridge\register-url.txt` |
| 后台常驻 | `launchd`（macOS）/ `systemd`（Linux） | 任务计划程序（`schtasks`） |
| 扫码注册 / 构建 / 聊天命令 | 全平台一致 | 同左 |

> 两个安装脚本均幂等。npm 包路径为：预检 → 复制 preset → 链接/注册 bundle；源码路径额外包含构建步骤。

## 首次运行：注册你的机器人

**每个人都要注册自己的飞书机器人**——你不能把 `app_secret` 给别人，那等于把机器人的控制权交出去。

首次启动、没有凭证时，插件会在终端打印**二维码**（后台运行时还会把链接写到 `~/.dsh-lark-bridge/register-url.txt`）。步骤：

1. 打开**飞书手机 App**，扫描二维码。
2. 在手机上确认创建一个自建应用。
3. 插件收到凭证，存到 `~/.dsh-lark-bridge/credentials.json`，并**自动连上飞书**。
4. 把机器人拉进一个群（或私聊它），开始对话。

想手动做 / 重新注册 / 换账号？跑独立向导：

```bash
pnpm register           # 源码仓库
npx -p @bihangchi9/lark-agent-bridge lark-agent-register   # npm 包
```

已经有凭证了？直接用环境变量跳过向导：

```bash
export LARK_APP_ID=cli_xxx
export LARK_APP_SECRET=yyy
export LARK_TENANT=feishu      # 国际版 larksuite.com 用 `lark`
```

---

## 在群里怎么用

| 命令 | 作用 |
|---|---|
| *（任意文字）* | 发给本群智能体的提示词 |
| `/help` | 显示帮助 |
| `/new` | 开一个全新会话（清空本群上下文） |
| `/where` | 显示本群的项目目录 |
| `/models` | 列出可用 provider/model |
| `/model [provider/model]` | （仅 owner）查看或切换本群模型 |
| `/preset [workspace\|read-only\|full]` | （仅 owner）查看或切换本群权限档位 |
| `/agent [id]` | 查看本群运行时；owner 可钉死（目前只有 `dsh`）。断线不会改绑 |
| `/whoami` | 显示身份、本群运行时和授权状态 |
| `/allow` | （仅 owner，群聊）在本群授权，允许成员使用机器人 |
| `/disallow` | （仅 owner，群聊）撤销本群授权 |

在**群聊**里，要 `@` 机器人才会触发（除非关掉了 mention 要求）。在**私聊**里，直接发消息即可。

## 配置

每个字段都可以来自插件 `config:` 块 **或** 环境变量（推荐用环境变量，更省事）。

| 配置项 | 环境变量 | 默认值 | 含义 |
|---|---|---|---|
| `appId` | `LARK_APP_ID` | — | 飞书 app id（`cli_...`） |
| `appSecret` | `LARK_APP_SECRET` | — | 飞书 app secret |
| `tenant` | `LARK_TENANT` | `feishu` | `feishu`(feishu.cn) 或 `lark`(larksuite.com) |
| `provider` | `DSH_LARK_PROVIDER` | dsh 默认 | LLM 提供方 |
| `model` | `DSH_LARK_MODEL` | dsh 默认 | 创建智能体用的模型 |
| `workspaceRoot` | `DSH_LARK_WORKSPACE_ROOT` | `~/dsh-lark-workspaces` | 按群文件夹的根目录 |
| `allowDm` | `DSH_LARK_ALLOW_DM` | `true` | 是否响应私聊 |
| `requireMention` | `DSH_LARK_REQUIRE_MENTION` | `true` | 群里是否必须 `@` 才触发 |
| `turnTimeoutMs` | `DSH_LARK_TURN_TIMEOUT_MS` | `600000` | 单次 agent 回合硬超时；超时会销毁卡住的会话 |
| `allowedChats` | `DSH_LARK_ALLOWED_CHATS` | `[]` | 允许使用机器人的群 chatId（逗号分隔）。**空 = 任何群都不允许（fail-closed）** |
| `allowedUsers` | `DSH_LARK_ALLOWED_USERS` | `[]` | 允许私聊使用机器人的用户 open_id（逗号分隔）。**空 = 私聊只允许 owner** |
| `accessMode` | `DSH_LARK_ACCESS_MODE` | `workspace` | 默认档位：`read-only`、`workspace` 或 `full` |
| `extraPresets` | `DSH_LARK_EXTRA_PRESETS` | `{}` | 额外 `id:preset-name` 配置 |
| `ssoGatedPresets` | `DSH_LARK_SSO_GATED_PRESETS` | `[]` | 切换及每次使用前都必须通过宿主 SSO 的档位 |
| `ssoCheckCmd` | `DSH_LARK_SSO_CHECK_CMD` | — | SSO 状态命令（argv 执行，不经过 shell） |
| `ssoOkMarker` | `DSH_LARK_SSO_OK_MARKER` | `Authenticated` | SSO 成功输出必须包含的文本 |
| `presetModels` | `DSH_LARK_PRESET_MODELS` | `{}` | `presetId:provider:model` 模型路由 |

凭证读取顺序：内联 config → 环境变量 → 注册向导写的文件。

## 访问控制（安全模型）

机器人的**安全边界 = "谁能给机器人发消息"**：每条消息都会变成一次宿主机权限的智能体回合，所以默认严格拒绝：

- **owner 永远放行**：注册时扫码的那个人就是 owner（open_id 存于 `credentials.json`）；老安装会在启动时通过应用信息 API 自动回填。
- **群聊**：只有 chatId 在 `DSH_LARK_ALLOWED_CHATS` 里的群可以用。
- **私聊**：只有 open_id 在 `DSH_LARK_ALLOWED_USERS` 里的用户可以用（owner 除外）。
- **fail-closed**：owner 未知且白名单为空时，**所有消息都被拒绝**，拒绝回复里会带上 chatId 方便你配置。

配置示例：

```bash
# 允许群 oc_xxx1、oc_xxx2，允许用户 ou_friend 私聊
export DSH_LARK_ALLOWED_CHATS="oc_xxx1,oc_xxx2"
export DSH_LARK_ALLOWED_USERS="ou_friend"
```

> 需要 `application-info` 权限才能运行时解析 owner；注册向导直接捕获 open_id，通常不需要额外配置。**强烈建议任何暴露给团队以外的人使用的部署都配置白名单。**

## 权限档位（爆炸半径）

即使消息通过了授权门，agent 能碰到什么仍然按档位收敛（`DSH_LARK_ACCESS_MODE`，默认 `workspace`）：

| 档位 | preset | agent 能做什么 |
|---|---|---|
| `read-only` | `lark-readonly` | 只能搜索/读取文件——不能写、不能执行、不能联网 |
| `workspace`（默认） | `lark-workspace` | 读写/编辑文件；**没有 shell、没有网络、没有子代理**（不可执行任意代码）。自带 `lark_cli` 工具：通过宿主已授权的 `lark-cli` 操作飞书（IM、文档、表格、日历……），以 argv 数组 spawn、带超时和输出上限——有飞书能力，但不开放 shell |
| `full` | 部署默认 | 宿主提供的全部能力（含 bash、网络、子代理） |

preset 是 dsh 的"工具集组合"概念：宿主沙箱对所有 preset 一致，档位的可执行差异 = **哪些工具存在**。工作区档把攻击面的皇冠（任意代码执行 + 网络出口 + 委托）整个拿掉。

安装 preset：`pnpm setup` / `pnpm setup:win` 会自动把它们装进 dsh 的 harness-home 用户根目录。手动安装（发现无缓存）：

```bash
# 把项目里的 presets/ 装进 dsh 的 preset 根
mkdir -p ~/.dsh/.agent-presets
cp -r presets/lark-workspace presets/lark-readonly ~/.dsh/.agent-presets/
```

> 进一步的收敛（宿主级）：dsh 的权限预设 `workspace-write`（沙箱=工作区内写 + 越界需审批）可以让 fs 写入硬性限制在工作区内——但该模式对远程用户是"越界即拒绝"（审批弹窗无人点），且会改变 bash 行为，启用前需在目标部署验证。当前插件层档位已经移除 bash/web/子代理，是收益/风险比最高的部分。

---

## 与 lark-cli 搭配使用

如果你已经在用 [`lark-cli`](https://github.com/larksuite/cli) / Lark 系列 skill 来操作飞书（文档、表格、IM、日历……），本插件正好和它互补：继续用 `lark-cli` 做结构化的飞书操作，让 **Lark Agent Bridge** 做那个「住在群聊里的对话式编码智能体」。**非常欢迎把两者结合起来用**——比如在群里让智能体起草内容，再用 `lark-cli` 的 skill 把它推进飞书文档。

## 排障

- **机器人不吭声 /「(no output)」** —— 确认模型能被解析（dsh 的默认模型服务要配好，或设 `DSH_LARK_MODEL`）。
- **「missing Feishu credentials」** —— 向导没走完；重新跑 `pnpm register`，或导出 `LARK_APP_ID` / `LARK_APP_SECRET`。
- **看不到二维码（dsh 在后台跑）** —— 用浏览器打开 `~/.dsh-lark-bridge/register-url.txt` 里的链接。
- **群消息被忽略** —— 需要 `@` 机器人，或设 `DSH_LARK_REQUIRE_MENTION=false`。

## 致谢

**Lark Agent Bridge**（npm `@bihangchi9/lark-agent-bridge`，仓库 `dsh-lark-bridge`）是对 [zarazhangrui](https://github.com/zarazhangrui) 的 [lark-coding-agent-bridge](https://github.com/zarazhangrui/lark-coding-agent-bridge)（最初名为 `feishu-claude-code-bridge`）的二创，经由 [trae-to-lark](https://github.com/bihangchi9-creator/trae-to-lark) 演化而来。本项目是一个 dsh 原生插件的从零重写。所有原始工作仍遵循其 MIT 许可；完整的版权链见 [LICENSE](./LICENSE) 与 [NOTICE](./NOTICE)。

## 许可

[MIT](./LICENSE)
