@ltao0829/dsh-task-notify
v0.3.0
Published
Task-completion reminder for DeepSeek Harness 0.2: in-page toast, OS notification, and sound when an agent turn or background job finishes
Downloads
457
Readme
dsh-task-notify
AI 编程代理的生命周期通知层,当前支持 DeepSeek Harness。
English · 简体中文
AI 编程代理越来越多地运行长时间、自主的任务:一轮任务可能持续数分钟,而用户通常已经切到别的窗口。本项目在代理任务生命周期之上补上了缺失的通知层:当代理完成、失败、后台任务结束、或开始等待人工介入(审批 / 计划评审 / 提问)时,立即通过页面 toast、操作系统级桌面通知以及可选提示音提醒你。
借鉴 Codex 的桌面通知体验,而非 Codex 集成。与 OpenAI 无关联,也不受 OpenAI 赞助。 目前本项目以 DeepSeek Harness(DSH)插件形式发布;其生命周期检测核心与宿主无关,设计目标是逐步演进为支持更多编程代理的适配器。
为什么需要它
长任务代理反转了传统注意力模型:用户提交任务后就会切走,而不是盯着终端。纯 UI 状态提示(后台标签页里的一个 spinner)恰恰在关键时刻失效——用户没有在看。dsh-task-notify 把生命周期变化转化为可打断的、系统级的信号,闭合自主代理与分心用户之间的反馈环。
演示
录屏尚未入库,因此下表只列出三个片段的文件名,而不是直接嵌入图片。录制方案(场景、工具、体积建议)与恢复图片所需的精确写法见
docs/demo-guide.md。
| 任务完成 | 需要审核 | 后台任务失败 |
| --- | --- | --- |
| docs/demo-turn.gif | docs/demo-review.gif | docs/demo-failure.gif |
功能
- 对话任务完成提醒 —— 助手一轮任务(思考或工具调用)结束时提醒。
- 后台任务完成提醒 —— 后台命令或子代理作业结束时提醒(
completed/failed/killed)。 - 需要审核提醒 —— 运行中等待审批 / 计划评审 / 回答提问时提醒。
- 失败提醒 —— 对话任务报错或后台任务失败 / 被终止时提醒。
- 三种通知通道 —— 浏览器系统通知 + 页面 toast + 可选双音提示音。
- 逐项开关 —— 每类事件和每种通道均可独立开关。
- 可自定义 —— 标题 / 正文模板、提示音样式与音量、toast 停靠角落与停留时长、免打扰时段、按会话静音。
环境要求
| 依赖项 | 说明 |
| --- | --- |
| DeepSeek Harness | 0.2.0-rc.2 或更新的 0.2.x —— 兼容性网关读取的是 peerDependencies,本包把所有 DSH peer 固定为 ^0.2.0-rc.2 |
| Node.js | >=22.0.0(CI 覆盖 Node 22 与 24;桌面端内置运行时为 Node 24) |
| pnpm | 仅在使用 dsh plugin 命令行安装时需要;桌面端使用其内置的 pnpm |
安装
# 从 npm 安装 —— 请写明版本范围,原因见下方冷却期说明
dsh plugin --profile <profile> add @ltao0829/dsh-task-notify@^0.3.0
# 或从 Git 安装,锁定发布标签
dsh plugin --profile <profile> add git+https://github.com/ltao0829/dsh-task-notify.git#v0.3.0<profile> 是要装入的 DSH Profile:dsh web 服务器用 web,桌面端用 desktop。安装会把包写入该 Profile 并注册它的 bundle patch,由后者插入唯一一行 task-notify,同时挂载插件的宿主半部与浏览器半部。
在桌面端请从应用自身的 设置 → 插件 安装。desktop Profile 由 Electron 应用独占管理:活动 Profile 的插件变更会立即生效、无需重启,而 dsh --profile desktop … 会拒绝在第二个命令行进程里运行该 Profile。
装入 dsh web 服务器后,重启服务并刷新页面。首次在页面里点击/按键时,浏览器会请求「通知」权限,点允许即可收到系统通知。
版本必须与 DSH 系列对应。
0.2.x与0.3.x需要 DeepSeek Harness 0.2;0.1.x需要 DSH 0.1.x。两个系列不能互相通用:DSH 0.2 的兼容性网关会直接拒绝0.1.x系列,而 DSH 0.1.x 也无法加载更新的系列。
0.1.x系列已停止维护。npm 上只有0.1.0;之后的0.1.1、0.1.2修复只存在于 Git 历史中,从未打过标签、也从未发布。如果你仍在 DSH 0.1.x,请先把 DSH 升级到 0.2,再安装0.3.x。
为什么要写版本范围而不是裸包名。 pnpm 11 内置了 24 小时供应链冷却期(
minimumReleaseAge默认为24 * 60分钟)。发布时间不足一天的版本对版本解析不可见,因此dsh plugin add @ltao0829/dsh-task-notify会静默解析到上一个发布版本,进而被判为不兼容而拒绝。写明版本或范围后,pnpm 会写入minimumReleaseAgeExclude并立即安装;等发布满一天后,裸包名也会正常解析到最新版。
卸载
dsh plugin --profile <profile> remove @ltao0829/dsh-task-notify
# 桌面端:在「设置 → 插件」中移除这会同时移除包与它的 bundle 条目,task-notify 行随即从合成后的 Profile 中消失。浏览器侧的 localStorage 记录(dsh.taskNotify.v3)不属于 Profile:若希望重装后从默认值开始,请手动清除该键。
配置
设置页面位于 DSH 设置界面的**「插件」**区(DSH 0.2 新增的 settings.plugins.tab 插槽),页面标题为任务完成提醒。配置存于 localStorage(键 dsh.taskNotify.v3)。遗留的 v2 记录会被迁移一次——所有开关保留,旧的提示音开关映射为双音模式——随后 v2 键会被移除:
| 开关 | 默认 | 说明 | | --- | --- | --- | | 启用提醒 | 开 | 总开关 | | 对话任务完成提醒 | 开 | 助手一轮任务结束时提醒 | | 后台任务完成提醒 | 开 | 后台命令 / 子代理作业结束时提醒 | | 监听全部会话的后台任务 | 关 | 为会话列表中每个会话各开一条后台任务流,而非仅监听本页面打开后活跃过的会话 | | 需要审核时提醒 | 开 | 运行中等待审批 / 计划评审 / 提问时提醒 | | 失败时提醒 | 开 | 对话任务报错或后台任务失败 / 被终止时提醒 | | 浏览器系统通知 | 开 | 同时发送操作系统通知(需授权) |
提示音、弹窗与免打扰
| 设置 | 默认 | 说明 |
| --- | --- | --- |
| 提示音 | 无 | 无 / 单音 / 双音 / 自定义音频 |
| 音频 URL | 空 | 提示音为自定义音频时播放;留空或加载失败时回退为双音 |
| 音量 | 100% | 对所有提示音样式生效 |
| 弹窗位置 | 右下 | 四个屏幕角落任选 |
| 停留时长(秒) | 5 | 弹窗显示 3–15 秒后自动消失 |
| 自定义标题 / 正文模板 | 空 | 占位符:{title}(任务标题)、{session}(会话 ID)、{kind}(事件类型);留空使用内置文案 |
| 免打扰时段 | 关 | 该本地时间段内不触发任何提醒;支持跨零点(如 22:00 → 08:00) |
| 静音的会话 | 空 | 每行一个会话 ID;这些会话不触发任何提醒 |
页面内 toast 没有开关——它是始终可用的通道,也是系统通知被拒绝、提示音被拦截时的兜底。模板输出按纯文本渲染,模板无法注入任何标记。
故障排查
| 现象 | 原因 | 处理 |
| --- | --- | --- |
| 有 toast,但没有系统通知 | Notification.permission 不是 granted,插件会静默跳过而非报错 | 授予通知权限。浏览器只允许在用户手势中弹窗,因此在页面里点击一次即可;或把浏览器系统通知关掉再打开,它会主动请求权限 |
| 没有提示音 | Web Audio 在用户手势之前处于 suspended 状态 | 在页面里点击一次或按一下键盘,音频会在首个手势解锁 |
| 完全没有任何提醒 | 总开关或对应事件开关是关的 | 检查启用提醒与各事件开关 |
| 设定时段附近没有提醒 | 已开启免打扰时段,当前本地时间落在窗口内 | 关闭免打扰时段或调整时间;静音的会话列表中的会话同样保持静音 |
| 已经跑完的任务没有补发提醒 | 页面加载后的首个快照只建立基线 | 属预期行为——刷新页面不会重放历史 |
| 只对当前在看的会话有后台任务提醒 | 默认只为「本页面打开后被观察到运行过」的会话各开一条 job.list 流 | 打开监听全部会话的后台任务 |
| 任务失败但没有提醒 | lastAgentError 只存在于被 retain 的会话 face 上 | 属预期行为——插件覆盖工作区已保持打开的会话,不会为了监听报错而额外 retain 会话 |
| 安装被判为不兼容 | 0.1.x 插件装在 DSH 0.2 上,或反之 | 让两个系列对应,见安装中的配对说明 |
| 用裸包名装到了旧版本 | pnpm 11 的 24 小时 minimumReleaseAge 冷却期 | 写明范围:@^0.3.0 |
架构
coding agent(编程代理)
│
▼
宿主适配器(当前为 DeepSeek Harness;
未来可扩展 Claude Code / Codex / OpenCode)
│
▼
会话快照(N-1 与 N)
│
▼
生命周期检测器 ──► 事件:turn | job | review | failure
│
▼
通知分发器
│ │ │
▼ ▼ ▼
系统通知 toast 提示音检测器(src/detect.ts)是纯函数:快照进去,生命周期事件出来。它不依赖 DSH 或 DOM,因此未来支持更多编程代理只需实现新的快照提供者,而无需重写通知核心。
DSH 0.2 的数据来源
DSH 0.2 移除了原先同时承载 jobsBySession 与逐行 pendingInteraction 的单一 sessions.list store。监听器现在把三个彼此独立的客户端数据源折叠成一份快照:
| 数据源 | 服务 | 提供内容 |
| --- | --- | --- |
| 会话目录 | ctx.sessions.list | id、标题、宿主基线的运行标志、引用计数 |
| 客户端状态投影 | ctx.uiSession.sessionStatus | 实时的 running、待处理交互(key + kind)、未读完成标记 |
| 后台任务名册 | ctx.jobs.state | 每个被监听会话的 JobView 行,由每会话一条 job.list 流驱动 |
值得注意的行为:
- 首个快照只建立基线 —— 刷新页面不会补发历史提醒;此后才出现的会话或任务名册同样视为既存,不触发提醒。
- 待处理交互以其不透明请求 key 追踪:被替换的新请求(审批之后又来一个提问)会再次提醒,而重连时对同一请求的重放不会重复提醒。
job.list是按会话的流。默认只为「本页面打开后被观察到运行过」的会话各开一条;未在此页面运行过的会话,不可能持有本页面正在等待的后台任务。开启「监听全部会话」则改为覆盖整个会话列表,代价是每个会话一条流。- 对话失败检测读取
lastAgentError,而该字段只存在于被 retain 的会话 face 上。因此本插件覆盖工作区已经打开的会话,不会为了监听报错而额外 retain 会话。
通知通道
toast 直接挂载到 document.body,不依赖 React root,也不占用任何插槽,因此在没有对话座位的界面上(未选中会话、设置面板打开等)依然可用。最多同时堆叠 4 条,每条显示 5 秒。系统通知只在权限已是 granted 时发送;提示音会先 resume() 处于 suspended 状态的 AudioContext——每条通道都选择静默降级,而不是抛错。
项目结构
src/index.ts 宿主半部 —— 惰性加载锚点
src/detect.ts 纯生命周期检测器(三源折叠 + 快照 diff)
src/client/index.ts 浏览器半部 —— 监听器、后台任务流与失败监听、UI 注册
src/client/notify.ts toast / 系统通知 / 提示音
src/client/locales.ts zh + en 词典与命名空间声明合并
src/client/settings.ts localStorage 设置存储
src/client/TaskNotifySettingsCard.tsx 设置页面(settings.plugins.tab)
tests/*.spec.ts 检测器、生命周期、设置、通知、Manifest 与 apply() 测试安全与隐私
- 无外部服务器,无云后端。
- 无遥测、无分析、无追踪。
- 无需 API 密钥。
- 通知全部在浏览器本地生成。
- 设置仅保存在本地
localStorage。 - 插件不会上传或外传对话内容。
与其他 DSH 插件一样,插件以你 DSH 进程的权限运行。
路线图
本节是"计划中工作"的唯一真源;CHANGELOG.md 只记录已经交付的内容。每项的范围、依赖、风险与验收标准见 docs/roadmap-plan.md。
当前阶段
- [x] 对话任务完成通知
- [x] 后台任务通知
- [x] 审批 / 计划评审 / 提问通知
- [x] 失败通知
- [x] 系统通知 + toast + 提示音
- [x] npm 正式发布(
@ltao0829/dsh-task-notify) - [x] CI/CD(typecheck + test + build + pack dry-run)
近期
- [ ] README 演示录屏
- [x] npm 下载量徽章(shields.io,仅用 npm 公共统计 API)
- [ ] 采用度趋势跟踪(周度下载量变化)
- [ ] 跨平台通知后端
- [x] 通知自定义
- [x] 测试覆盖率度量与"只升不降"回归门禁
- [ ] 提升监听器与通知渲染器的覆盖率
长期
- [ ] 与代理无关的生命周期 API
- [ ] 更多编程代理集成(Claude Code、Codex、OpenCode……)
- [ ] 可复用的通知核心
相关项目
- dsh-launcher —— DeepSeek Harness Web 的一键启动器。
两者共同构成一套面向 AI 编程代理工作流的小型工具集。
同一作者的另一个项目:buaa-auto-auth —— 为一台 Windows 机器每天自动完成校园网(SRun)认证。
贡献
兼容性与一次性 Profile 验证
- Node.js:
>=22.0.0(CI 矩阵覆盖 Node 22 与 24) - DeepSeek Harness:
^0.2.0-rc.2
DSH 0.2 已不再读取被废弃的 dsh.compatibility 字段。Profile 导入插件前,evaluatePluginCompatibility(@deepseek-ai/dsh-app-boot)会把插件 peerDependencies 中所有 @deepseek-ai/dsh / @deepseek-ai/dsh-* 项与唯一运行时版本逐一比对,预发布版本参与范围匹配。因此本包把所有 DSH peer 固定为 ^0.2.0-rc.2,并在 engines.dsh 中镜像同一范围;只要有任何一项漂移,tests/manifest.spec.ts 就会失败。
0.2.0是破坏性的重新定位,而非增量发布:0.1.x系列基于已被移除的@deepseek-ai/dsh-client-runtime与settings.plugin.item插槽构建,这两者在 DSH 0.2 中都不复存在。npm 上只发布过0.1.0;0.1.1与0.1.2仅存在于 Git 历史中。
一次性 Profile 验证命令
在不污染任何现有用户配置(~/.dsh)的隔离临时 Profile 中,验证插件安装、配置合成、Web 启动监听与卸载:
pnpm run verify:profile脚本默认等待 DSH 首次准备 Profile 最多 90 秒。如果 CI Runner 或本机需要调整,可将 DSH_WEB_START_TIMEOUT_MS 设置为正整数毫秒值。插件安装另有默认五分钟的等待限制,可通过 DSH_PLUGIN_INSTALL_TIMEOUT_MS 调整。
验证日志证据报告:docs/verification-evidence.md。
限制与边界说明:Catalog 准入及一次性 Profile 验收证明插件生命周期契约与标准 Web Profile 启动兼容;系统级操作系统通知依然依赖用户在浏览器前端授予通知权限。一次性 Profile 验证会把打包后的 tarball 装入一个临时
DSH_HOME,证明插件被接受并成功加载;此外构建产物还会被单独校验,确认其外部依赖只包含平台模块。该脚本不驱动真实浏览器,页面内通知行为由 jsdom 测试套件覆盖。
开发
pnpm install
pnpm run typecheck
pnpm test
pnpm run coverage
pnpm run build
pnpm run verify:profile必须先 pnpm run build 再 pnpm test:tests/bundle.spec.ts 会断言产出的 lib/client.js 只 require 冻结的平台模块。lib/ 是提交进仓库的,因此改动源码后必须把重新构建的产物一并纳入提交。
