# dsh-task-notify **Repository Path**: chy959799/dsh-task-notify ## Basic Information - **Project Name**: dsh-task-notify - **Description**: No description available - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2026-08-15 - **Last Updated**: 2026-10-14 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README
# 🔔 dsh-task-notify **DSH(DeepSeek Harness)插件:任务完成 · 系统弹窗通知** 模型回答完你的请求,系统立即弹出原生通知——切到别的窗口刷剧、写代码时,再也不怕错过 DSH 的完成信号。 `系统通知` · `零侵入` · `防打扰` · `可配置` [![npm version](https://img.shields.io/npm/v/dsh-task-notify)](https://www.npmjs.com/package/dsh-task-notify) [![npm downloads](https://img.shields.io/npm/dt/dsh-task-notify)](https://www.npmjs.com/package/dsh-task-notify) [![License](https://img.shields.io/npm/l/dsh-task-notify)](LICENSE) [![DSH](https://img.shields.io/badge/DeepSeek%20Harness-plugin-4D6BFE)](https://github.com/deepseek-ai/deepseek-harness) [![Gitee](https://img.shields.io/badge/Gitee-仓库-9C30F1)](https://gitee.com/chy959799/dsh-task-notify)
--- ## ✨ 特性一览 | 特性 | 说明 | |---|---| | 🪟 **原生系统通知** | host 端 node-notifier:Windows 通知中心 / macOS 通知 / Linux 原生通知 | | ✅ **准确的完成信号** | 监听 `turn/end (completed)`,只在模型真正回答完时弹;失败/中断不误报 | | ⏳ **等待授权提醒** | 运行中需要用户批准时同样弹窗提醒回到界面;授权结果不再重复通知 | | 💬 **等待回答提醒** | 模型暂停询问、需要你作答时同样弹窗提醒回到界面 | | 📝 **回复预览** | 通知正文带模型回答的开头文本(长度可配置) | | 🧘 **防打扰设计** | 默认仅在页面不在前台时弹;秒回的小问答不弹;任务完成默认跳过子代理(授权提醒不受限) | | 🎛️ **7 项可配置** | 设置页「任务通知」区块可视化调整,或 cordis patch 覆盖 | | 🛡️ **安全** | 全部 API 经 Host 头信任围栏;与 `/api` 网关同源校验 | | 📦 **一键安装** | `dsh plugin add` 自动装依赖 + 自动挂载,零配置 | --- ## 📦 安装 ### 前置要求 | 要求 | 说明 | |---|---| | DSH | 已安装且 `dsh web` 可正常运行(首次运行会自动初始化 `~/.dsh/profiles/web`) | | Node.js | ≥ 20 | | pnpm | ≥ 10 | ### 方式一:npm 安装(推荐) ```sh dsh plugin --profile web add dsh-task-notify ``` > `dsh plugin add` 自动完成三步:① 安装依赖(`node-notifier`、`schemastery` 自动带上);② 检测 `dsh.bundle` 声明自动挂载;③ 无需手动改任何配置。 ### 方式二:本地开发(link) ```sh cd ~/.dsh/profiles/web pnpm add "dsh-task-notify@link:<插件目录绝对路径>" ``` ### 卸载 ```sh dsh plugin --profile web remove dsh-task-notify ``` ### 生效 - **host 端**(监听逻辑)改动 → 重启 `dsh web` - **client 端**(设置面板)改动 → 硬刷新浏览器(Ctrl/Cmd+Shift+R) ### 🔄 更新(本地 link / 开发安装) 本仓库通常以 **link 安装**(`package.json` 里 `dsh-task-notify: link:<插件目录>`),DSH 直接引用本地目录的 `lib/`。更新步骤: ```sh # 1. 在插件目录拉取/修改源码后,重新构建 cd <插件目录绝对路径> pnpm install # 首次或依赖变更后 pnpm build # 重建 host 端 tsc + client 端 tsdown → lib/ # 2. 重启 DSH 宿主,让 host 端新逻辑生效(client 端改动则硬刷新浏览器) dsh web --port 8080 ``` > 因为是 link 连接,`pnpm build` 产出的 `lib/` 会被 DSH 立即读取,**无需重装依赖或重新 `pnpm add`**。 > 若更换了插件目录路径,用「方式二」重新执行一次 `pnpm add "dsh-task-notify@link:<新路径>"`。 --- ## 🚀 快速使用 ```sh # 1. 安装 dsh plugin --profile web add dsh-task-notify # 2. 重启 dsh web --port 8080 # 3. 使用 —— 让 DSH 跑一个任务,完成后系统弹窗: # 标题:任务完成 · <工作目录名> # 正文:模型回答的开头 120 字 ``` > 💡 默认 `onlyWhenBlurred: true`——**页面在前台时不弹**(防打扰)。想测试效果,先把 DSH 页面切到后台/最小化再跑任务。 --- ## ⚙️ 配置 ### 设置页(推荐) DSH 设置 → 「任务通知」区块,可视化调整全部选项。 ### cordis patch 覆盖 `~/.dsh/profiles/web/cordis.patch.yml`: ```yaml - id: task-notify name: 'dsh-task-notify' config: enabled: true # 总开关 onlyWhenBlurred: true # 仅页面不在前台时通知 notifyOnError: false # 出错/中断也通知 maxBodyChars: 120 # 正文预览长度(字符) minTurnMs: 0 # 最短通知时长(毫秒),过滤秒回 sessions: top-level # all | top-level showCwd: true # 标题显示工作目录名 ``` ### 配置项详解 | 配置项 | 类型 | 默认值 | 说明 | |---|---|---|---| | `enabled` | boolean | `true` | 总开关 | | `onlyWhenBlurred` | boolean | `true` | 仅页面不在前台时通知(client 上报可见性) | | `notifyOnError` | boolean | `false` | 非正常结束也通知:`error` / `aborted` / `max-tokens` / `blocked` / `interrupted` | | `maxBodyChars` | number | `120` | 正文预览截断长度(0–500) | | `minTurnMs` | number | `0` | 回合短于此时长(毫秒)不通知,过滤快速问答 | | `sessions` | `'all' \| 'top-level'` | `'top-level'` | `top-level` 跳过子代理会话 | | `showCwd` | boolean | `true` | 标题附带工作目录名,区分多会话 | --- ## 🧠 工作原理 ``` ┌─────────────────────────────── DSH 运行时 ───────────────────────────────┐ │ │ │ ┌─────────────────┐ append ┌─────────────────────────────┐ │ │ │ Agent 回合执行 │ ──────────► │ Session 事件日志(持久化) │ │ │ │ turn/end │ │ session/event 事件流 │ │ │ └─────────────────┘ └──────────────┬──────────────┘ │ │ │ ctx.on('session/event') │ │ ┌──────────────────────────────────────────────▼──────────────┐ │ │ │ dsh-task-notify (host 端) │ │ │ │ · turn/end & reason.kind === 'completed' 触发 │ │ │ │ · 过滤:enabled / sessions / onlyWhenBlurred / minTurnMs │ │ │ │ · 提取该回合最后一条 assistant 文本作正文(截断) │ │ │ │ · node-notifier 弹原生系统通知(失败回退 msg.exe) │ │ │ └──────────────────────────────────────────────┬──────────────┘ │ │ │ │ │ ┌──────────────────────────────────────────────▼──────────────┐ │ │ │ dsh-task-notify (client 端) │ │ │ │ · 监听 visibilitychange/blur/focus → POST /task-notify/api/ │ │ │ │ visibility 上报页面可见性,供 onlyWhenBlurred 门控 │ │ │ │ · 设置页「任务通知」区块 → /task-notify/api/settings.* 读写 │ │ │ └─────────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────────────────┘ ``` ### 通知时机 | 信号 | 事件 | 说明 | |---|---|---| | 任务完成 | `session/event` → `turn/end` | `reason.kind === 'completed'` = 模型回答完一轮 | | 非正常结束 | `error` / `aborted` / `max-tokens` / `blocked` / `interrupted` | 默认不通知;`notifyOnError` 开启时这五种都通知(`blocked`、`interrupted` 自 DSH 0.1.5 起提供) | | 等待授权 | `approval/asked` | 任务运行中需要用户批准;子代理的授权同样提醒 | | 需要回答 | `tool/call` = `ask_user_question` | 暂停等待用户作答时提醒;正文带问题文本 | | 授权结果 | `approval/decided` | 不通知(授权决策须回主界面操作),仅用于摘除待办记录 | ### 兼容性 | 依赖 | 说明 | |---|---| | DSH 版本 | 面向 **0.2.1-alpha.1+** 开发与验证(当前实测 0.2.1-alpha.2)。`peerDependencies` 声明 `^0.2.1-alpha.1`,可直接通过 DSH 的插件兼容性检查(`evaluatePluginCompatibility` 只校验 `@deepseek-ai/dsh*` peer 与运行时版本的 semver 匹配,且以 `includePrerelease` 判定),**不豁免、不报警告**。0.2.0-rc.x 及更早版本不适用 | | profile | 需要 **web** profile。`webServer` 与 `webStartup` 由 web bundle 提供,装进 headless / acp / sdk profile 时该行会永久停在 PENDING(不报错,也不工作) | ### 0.2.1 迁移要点(v0.4.1) | 变化 | 本插件的适配 | |---|---| | Web profile 删除 `webRuntime` 服务 | Cordis fiber 注入一个永不挂载的服务会**永久停在 PENDING**:`apply()` 不执行 → 通知全不发、`/task-notify/api/*` 路由不注册(设置区块读写全废),且不报错。现已改为注入 `webStartup`(`web-startup` 行发布),信任围栏读 `ctx.webStartup.trustedHosts`,字段名与语义不变 | | peer 版本范围 | 4 个 `@deepseek-ai/dsh-*` peer 与 7 个同款 devDependency 从 `^0.2.0-rc.1` 升到 `^0.2.1-alpha.1`——否则 0.2.0-rc.x 会通过兼容性检查却静默失效(这正是 v0.4.0 在 0.2.1 上的实际故障) | | 同步验证 | `dsh --profile web --dump-config` 可见 `web-startup` 行与 `task-notify` 行;重启后 `curl -X POST -d '{}' http://127.0.0.1:8080/task-notify/api/settings.get` 应返回 `{"ok":true,...}`(PENDING 时是 405) | ### 0.2.0 迁移要点(v0.4.0) | 变化 | 本插件的适配 | |---|---| | peer 版本范围 | 4 个 `@deepseek-ai/dsh-*` peer 从 `^0.1.5-rc.2` 升到 `^0.2.0-rc.1`(`^0.1.5-rc.2` 展开为 `>=0.1.5-rc.2 <0.2.0-0`,天然不匹配 `0.2.0-rc.1`,会触发启动跳过 + 预检禁用 + 安装拒绝三层阻断) | | `settings.register()` 被删除 | 改为导出 `Config = PrefsSchema`(Cordis Loader 模型);设置读写走 `settings.describe()/update()`,命名空间是 **profile entry id**(`task-notify`,必须与 `cordis.patch.yml` 的 `id` 一致) | | 表单只收录 `.volatile()` 字段 | 7 个配置字段全部加 `.volatile()`——否则 `describe()` 不返回本插件、`update()` 报 `no volatile fields` | | volatile 字段热更新不重挂载 fiber | 偏好改为**事件发生时现读** `describe()`(apply 捕获的启动配置会过期),读不到时回退启动配置 | | 声明 Config 后设置页自动生成原生表单 | `settings.configure({ auto: false })` 抑制原生页,沿用自定义「任务通知」区块,避免重复入口 | | schema 库 | 改用 DSH 维护的 `@deepseek-ai/schemastery`(npm `schemastery` 没有 `.volatile()`) | ### 安全设计 - 所有 `/task-notify/api/*` 路由经过 **Host 头信任围栏**(loopback / `webStartup.trustedHosts`),与 `/api` 网关同一套校验 - 插件只**读**会话日志,不修改任何 DSH 数据 - 通知内容截断(`maxBodyChars`),无敏感信息外泄 --- ## 📁 项目结构 ``` dsh-task-notify/ ├── package.json # dsh.client 声明 + 双端 exports + bundle patch ├── cordis.patch.yml # 挂载声明(dsh plugin add 自动识别) ├── tsconfig.json # 开发类型检查 ├── tsconfig.build.json # 构建编译(host 端 tsc → lib/) ├── tsdown.config.ts # client bundle 打包(__ModuleLoader__ 格式) ├── README.md ├── DISTRIBUTE.md # 发布与安装指南 └── src/ ├── index.ts # host 端:订阅 turn/end → node-notifier 弹窗 ├── config.ts # schemastery 配置 schema(7 项) ├── context-types.ts # cordis Context 类型增强 └── client/ └── index.tsx # client 端:设置面板 + 页面可见性上报 ``` --- ## 🛠️ 开发与构建 ```sh pnpm install # 安装依赖 pnpm typecheck # tsc --noEmit 类型检查 pnpm build # tsc(host) + tsdown(client) → lib/ pnpm watch # tsdown --watch(client 热重载) pnpm pack # 生成可分发 tarball ``` ### 构建产物 | 产物 | 说明 | |---|---| | `lib/index.js` | host 端(Node,ESM) | | `lib/client.js` | client 端 bundle(`window.__ModuleLoader__.load` 格式,供浏览器加载) | | `lib/*.d.ts` | 类型声明 | ### 发布到 npm ```sh # 改 package.json version → 0.1.x npm publish ``` --- ## ❓ 常见问题
装了但设置页没有「任务通知」区块? 硬刷新浏览器(Ctrl/Cmd+Shift+R)。client 端改动不需要重启 DSH。
任务完成了但没弹通知? 按顺序排查: 1. 页面是否在前台?默认 `onlyWhenBlurred: true`,页面可见时**故意不弹**(防打扰)——切后台再试,或到设置页关掉该项 2. 系统通知权限?Windows「设置 → 系统 → 通知」允许来自 PowerShell/终端的通知;macOS「系统设置 → 通知」 3. 检查设置页「任务通知」是否启用了总开关
安装时出现 "missing peer @deepseek-ai/*" 警告? 正常现象。`@deepseek-ai/*` 由 DSH 运行时提供(不在 profile 的 node_modules 里),pnpm 在 profile 层检查不到所以告警,**不影响运行**——官方 dsh-better-sidebar 安装时同样如此。
Windows 首次弹窗没反应? node-notifier 在 Windows 走 PowerShell toast 脚本,首次可能被 SmartScreen 拦截,放行一次即可;插件内置回退(`msg.exe`),实在不行会在会话日志输出 `[dsh-task-notify]` 警告。
子代理/后台任务完成也会弹吗? 默认不弹(`sessions: top-level` 跳过 `delegationDepth > 0` 的子代理会话)。改为 `all` 则所有会话都通知。
--- ## 📄 许可证 [MIT](LICENSE) ---
**dsh-task-notify** · 让每一次任务完成都不被错过