@kriskwok/dsh-feishu-gateway
v0.2.14
Published
DeepSeek Harness-native Feishu (Lark) gateway: chat with the DSH agent from Feishu via long connection, with persistent sessions, /new, Markdown replies, live streaming progress cards, native Typing reactions, click-to-answer approval / Q&A cards, and pro
Maintainers
Readme
dsh-feishu-gateway
English | 中文
在**飞书(Feishu/Lark)**里与你的 DeepSeek Harness(DSH) agent 对话。
这是一个 DSH 插件 bundle:挂载飞书长连接监听器,每条飞书消息路由到稳定的 DSH 会话
(通过 agents 服务的 resume 恢复,多轮对话保持在同一个会话),agent 的答复以
Markdown 富文本(post 消息的 md 标签)回复。支持 /new 开启全新会话、
原生 Typing 表情处理中指示、长任务的流式进度卡片、权限审批与
ask_user_question 的点击即答卡片、以及主动推送。
功能
- 💬 完整对话 — 飞书私聊 / 群聊 @机器人 → DSH agent → 回复。群聊中每个话题(thread)都是独立的 DSH 会话,该话题下的所有对话都保持在同一会话里,直到发
/new。 - 🔁 会话保持 — 每个飞书会话(或群聊话题)对应一个 DSH 会话(
agents.resume/agents.create);发/new(或"另起会话 / 新会话 / 重新开始 / 换个话题")开启全新会话, 群聊话题里发/new同样新开一个会话(该话题绑定新的 DSH 会话)。 - ⌨️ 原生 Typing 指示 — 处理期间机器人在你的消息上加一个
Typing表情回复 (同 hermes-agent 的飞书网关), 回答未结束就一直显示,失败时换成CrossMark。默认不再发"思考中…"提示语。 - 🎞 流式汇报 — 长任务持续汇报:一张实时交互卡片流式显示 agent 的
思考、工具调用、回答草稿(
reporting.mode: 'stream',默认开启); 设为reporting.mode: 'final'则只显示最终结果。 - 🃏 点击即答卡片 — 权限审批(
approval/request,如沙箱提权)与模型的ask_user_question工具会渲染成飞书交互卡片:点 ✅ 允许一次 / 🚫 拒绝 或选项按钮即可作答。点击后回调响应会瞬间把卡片替换为已处理态 (按钮移除、显示结果)并弹出 toast 确认。 - ✍️ Markdown 回复 — 用普通富文本(post)消息的
md标签:粗体、行内代码、 列表、链接原生渲染,无需卡片 - 🧩 Web-only 交互围栏降级 — 模型输出的
dsh-ui交互组件围栏(如 dsh-genui) 只在 Web UI 渲染;飞书渠道会自动降级成一行可读提示(提取标题,注明"请在 Web UI 查看"),不会出现裸 JSON 代码块 - 🤖 完整 agent 能力 — DSH agent 自带模型与工具(bash、文件、子代理…),完全自主
- 📨 主动推送 — 可选管理 HTTP API(
/api/push),随时向用户/群推送文本、Markdown、卡片 - 🔌 无需公网 — 飞书长连接,不需要回调地址
- 🗂 持久化 — 飞书↔DSH 会话映射重启不丢
环境要求
- 已安装并构建的 DeepSeek Harness(
dshCLI),并配置好DEEPSEEK_API_KEY(agent 直接使用 DSH 当前模型,无需另配) - 一个飞书开放平台企业自建应用,已开启机器人能力(见下)
飞书应用配置
- 飞书开放平台 → 创建企业自建应用。
- 开启机器人能力。
- 开通权限:
im:message、im:message:send_as_bot(如需读取消息内容再加im:message:send_as_bot:readonly),然后创建版本并发布。 - 事件与回调 → 选择使用长连接接收事件,订阅
im.message.receive_v1(无需公网)。 审批/问答卡片的按钮点击(card.action.trigger)也走同一条长连接,无需回调地址。 - 在飞书客户端搜索应用名,添加机器人为联系人。
Typing表情与卡片按钮依赖机器人在会话内有消息交互权限(im:message)。 若表情接口被拒,网关会自动退回发送hintText提示语。
安装(作为 DSH 插件)
前提:本包已发布到 npm,且 dsh 命令可用。
推荐把网关挂载到 web profile:与 DSH Web UI 同进程运行,启动 Web UI 即同时启动飞书网关,两者共用同一个 DSH agent。也可以用独立 profile 运行 (见文末「备选」)。
方式一(推荐):挂载到 web profile
web profile 是 DSH 的默认图形界面 profile(dsh --profile web)。
- 编辑
~/.dsh/profiles/web/package.json,加入依赖与 bundle:
{
"name": "dsh-profile-web",
"private": true,
"dependencies": {
"@kriskwok/dsh-feishu-gateway": "^0.2.0"
},
"dsh": {
"profile": {
"bundles": [
"@deepseek-ai/dsh-base",
"@deepseek-ai/dsh-web-app",
"@kriskwok/dsh-feishu-gateway"
]
}
}
}- 在 web profile 目录安装依赖:
cd ~/.dsh/profiles/web && pnpm install- 编辑
~/.dsh/profiles/web/cordis.patch.yml,填入飞书应用凭据:
- id: feishu-gateway
config:
feishu:
appId: cli_xxxxxxxxxxxxxxxx
appSecret: xxxxxxxxxxxxxxxxxxxxxxxx
http:
port: 3100 # 可选管理 API
token: your-token- 启动(或重启)web profile:
dsh --profile web也可以直接运行本仓库的一键脚本:
./scripts/create-profile.sh(默认挂载到 web profile;--standalone则创建独立 feishu profile)。
备选:独立 feishu profile
若不想通过 Web UI 使用,可让网关在独立 profile 中运行:
mkdir -p ~/.dsh/profiles/feishu && cd ~/.dsh/profiles/feishu
cat > package.json <<'EOF'
{
"name": "dsh-profile-feishu",
"private": true,
"dependencies": {
"@kriskwok/dsh-feishu-gateway": "^0.2.0"
},
"dsh": {
"profile": {
"bundles": ["@deepseek-ai/dsh-base", "@kriskwok/dsh-feishu-gateway"]
}
}
}
EOF
cat > pnpm-workspace.yaml <<'EOF'
packages:
- .
nodeLinker: hoisted
autoInstallPeers: false
EOF
pnpm install
# 再创建 ~/.dsh/profiles/feishu/cordis.patch.yml 填入应用凭据
dsh --profile feishu配置项
所有配置都在 feishu-gateway 命名空间下(profile patch 行或 ~/.dsh/settings.yaml):
| 字段 | 默认 | 说明 |
|---|---|---|
| feishu.appId | — | 飞书应用 App ID(必填) |
| feishu.appSecret | — | 飞书应用 App Secret(必填) |
| feishu.domain | feishu | feishu(国内)/ lark(海外) |
| feishu.botOpenId | 空 | 可选;@ 识别可自动完成 |
| feishu.replyMode | at | 群聊策略:at 仅被 @ 回复 / all 全部回复。每个群聊话题(thread)是独立的 DSH 会话。 |
| workspace | /root/Documents/DSH-Workspace | agent 工作目录(会话也会自动挂到对应的 DSH 工作区,在 Web UI 里归入该工作区而非"未分组")。私聊与群聊话题会话都固定在此工作区。 |
| hintText | 爸爸,我正在努力处理中…… | 兜底"处理中"文案(仅当 Typing 表情被禁用/不可用时) |
| reporting.mode | stream | stream=流式进度卡片;final=只显示最终结果 |
| reporting.typingReaction | true | 处理中显示原生 Typing 表情 |
| reporting.showReasoning | true | 卡片中流式显示模型思考 |
| reporting.showToolCalls | true | 卡片中流式显示工具调用 |
| reporting.patchIntervalMs | 1100 | 卡片刷新最小间隔(毫秒);飞书单条消息更新约限 1 次/秒(错误 230020),失败会自动退避 |
| reporting.maxBodyChars | 900 | 卡片正文最大渲染长度 |
| reporting.failureReaction | CrossMark | 失败时(移除 Typing 后)追加的表情 |
| reporting.cardTitleStreaming | 🤖 DSH 处理中… | 处理中卡片标题(黄色头)可自定义 |
| reporting.cardTitleDone | 🤖 DSH 处理完成 | 完成卡片标题(绿色头)可自定义 |
| interactions.approvalCards | true | 权限审批用可点击卡片回答 |
| interactions.userQuestionsCards | true | ask_user_question 用可点击卡片回答 |
| interactions.approvalCardDispose | update | 审批卡片点击后:update=回调响应瞬间替换为已处理态(按钮移除+结果+toast);recall=撤回卡片消息(失败自动回退 update;注意飞书会在原位显示"撤回了一条消息"占位) |
| newSessionPatterns | /new 及中文短语 | 触发另起会话的正则列表 |
| sessionsFile | data/dsh-feishu-sessions.json | 会话映射持久化文件 |
| http.port | 0 | 管理 API 端口(0=禁用) |
| http.token | 空 | 管理 API Bearer Token |
web profile 下的问答卡片:
ask_user_question的作答走唯一的ctx.userQuestionsprovider 槽位。网关从不抢占该槽位(否则 Web UI 的 apiProxy 宿主会因DUPLICATE_PROVIDER启动失败)——它在服务边界包裹service.ask做桥接:飞书会话的提问用飞书卡片作答,其余会话继续走 Web UI provider。权限审批卡片在任何部署下都从飞书作答。独立 feishu profile 下,ask_user_question与审批都在飞书卡片中作答。
会话共存与自愈
- 预设编排(有工具!) — 在 preset-roster 部署(如 web profile)下,飞书
agent 会从部署的 agent preset 编排(
meta.agentPreset+ presetmount), 模型因此拿到工具,而不会把工具调用当成纯文本。 - 与 Web UI 共存 — 会话单 owner。当 Web UI 打开某会话后,飞书侧通过
agents.get()接管正在运行的 agent 并驱动同一会话,不再报 "while it is live" / "already exists";两个界面共享同一段对话。 - wedged 会话自愈 — 进程中途死亡留下永久冲突的会话("already exists")时, 网关自动换一个新 session id,并把飞书会话映射重指向它,继续对话。
管理 HTTP API(可选)
设置 http.port 启用。端点:
GET /health— 状态POST /api/push— 主动推送{ "receive_id": "ou_xxx", "receive_id_type": "open_id", "msg_type": "text", "content": "{\"text\":\"hi\"}" }GET /api/sessions— 飞书↔DSH 会话映射概览
开发
pnpm install
pnpm build # tsc → lib/
pnpm test # 离线自测说明:
@deepseek-ai/*运行时由 DSH 宿主提供;本地类型检查从你的 deepseek-harness 检出目录 symlink(见发布检查清单)。
许可
MIT
