DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

SJTUMalPan /

SJTUMalPan/dsh-ask-timeout-notify

Verified

DSH 宿主插件:ask_user_question 超过阈值(默认 60 分钟)无人回答时,向 notify-hub 投递一条待办并通过飞书/webhook/email 推送

★ 0 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHub
READMESource: main@03bd5397

dsh-ask-timeout-notify

DSH 宿主插件:模型通过 ask_user_question 提问后,如果超过阈值(默认 60 分钟) 仍无人回答,就往 notify-hub 投递一条 need_ack 消息,由 notify-hub 落成待办并按渠道推送(飞书 / webhook / email)。

典型场景:模型问了个选择题就去干别的了,你在开会/出门,DSH 的会话就这么挂着。 本插件让你在飞书里收到一条"DSH 在等你回答"的待办,而不是事后才发现它等了两小时。

它做什么、不做什么

做 不做
在 user-questions/request 这条 waterfall 链首起一个定时器 不替用户作答,也不改变 DSH "一直等"的语义
到点往 notify-hub 投一条待办(带 dedup_key 去重) 不改 notify-hub 一行代码、不需要给它加规则
用户答了/请求取消/出错了就销毁定时器 不影响问答主流程(投递失败只记日志)

为什么不用改 notify-hub:POST /api/v1/messages 本来就接受调用方声明 need_ack: true——分类器按 declared_need_ack or rule.need_ack 裁决并记 ack_reason=caller_declared,待办因此自动产生;dedup_key 让同一问题重复超时不会刷出 多条(服务端按 (source, dedup_key) 复用未完成的待办)。所以本插件只是一个普通的消息 生产者,跟 CI 脚本、监控告警走同一个入口。

什么时候会触发、什么时候不会

这一点是在真实 DSH 进程里实测确认的(不是推测),值得先讲清楚:

场景 链上的表现 本插件的行为
前端开着、你在忙,一直没点 回答者接手,next() 一直挂着 到阈值投一条待办 ✅
前端关掉 / 没有回答者 链上立刻以 NO_PROVIDER 拒绝 finally 销毁定时器,不发(这条问答已经失败了,不是「在等你」)❌
前端开着,但你在阈值内答了 next() 正常返回 定时器销毁,不发 ✅
会话被取消 / 处理中断 next() 抛错 定时器销毁,不发 ✅

也就是说:它管的是「人还没回」,不是「人不在」。浏览器关掉导致无人接手时,DSH 会 立刻判定这条问答失败,那种情况不会有待办——本插件的价值恰好在前端还开着、会话真的挂着 等你的那段时间。

前置条件

  1. DSH 已安装并能跑 dsh web(profile 名为 web)。
  2. notify-hub 在跑,且你知道它的地址与访问令牌:
    • 地址:默认 http://127.0.0.1:8000;
    • 令牌:config.yaml 里 server.auth_token 的值(未配置令牌 = 服务不鉴权,此时 本插件也要给一个非空令牌值,随便填即可——见下面「配置」)。

安装

本插件是独立仓库(仓库根目录就是插件包,因此插件市场的 dsh plugin add github:SJTUMalPan/dsh-ask-timeout-notify 可直接安装)。 它原先住在 notify-hub 仓库的 dsh-plugin/ 子目录里,拆出来时保留了提交历史。

从 GitHub 安装(推荐,锁 commit)

# 跟最新 main(方便,但会被上游改动影响)
dsh plugin --profile web add github:SJTUMalPan/dsh-ask-timeout-notify

# 锁 commit(可复现)
dsh plugin --profile web add "git+https://github.com/SJTUMalPan/dsh-ask-timeout-notify#<sha>"

本机开发(符号链接,改完即生效,不用重装)

dsh plugin --profile web add "link:$PWD"   # 在本仓库根目录执行

裸路径与 link:/file: 是 pnpm link 语义(符号链接):源码改了不用重装,但换机器就断。

安装之后

dsh plugin add 会做两件事:把包装进 ~/.dsh/profiles/web/,并把包名追加进该 profile package.json 的 dsh.profile.bundles(因为本包声明了 dsh.bundle.patch)。所以 不需要手改 profile 的任何文件。然后:

# 重启 dsh web 才生效(profile 的插件组合变更只对新进程生效)

卸载:

dsh plugin --profile web remove dsh-ask-timeout-notify

配置

两种方式,环境变量优先(同一份插件装在多台机器上时,只有环境变量能表达机器差异)。

环境变量 cordis.patch.yml 里的键 默认 说明
DSH_ASK_TIMEOUT_MINUTES timeoutMinutes 60 等待多少分钟算超时(正整数,1–10080)
DSH_NOTIFY_HUB_ENDPOINT endpoint 无(必填) notify-hub 地址,如 http://127.0.0.1:8000
DSH_NOTIFY_HUB_TOKEN token 无(必填) 访问令牌,放查询串 ?token=…,不写日志
DSH_ASK_NOTIFY_ENABLED enabled true 设 false 可整体关闭
DSH_ASK_NOTIFY_REQUEST_TIMEOUT_MS requestTimeoutMs 5000 单次 HTTP 投递超时(毫秒)
—(只认 config) source dsh 消息来源标识,notify-hub 规则匹配用

环境变量应该跟 dsh web 的启动方式放在一起(例如启动脚本里 export),不要写进仓库。

改 cordis.patch.yml 里的 config(~/.dsh/profiles/web/node_modules/dsh-ask-timeout-notify/cordis.patch.yml 不会生效——它属于包本身;要覆盖请改 profile 自己的 ~/.dsh/profiles/web/cordis.patch.yml):

# ~/.dsh/profiles/web/cordis.patch.yml
- id: ask-timeout-notify
  config:
    timeoutMinutes: 15

怎么判断它在工作

  • 没配置端点/令牌时:插件照常装载,但启动日志会有一条 warn 说明原因,超时后不会产生 待办。这是刻意设计——补上环境变量重启即可生效,而不是静默缺席。
  • 投递成功:DSH 日志里出现 ask-timeout-notify: 已投递待办 message_id=… todo_id=… dedup=ask:<会话>:<问题 id>。
  • 投递失败:日志里 ask-timeout-notify: 投递失败(…);不影响等待中的问答, 同时 DSH 里的问答照常继续等——通知失败永远不影响主流程。

DSH 的 ctx.logger.info 不一定落到 stdout(取决于 profile 的日志配置),所以最可靠的 旁证是 notify-hub 侧:/todos 页面出现一条 source=dsh 的待办。

排障

现象 原因与处理
待办没出现,日志也没有 已投递 端点或令牌没配。用 curl -sS "$ENDPOINT/healthz" 先确认服务活着(/healthz 免鉴权)
日志报 401 令牌不对。令牌取自 notify-hub config.yaml 的 server.auth_token
日志报 请求超时 notify-hub 没起或地址不可达;若它挂在公网隧道后面,确认隧道在线
有 apply 但从不触发 定时器只在有提问时才会起。让模型真的调一次 ask_user_question 试试
收到多条待办 不该发生——dedup_key 保证同一问题只建一条。若同一条待办被反复提醒,那是 notify-hub 的 reminders 在按日提醒,不是本插件
想立刻验证 把 DSH_ASK_TIMEOUT_MINUTES 设为 1,重启 dsh web,让模型问一个问题,然后不要回答,一分钟内应收到待办;验证完改回 60
启动报 Cannot find module '…/dsh-plugin/index.json' 你是在 --patch 里写了目录路径。loader 对目录路径按 cordis:include 目录入口处理(找 index.json),不会读 package.json 的 main。--patch 里要写入口文件:.../dsh-plugin/lib/index.js;只有通过 dsh plugin add 安装(按包名装载)时才用目录/包名

开发

cd dsh-plugin
npm test          # = node --test test/*.test.mjs

测试覆盖三块:配置解析(含环境变量/ config 优先级与非法值回退)、请求体构造 (dedup_key 稳定性、字段只含契约声明的键)、以及真实 HTTP 投递——测试里起一个真的 node:http 服务端,断言发出去的请求行(含 ?token= 转义)、请求头与请求体,而不是断言 我们自己的对象。超时、401、连不上、非 JSON 响应都有对应用例。

零依赖、纯 ESM JavaScript,没有构建步骤(这是刻意的:git 安装的包若要跑 prepare 脚本,pnpm 需要在 pnpm-workspace.yaml 的 allowBuilds 里逐个放行,一个百行插件不值得 背这个负担)。

文件

文件 作用
package.json 声明 main 与 dsh.bundle.patch(后者是本包能成为 profile 层级的关键)
cordis.patch.yml 两行 insert,把本插件挂进 profile 插件树
lib/index.js Cordis 接线:事件监听、定时器、effect 生命周期
lib/notify.mjs 纯函数层:配置解析、请求体构造、HTTP 投递(可独立测试)
test/notify.test.mjs 单测
—/ 5

No ratings yet

Verified DSH bundle

Commit 03bd53979e2d

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout