Skip to content
dsh.fish
Bundle

proactive-notify

DSH 主动通知插件:按类型区分通知文案,审批通知等页面弹窗就位后才弹出

Source
DemoJ
License
MIT
Updated
Updated 7 days ago

Readme

# 🔔 DSH 主动通知插件(proactive-notify)

一个运行在 [DeepSeek Harness](https://github.com/deepseek-ai)(DSH)Web GUI 上的**永久组合插件**:

当 Agent **遇到权限问题**(工具等待审批 / 被沙箱或审批拒绝)或**完成任务**(每轮处理结束)时,通过浏览器系统通知(Notification API)**主动提醒你**——即使你在其他网页、其他软件中,也能看到通知弹窗,并自带系统通知音效。

## ✨ 功能

| 场景 | 通知标题 | 通知正文 |
| --- | --- | --- |
| 工具需要用户审批 | ⚠️ 需要审批 | 需要审批:工具「××」正在等待你的授权(含原因) |
| 工具被沙箱/审批/权限规则拒绝 | ⚠️ 权限问题 | 权限问题:工具「××」执行被拒绝 — 原因 |
| 任务完成(Agent 转为 idle) | ✅ 任务完成 | 任务完成:本轮处理已结束 |

- **跨应用可见**:通过浏览器 Notification API 弹出系统级通知(Windows 上显示在通知中心,覆盖任何软件)
- **自带系统音效**:使用 Windows 系统通知声音,无需额外配置
- **审批通知不错位**:「需要审批」通知会等页面上的审批弹窗出现后才弹出——你看到通知切回页面时,审批面板一定已经在那儿了;若页面 WebSocket 一直未连上,3 秒后兜底强制弹出,提醒绝不丢失
- **平时零打扰**:通知正常工作后,页面内不显示任何 UI,纯系统级提醒(仅权限未就绪时出现右下角引导卡片)
- **开页即引导**:打开网页时若通知权限未授权,页面右下角会出现「开启通知」卡片,点击即弹出浏览器授权窗口(浏览器要求权限请求必须由一次真实点击触发,无法在打开页面时静默弹出)
- **永久加载**:一次安装,随 DSH 启动自动生效,无需重复安装

## 📋 前置条件

- DSH Web GUI(组合插件,随 DSH 进程永久加载)
- [pnpm](https://pnpm.io)(`dsh plugin` 命令通过它安装插件,装一次即可)
- 浏览器:Chrome / Edge(推荐)或 Firefox
- ⚠️ DSH 标签页必须保持打开(后台运行即可),关闭标签页后收不到通知

## 🚀 安装

一条命令,直接从本 GitHub 仓库安装:

```bash
dsh plugin --profile web add "github:DemoJ/proactive-notify#main"
```

然后重启 DSH(重新运行 `dsh web`)即可生效。无需手动复制文件、编辑任何配置。

> 原理:本包在 `package.json` 中声明了 `"dsh": { "bundle": { "patch": "./cordis.patch.yml" } }`。
> `dsh plugin add` 会用 pnpm 把 GitHub 仓库克隆、打包进 profile 的 `node_modules`,检测到 bundle 声明后自动把它追加到
> profile 的 `dsh.profile.bundles` 组合层列表;下次启动时组合器自动应用包内的 `cordis.patch.yml`。
>
> 本插件是纯 JS、无构建步骤的包,安装时不执行任何构建脚本,不会触发 pnpm 的「允许构建」确认
> (TS 等需要 `prepare` 构建的插件才会遇到那一步)。

### 更新

GitHub 方式没有 npm 版本号,「最新版」就是 `#main` 指向的最新提交。更新到最新:

```bash
dsh plugin --profile web update proactive-notify
```

pnpm 会重新从 `main` 分支拉取并替换已安装的包,之后重启 `dsh web` 生效。

若 `update` 后内容没有变化(可能命中 pnpm 的 git 解析缓存),卸载重装一次即可强制刷新:

```bash
dsh plugin --profile web remove proactive-notify
dsh plugin --profile web add "github:DemoJ/proactive-notify#main"
```

### 卸载

```bash
dsh plugin --profile web remove proactive-notify
```

卸载后重启 `dsh web`;bundle 随依赖一起移除,profile 组合层列表会自动同步。

### 锁定版本(可选)

`#main` 表示始终跟随 main 分支最新提交。也可以按标签或提交 hash 固定在某个状态:

```bash
dsh plugin --profile web add "github:DemoJ/proactive-notify#11bf8dc"   # 按提交 hash
```

### 从本地源码安装(开发调试)

改代码后想立刻在本机验证,不用先推 GitHub。在本仓库的**上级目录**执行(相对路径会锚定到你执行命令时所在的目录):

```bash
dsh plugin --profile web add ./proactive-notify
```

## 📦 发版方式(维护者)

不需要发布 npm——提交并推送到 GitHub 就等于发版:

```bash
git add -A
git commit -m "feat: ..."
git push origin main
```

已安装的用户随后执行 `dsh plugin --profile web update proactive-notify` 即可拿到新版。
建议每次顺手递增 `package.json` 的 `version` 字段(或直接用 `npm version patch`,它会自动改号并打 tag),方便用户核对自己装的版本。

## 🎯 使用

1. **首次打开网页**:若尚未授权通知权限,页面右下角会出现「🔔 开启通知」卡片
2. 点击 **开启通知**,浏览器会弹出权限请求,点击 **允许**(浏览器安全策略要求授权必须由一次点击触发,所以无法在打开页面时自动弹出)
3. 之后遇到权限问题或任务完成时,会自动弹出系统通知;若通知到来时权限仍未开启,「开启通知」卡片会再次出现提醒你

## ⚠️ 注意事项

- 若误点了「拒绝」,请到**地址栏左侧站点设置 → 通知 → 允许**,刷新页面后插件会重新检测;权限被拒后浏览器不会再弹授权窗,只能从站点设置手动开启
- **系统通知未授权时不会弹系统通知**,但页面右下角会保留「开启通知」引导卡片提醒你
- DSH 标签页必须保持打开(后台可用,关闭则收不到通知)

## 🔧 工作原理

**Host 半区**(Node 进程内,`index.js`):

- 监听 `tools/result`:工具执行失败且错误包含 `sandbox` / `permission` / `denied` 等关键词 → 推送 `approval-denied` 通知(立即弹出)
- 监听 `approval/request`:工具正在等待用户审批 → 推送 `approval-waiting` 通知,并在 3 秒后追加一条 `approval-waiting-force` 兜底通知
- 监听 `agent/status`:Agent 状态变为 `idle`(本轮处理结束)→ 推送 `task-complete` 通知(立即弹出)
- 通过 `webServer` 服务注册 HTTP 路由 `GET /api/notify/queue`,供 Client 轮询取走通知队列

**Client 半区**(浏览器页面内,`client.js`):

- 打开网页时检测通知权限:未授权(`default`)→ 显示「开启通知」卡片;已拒绝(`denied`)→ 显示去站点设置手动开启的指引卡片
- 点击卡片按钮时(用户手势上下文内)调用 `Notification.requestPermission()`,浏览器才会弹出授权窗口;授权成功后弹一条系统通知确认
- 每秒轮询一次 `GET /api/notify/queue` 拉取通知队列
- 按通知类型区分系统通知标题:`approval-waiting` / `approval-waiting-force` →「⚠️ 需要审批」,`approval-denied` →「⚠️ 权限问题」,`task-complete` →「✅ 任务完成」
- **审批通知就位机制**:`approval-waiting` 先缓存在本地,每轮轮询检查页面 DOM 中是否已出现审批面板(`[data-approval-key]`,由 WebSocket 实时推送渲染),就位后才弹系统通知——保证你看到通知切回页面时审批弹窗已经在那里;若 WebSocket 一直未连上(页面未打开/断连),Host 3 秒后追加的 `approval-waiting-force` 会强制弹出通知并清掉本地缓存,提醒绝不丢失
- 权限已授予时调用浏览器 `Notification` API 弹出系统通知(跨应用可见,自带系统音效);权限未授予时把引导卡片重新拉起来

**为什么权限引导放在页面内、且必须由点击触发?**

浏览器安全策略要求 `Notification.requestPermission()` 必须由用户手势(user activation)发起,否则会被静默忽略、不显示授权窗。因此无法在打开页面或收到通知时自动弹出授权,只能通过页面内按钮引导点击。Host 侧也无法代劳:Web 端的 `userQuestions` 提问要求 agent-owned session(插件加载时不存在 agent),而且任何经 Host 中转的流程到达 Client 时手势早已失效。

**为什么用 HTTP 轮询而不是动态插件的 `harness.handle`/`host.call`?**

组合插件没有动态插件的 Package-private RPC 通道,因此改用 `webServer` 注册 HTTP 路由 + Client `fetch()` 轮询的方式通信。

## 📁 文件说明

| 文件 | 说明 |
| --- | --- |
| `package.json` | 包声明(`dsh.bundle` 声明 bundle patch 层,`dsh.client` 标记 Client 半区,`exports` 声明入口) |
| `cordis.patch.yml` | bundle patch 层:安装时自动把插件行插入 profile 组合树 |
| `index.js` | Host 半区:事件监听、通知队列、HTTP 路由 |
| `client.js` | Client 半区:权限引导卡片、系统通知、队列轮询 |
| `README.md` | 本文档 |
| `LICENSE` | MIT 开源许可 |

## 📄 License

MIT

Install

dsh plugin --profile web add github:DemoJ/proactive-notify

Profile: web

  • This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.
Source