dsh-plugin-lark

August 30, 2026 · View on GitHub

在 DSH Web 界面(设置 → 插件 →「飞书接入」标签页)提供扫码新建机器人 + 扫码授权 + 监听器管理, 不再需要手动敲 lark-cli 命令,也不用去开放平台手动配置。

各大能力

  1. 新建飞书机器人(推荐):面板点「新建机器人」→ 手机扫码登录开放平台 → 工具自动完成 创建应用 → 开机器人能力 → 导入权限 → 订阅消息事件 → 发布版本 → 约 30-60 秒拿到新机器人凭证并自动写入 lark-cli → 自动启动监听器 → 直接可用(一次扫码即连接)。 底层用 feishu-bot-bootstrap(headless + events-jsonl 事件流)。
  2. 用户身份授权(可选):需要以个人身份访问日历/文档/邮件等资源时再扫码授权(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/ps exec 被 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_BINdsh(PATH)dsh 可执行(绝对路径或 PATH 名)
LARK_CLI_BINlark-cli(PATH)lark-cli 路径
LARK_APP_JSON~/.dsh/feishu-app.jsonDSH 飞书应用凭据文件路径
LARK_BOT_NAMEDeepSeek 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.exampleDSH 飞书应用凭据模板(复制为 ~/.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 进程结束
  • diskutilshutdown/reboot/haltdd 写盘、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

配置(网关)

  1. 飞书应用凭据cp bridge/feishu-app.json.example ~/.dsh/feishu-app.json,填入 DSH bot 应用的 App ID / App Secret(飞书开放平台 → 应用 → 凭证与基础信息)。
  2. 审批规则(可选):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 /configlark-cli 配置(掩码)

装配(已完成的步骤,重启后生效)

  1. 源码在 ~/deepseek/dsh-plugin-lark(= 当前工作区域,便于维护)
  2. 软链到 profile:~/.dsh/profiles/web/node_modules/dsh-plugin-lark → ~/deepseek/dsh-plugin-lark
  3. ~/.dsh/profiles/web/cordis.patch.yml 增加条目:
    - insert:
        - id: lark-connector
          name: 'dsh-plugin-lark'
    
  4. 重启 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 且 column width: "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——会互相占配置锁导致命令卡住