dsh-message-push
English | 中文
DeepSeek Harness 的 Cordis 插件:除了子代理以外,任何会话停下来都把消息推到配置好的消息平台(QQ / Telegram / 飞书 / 微信),于是人不用一直盯着屏幕。
覆盖所有「停下来」的情形:
| 场景 | 触发点 | 推送文案 |
|---|---|---|
| 任务完成 | turn/end(completed) + agent/status(idle) |
✅ 任务完成 |
| 任务中断 | turn/end(aborted) |
⏸ 任务中断 |
| 任务阻塞 | turn/end(blocked) |
🚫 任务阻塞 |
| 任务出错 | turn/end(error) |
❌ 任务出错(附错误信息) |
| 达到长度上限 | turn/end(max-tokens) |
↯ 达到长度上限 |
| 需要审批 | approval/request |
⚠️ 需要你审批 |
| 需要回答问题 | user-questions/request |
❓ 需要你回答问题 |
每条推送带上会话名(标题 / 目录 + 会话尾号)、最后一段回复的预览,以及「打开网页处理」的链接。
本插件是推送层:不做审批决策、不在聊天里批复、不把入站注入 agent 会话。决策仍在网页;推送只负责把人叫回来。其他插件可以直接调用 ctx.messagePush.notify({...}) 复用同一套渠道与去重。
安装
从 GitHub(钉 tag 更稳):
dsh plugin --profile web add github:DNAlec/dsh-message-push
从本地检出安装(开发时用):
git clone https://github.com/DNAlec/dsh-message-push.git
dsh plugin --profile web add "$PWD/dsh-message-push"
需要 pnpm。装完重启 dsh web:profile 组合在启动时解析,重启后设置页才会出现「消息推送」。
可选依赖(QQ 扫码创建机器人 / 飞书 SDK)随包声明,dsh plugin add 时由 pnpm 安装;缺了只影响对应渠道,插件照常工作。
从 npm 安装(发布后):
dsh plugin --profile web add @dnalec/dsh-message-push
卸载:dsh plugin --profile web remove @dnalec/dsh-message-push,重启后生效。~/.dsh/message-push/ 下的配置与凭据不会删除。
配置
打开 设置 → 消息推送。
- 选一个渠道并连接(至少要有一个「已启用 + 已绑定目标 + 已连接」的渠道):
- QQ:点「扫码创建机器人」用手机 QQ 扫码,或手填 AppID / AppSecret。扫码成功后会自动把扫码者设为推送目标。
- Telegram:填 @BotFather 给的 Bot token,保存即开始长轮询。
- 飞书 / Lark:填开放平台 App ID / Secret(需要可选依赖
@larksuiteoapi/node-sdk)。 - 微信:点「扫码登录」(官方 iLink,仅私聊,建议专用小号)。
- 绑定推送目标:未绑定时,用手机私聊机器人发一条消息,然后在设置页「最近入站」里点选;或者直接在未绑定时回复
是/确认/yes/ok,该聊天即被设为推送目标。这是私人 bot 设计:谁先回复「是」,谁的聊天就成为推送目标。不要把机器人放到陌生人能私聊的地方。QQ 群聊必须 @ 机器人,并在设置里填发言人的
userId。 - 点「发送测试推送」:收到消息说明通道打通。
- 按需调整通知规则:总开关、
webUrl、预览字数、合并窗口、各类停止原因的开关、以及「子代理会话也推送」(默认关闭)。
触发与去重
- 只看主会话:
session.header.origin === 'subagent'或delegationDepth > 0的会话默认跳过(可在设置里打开)。 - 同一 turn 只推一条:
turn/end与agent/status(idle)到达顺序不保证;先到的先把「停下来」定型,等turn/end带来原因后再发(最多等reasonUnknownDelayMs,默认 8s,超时按「会话已停下」发)。 - 待审批 / 待回答优先:它们比「停下」更具体,立即推送;同一段停顿里紧跟的
idle会折叠进这一条(追加「另有 N 条待处理」),不会再多发一条「任务完成」。 - 合并窗口:同一会话的同类事件在
repeatWindowSecs(默认 60s)内合并,只在计数变化时补发一条更新的提醒,避免刷屏。 - 发不出去不影响宿主:没有任何可用渠道时记
PUSH_SKIP/PUSH_FAIL审计并按 60s 限频告警,会话照常运行。
服务 API(给其他插件)
插件启动时通过 ctx.provide('messagePush', …) 在 host plane 提供服务,任意插件可消费:
// 方式一:可选消费,缺了也能跑
const push = ctx.get('messagePush')
if (push) {
push.notify({
kind: 'needs-approval', // needs-approval | needs-question | custom | turn-stop
sessionId, // 会话 id(用于去重与显示尾号)
title: '修复推送插件', // 会话名(可选)
body: '需要批准:删除 3 个文件',
toolName: 'bash', // 可选
})
}
// 方式二:硬依赖(服务不在就等它出现)
export const inject = ['messagePush', 'timer']
export function apply(ctx) {
await ctx.messagePush.broadcast('自定义通知')
}
| 方法 | 说明 |
|---|---|
broadcast(text, opts?) |
发给所有「开着 + 有目标 + 已连接」的渠道,返回 { sent, ok, failed, skipped } |
send(channelId, chatId, text, opts?) |
直发指定渠道/目标 |
notify(event) |
走观察器管线(去重 + 窗口合并)推送一条通知 |
onInbound(fn) |
订阅入站文本(只读)。返回 { dispose() };回调返回 true 表示已认领,宿主不再处理 |
channels() |
当前真正可推送的渠道 id 列表 |
state |
最近推送时间 / 结果 / 错误 |
设置页有 overwriteCorrupt 兜底;配置文件损坏时插件用内存默认值运行且绝不覆盖磁盘。
数据
全部在 $DSH_HOME/message-push/(默认 ~/.dsh/message-push/,权限 0600)。不要提交。
| 文件 | 内容 |
|---|---|
config.json |
渠道开关、推送目标、通知规则 |
secrets.json |
QQ AppID/Secret、Telegram token、飞书 AppID/Secret |
wechat.json |
微信 iLink 登录态(botToken / 上下文 / 游标) |
audit.log |
PUSH / PUSH_FAIL / PUSH_SKIP / BIND / INBOX / CONFIG / WARN |
没有审计行,就说明这次没触发推送。
故障排查
| 现象 | 处理 |
|---|---|
| 设置页看不到「消息推送」 | 确认 dsh plugin --profile web add 成功并重启 dsh web |
| 测试推送报「没有任何渠道可推」 | 渠道没启用 / 没绑 chatId / 没连上——看渠道卡片的状态行 |
| QQ 状态「未配置凭据」 | 扫码或填 AppID + AppSecret 后点「保存并连接」 |
| QQ 扫码报「未安装扫码依赖」 | 在插件目录执行 npm install(需要 qrcode 与可选的 @tencent-connect/qqbot-connector) |
| 飞书状态「缺少依赖」 | npm i @larksuiteoapi/node-sdk |
| 收到消息但内容为空 | 会话还没产生任何 assistant 文本(例如空回复);previewChars = 0 也会关闭预览 |
| 推送里没有「打开网页」链接 | 设置里填 webUrl,或让进程环境有 DSH_WEB_URL |
| 群聊回复没人应答 | QQ 群必须 @ 机器人,并在设置里填该发言人的 userId |
| 子代理任务没有推送 | 默认就是关闭的;需要时打开「子代理会话也推送」 |
开发
npm test # node --test tests/*.test.mjs
npm run check # node --check 全部源文件
结构:
src/index.mjs 宿主:事件接线、渠道装配、RPC、provide('messagePush')
src/watcher.mjs 会话停下观察器(去重 / 窗口合并 / 原因兜底)
src/format.mjs 推送正文渲染(纯函数,zh/en)
src/service.mjs 服务对象(broadcast / send / notify / onInbound)
src/inbound.mjs 入站:绑定目标 + 回执
src/replies.mjs 入站文本判定(纯函数)
src/store.mjs 配置与凭据读写(区分缺失与损坏)
src/audit.mjs 审计与限频告警
src/channels/ 枢纽 + QQ / Telegram / 飞书 / 微信 适配器
src/provisioning.mjs QQ 官方扫码创建机器人
client.js 设置页(React createElement,无 JSX)
locales.mjs Client zh/en 文案
纯 JS、零 @deepseek-ai/* 依赖:只用 cordis 的注入名(timer)与运行时自带的 fetch / WebSocket。
改代码前先读 AGENTS.md。
No comments yet. Be the first to write one.