dsh-plugin-lark
August 30, 2026 · View on GitHub
在 DSH Web 界面(设置 → 插件 →「飞书接入」标签页)提供扫码新建机器人 + 扫码授权 + 监听器管理, 不再需要手动敲 lark-cli 命令,也不用去开放平台手动配置。
各大能力
- 新建飞书机器人(推荐):面板点「新建机器人」→ 手机扫码登录开放平台 → 工具自动完成 创建应用 → 开机器人能力 → 导入权限 → 订阅消息事件 → 发布版本 → 约 30-60 秒拿到新机器人凭证并自动写入 lark-cli → 自动启动监听器 → 直接可用(一次扫码即连接)。 底层用 feishu-bot-bootstrap(headless + events-jsonl 事件流)。
- 用户身份授权(可选):需要以个人身份访问日历/文档/邮件等资源时再扫码授权(lark-cli 设备流)。
⭐ 核心功能(v0.2)
- 官方 SDK 长连接接收:网关用飞书官方
lark-oapi的 WebSocket 长连接(FeishuWSClient) 接收消息与卡片回调,替代 lark-cli consume(lark-cli 收不到 card.action.trigger)。 参照 hermes-agent 的实现(EventDispatcherHandler+ 事件循环设置)。 - 飞书审批菜单(点卡片按钮):DSH 要执行高危命令(删除文件/目录、sudo、launchctl、kill 等) 时,飞书弹出带按钮的审批卡片(✅ 批准 / 🚫 拒绝),你点一下即完成审批——批准才执行,拒绝即中止。 也支持文字审批:回复「批准 xxxx」/「拒绝 xxxx」(xxxx 为 4 位验证码)。
- 审批门(headless 拦截):headless 任务里命中高危规则(
feishu-approval.json)的命令 在执行前被拦截,走飞书审批;审批通过后跳过 DSH 安全钩子(security-suite)放行。 - ps 替代 shim:Seatbelt 沙箱内
/bin/psexec 被 macOS 拒(与策略无关), 提供shims/ps(libproc 枚举),agent 敲ps即得进程列表(PID/PPID/USER/COMMAND)。
安装(npm)
npm install -g dsh-plugin-lark # 或按 dsh 插件方式
dsh plugin --profile web add dsh-plugin-lark
依赖:lark-cli(PATH 中可用)、dsh、Chrome/Edge(扫码新建机器人用)、飞书开放平台账号、
lark-oapi(网关长连接,manage_lark_bridge.sh start 自动 pip 安装)。
环境变量(均可覆盖,默认已通用化):
| 变量 | 默认 | 说明 |
|---|---|---|
LARK_BRIDGE_DIR | ~/.dsh/lark-bridge | 网关目录(内含 lark_bridge.py + manage_lark_bridge.sh,随包自带) |
DSH_WORK_DIR | ~ | 任务工作目录(网关侧) |
DSH_BIN | dsh(PATH) | dsh 可执行(绝对路径或 PATH 名) |
LARK_CLI_BIN | lark-cli(PATH) | lark-cli 路径 |
LARK_APP_JSON | ~/.dsh/feishu-app.json | DSH 飞书应用凭据文件路径 |
LARK_BOT_NAME | DeepSeek Harness 助手 | 新建机器人的应用名 |
架构(薄插件设计 + 网关增强)
DSH Web 面板(本插件前端)
│ fetch /api/lark/*
▼
本插件服务端(跑在 dsh web 进程内)
│ ① feishu-bot-bootstrap(扫码新建机器人) ② lark-cli 设备流 ③ 启停/状态/日志 转发
▼
lark_bridge.py(独立网关进程,业务收发+审批全在这)
│ ① lark-oapi FeishuWSClient 长连接(接收消息+卡片回调,SDK 自动回 200)
│ ② 审批服务(本地 HTTP /approval)+ 审批卡片发送 + pending 管理
│ ③ dsh headless 执行高危命令前 → 审批门拦截 → 飞书审批
▼
飞书
- 插件只做「授权 + 面板 + 进程管理」,业务收发+审批在独立
lark_bridge.py,与 DSH 核心解耦 - 发包自包含
bridge/:升级版网关 + 配置模板 + 依赖 + ps shim + headless 审批门
文件
| 文件 | 说明 |
|---|---|
package.json | 插件包声明;dsh.client 声明前端 bundle |
lib/index.js | 服务端 Cordis 插件:/api/lark/* 路由 |
lib/client.js | 前端面板(手写 ModuleLoader bundle 格式) |
bridge/lark_bridge.py | 升级版网关:lark-oapi 长连接 + 审批门 + 卡片/文字审批 |
bridge/manage_lark_bridge.sh | 网关启停/状态/日志(自动装 lark-oapi、提示配置) |
bridge/feishu-app.json.example | DSH 飞书应用凭据模板(复制为 ~/.dsh/feishu-app.json) |
bridge/feishu-approval.json | 审批规则(高危命令模式 + 中文原因) |
bridge/requirements.txt | 网关依赖(lark-oapi) |
bridge/shims/ps | 沙箱 ps 替代(libproc) |
headless-approval/ | 审批门插件(dsh-feishu-approval):headless 高危命令审批 |
test/harness.mjs | 后端自测(mock ctx,不启动 Web) |
审批流程(高危命令)
用户发任务 → headless 执行 → 命中高危规则(feishu-approval.json)
→ 审批门拦截 → 网关发审批卡片(获批/拒按钮 + 文字兜底)
→ 用户点按钮 / 回复「批准 xxxx」「拒绝 xxxx」
→ 批准 → 跳过安检 → 命令执行;拒绝 → 命令中止,agent 告知用户
审批规则(feishu-approval.json,默认):
rm删除文件/目录(不可逆,含普通 rm 与 rm -rf)sudo提权、launchctl服务管理、kill -9/killall/pkill进程结束diskutil、shutdown/reboot/halt、dd写盘、mkfs格式化
注:
sudo审批通过后,命令仍可能被 macOS Seatbelt 沙箱拒绝(防提权硬边界); 审批门给的是「可确认」通道,OS 沙箱兜底禁提权。
headless 审批门集成(飞书链路自动生效)
headless-approval/ 是审批门插件(cordis)。飞书 headless 任务要启用审批,需让 headless
profile 加载它。示例(~/.dsh/profiles/headless/package.json):
"dependencies": { "dsh-feishu-approval": "file:<dsh-plugin-lark>/headless-approval" },
"dsh": { "profile": { "bundles": [ "...", "dsh-feishu-approval" ] } }
网关启动时通过 DSH_APPROVAL_ENDPOINT 环境变量把审批服务地址注入 headless 进程,
审批门插件据此把高危命令转到飞书审批。审批规则读 ~/.dsh/feishu-approval.json。
配置(网关)
- 飞书应用凭据:
cp bridge/feishu-app.json.example ~/.dsh/feishu-app.json,填入 DSH bot 应用的App ID/App Secret(飞书开放平台 → 应用 → 凭证与基础信息)。 - 审批规则(可选):
cp bridge/feishu-approval.json ~/.dsh/feishu-approval.json, 按需增删高危模式。
⚠️ 关键:DSH 与 hermes 是两个不同的飞书应用
- DSH bot 的 App ID 是你单聊里的机器人(
cli_开头); - 不要用 hermes 的 App(
~/.hermes/.env里的FEISHU_APP_ID)。 混用会导致:发消息230002 Bot/User can NOT be out of the chat、后台订阅错位、 长连接冲突(一个应用同一时间只允许一条连接)。
API(服务端,前缀 /api/lark)
| 方法/路径 | 说明 |
|---|---|
GET /status | 应用/授权/监听器/工作目录 总状态 |
POST /bot/create | 启动「扫码新建机器人」(feishu-bot-bootstrap headless) |
GET /bot/create/status | 新建流程状态(二维码 URL、步骤进度、结果、错误) |
POST /listener/start|stop | 启停 lark-bridge 网关 |
GET /listener/logs?lines=N | 网关日志 tail |
GET /config | lark-cli 配置(掩码) |
装配(已完成的步骤,重启后生效)
- 源码在
~/deepseek/dsh-plugin-lark(= 当前工作区域,便于维护) - 软链到 profile:
~/.dsh/profiles/web/node_modules/dsh-plugin-lark → ~/deepseek/dsh-plugin-lark ~/.dsh/profiles/web/cordis.patch.yml增加条目:- insert: - id: lark-connector name: 'dsh-plugin-lark'- 重启 dsh web(见全局约定
dsh web或面板),刷新浏览器
自测
node test/harness.mjs # 不启动 Web,实测全部 API
python3 bridge/lark_bridge.py --test "你好" # 网关自测(不依赖飞书)
排障
- 卡片审批点了没反应 / 报 200671 → 看
lark_bridge.log是否handle message failed ... expected Dict but was str at field: value;若是, 检查卡片behaviors[].value是否用了对象而非 JSON 字符串(本项目已用对象)。 - 卡片按钮横排间隔过大 → 用
column_set且 columnwidth: "auto"(本项目默认)。 - 审批「批准」仍显示拒绝 → 检查是否 double-pop:
_issue_approval与卡片回调 不能都 pop 同一个 pending(本项目已改为回调get、_issue_approval统一pop)。 - lark_oapi 收不到消息 → 确认设置了
ws_client_module.loop(参照 hermes-agent 的asyncio.new_event_loop()+set_event_loop)。 - 面板不出现 → 确认重启了 dsh web、浏览器刷新
- 消息不回复 → 看「监听器」是否运行中;
GET /api/lark/listener/logs - ⚠️ 不要同时跑两个
lark-cli config init——会互相占配置锁导致命令卡住