Skip to content
dsh.fish
Bundle

dsh-blocker-notify

Real-time attention alerts for DeepSeek Harness: a global banner plus flashing workspace entries when the agent is blocked (approval request / sandbox denial), with an audible chime and OS-level notifications for new blockers.

Source
Frost-Reed
stars
2 stars
License
MIT
Updated
Updated 18 hours ago

Readme

# dsh-blocker-notify

[English](README.en.md) | 简体中文

**DeepSeek Harness(DSH)阻塞提醒插件**:当 Agent 在工作中被卡住、需要用户介入时,在**网页端实时提醒**,让你不用一直盯着屏幕等待操作。

![示例](./assets/show_fullscreen.png)

触发提醒的三类"阻塞"信号:

| 信号 | 说明 |
|---|---|
| **提权 / 审批**(`approval/request`) | Agent 请求提升沙箱权限、或需要审批时(如 read-only 下 `write` 提权)→ **提醒** |
| **沙箱拒绝**(`FS_SANDBOX_DENIED`) | write/edit 在 read-only / workspace-write 模式下被拒 → 仅 Host 记录(诊断用),**不打扰用户** |
| **等待用户交互**(`question` / `plan-review`) | Agent 在等你回答提问、审阅计划 → **提醒** |

> **只提醒需要你操作的事件**(审批 / 提问 / 计划审阅)。沙箱拒绝是已完结事件、无可操作项,保持静默(即使审批策略是 `never` 也不会弹出打扰)。

## 四种提醒方式

| 方式 | 效果 |
|---|---|
| **全局消息(横幅)** | 右上角琥珀色横幅,列出所有被阻塞的会话;每行只显示**会话标题 + 状态**(不写工具名/原因);**点击条目 = 跳转会话 + 该条立即移除**(不依赖审批结果);✕ 关闭整个横幅;新阻塞到达时重新出现 |
| **工作区条目标黄并闪烁** | 左侧工作区会话条目上的黄色状态点 **1.1s 闪烁**——黄色标记是 DSH 自带的("等待用户"状态),本插件加上闪烁动画;阻塞解决后自动停止 |
| **提示音(声响)** | **新增**阻塞出现时,页面播放两声短提示音(Web Audio 合成,无需音频文件);只在阻塞集合变化(有新阻塞)时响,页面加载时已有的阻塞不响;浏览器未解锁自动播放或环境不支持时静默降级 |
| **操作系统级通知** | **新增**阻塞出现时,浏览器通过 Notification API 弹出**系统通知**(Windows 操作中心 / macOS 通知中心);点击通知可聚焦 DSH 页面(单个阻塞时直接打开对应会话);单个阻塞显示「会话标题 — 类别(工具名 — 原因)」,多个阻塞合并为一条;权限**已开启**时横幅标题栏显示小 🔔 图标(点击弹测试通知),**未开启/被拒绝**时横幅显示操作行(一键开启 / 去浏览器设置修复) |

## 怎么用

### 方式一:动态插件(快速启用,5 分钟,推荐先试)

不需要安装任何东西,不需要重启 DSH。**缺点**:进程内存态,DSH 重启后需重新启用。

1. 获取本仓库源码:`git clone https://github.com/Frost-Reed/blocker-notify`(或直接在 GitHub 页面下载)。
2. 打开任意会话(建议 **cordis / 创造模式**),把下面这段指令发给 Agent(把 `<仓库路径>` 换成你本地克隆的路径):

   ```text
   请把 dsh-blocker-notify 启用为动态插件:
   1. 用 read 工具读取 <仓库路径>/dynamic/host.js 和 <仓库路径>/dynamic/client.js(分别是 code.host 与 code.client 的函数体,直接使用文件内容)。
   2. 调用 cordis_define:plugin: { kind: "new", idPrefix: "ntfy" };name: "blocker-notify";purpose 一句话;code.host = host.js 内容;code.client = client.js 内容。
   3. 用 cordis_define 返回的 pluginId/packageId 调用 cordis_run(mode: "run")。
   4. 运行请求可能需要用户在网页端批准 Client 半;等待批准结果,不要重复请求。
   5. 完成后用 cordis_inspect_self(pluginId, packageId) 确认 host/client 均为 running。
   ```

3. 在网页端**批准** Client 半的运行请求(一次即可)。
4. 立即验证(见下文"验证")。

### 方式二:正式 bundle 安装(持久生效,v0.2.0)

按 DSH 的正式插件规范打包(`dsh.bundle` + `dsh.client`),安装后跨重启生效。

> 若 `dsh` 不在 PATH(例如从源码仓构建运行、或未全局安装),把下面命令里的 `dsh` 换成 `pnpm dsh`(在源码仓根目录)或 `npx @deepseek-ai/dsh`。

**从 GitHub 安装**(已发布,推荐):

```powershell
dsh plugin --profile web add github:Frost-Reed/blocker-notify
```

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

```powershell
dsh plugin --profile web add file:<本地克隆路径>
```

安装后:

```powershell
# 确认进入层栈
Get-Content $env:USERPROFILE\.dsh\profiles\web\package.json   # 应看到 dsh-blocker-notify 出现在 dsh.profile.bundles
# 确认配置树里有行
dsh --dump-config --profile web | Select-String "dsh-blocker-notify"
# 新增依赖/层需要重启进程
dsh web
```

> `lib/` 是**已提交的构建产物**(由 `src/` 经 tsdown 生成)。git 安装会运行 `prepare` 构建脚本,pnpm 需在 `allowBuilds` 授权一次;也可用 npm 包 / `pnpm pack` tarball 安装(预构建产物,无需授权)。

### 验证(两种方式通用)

| 场景 | 操作 | 预期 |
|---|---|---|
| 提问自测 | 让 Agent 调用一次 `ask_user_question` | 提问弹窗出现时:横幅「等待回答」+ 提示音 + 系统通知;点击横幅行跳转并移除该条 |
| 提权审批 | 设置里把审批策略设为 `ask`、沙箱设为 `read-only`,让 Agent 尝试带 `sandbox_permissions` 的写入 | 审批卡片出现时:横幅「等待授权」+ 提示音 + 系统通知(含工具名与原因);点击横幅行跳转并移除(批准与否都移除) |
| 沙箱拒绝 | 让 Agent 不带提权参数写入被拒文件 | **静默**(无横幅、无提示音、无系统通知;仅 Host 日志记录供诊断) |

## 工作原理

```
Host 半(进程内,未打 scope 标签 → 观察所有 agent 的审批/工具结果)
  ├─ ctx.on('approval/request', (req, next) => …)   // waterfall,仅观察,必须 return next()
  └─ ctx.on('tools/result', …)                       // 检测 FS_SANDBOX_DENIED
        │  写入进程内告警日志(按 key 去重、10 分钟 TTL、上限 60 条)
        ▼
  详情通道:正式版 = webServer 路由 GET /api/dsh-blocker-notify/alerts
             动态版 = harness.handle('blocker-notify/alerts') RPC
        ▼
Client 半(浏览器页面,根作用域)
  ├─ useSessions(shell.overlay 标准 prop)→ 实时发现 pendingInteraction(approval / plan-review / question)
  ├─ 轮询详情(fetch / host.call)→ 合并审批详情(工具名 / 原因)
  ├─ CSS 注入 → 让 [data-state="warning"] 黄点闪烁
  ├─ Web Audio → 新增可操作阻塞出现时播放提示音(首次交互预热 AudioContext)
  └─ Notification API → 新增可操作阻塞出现时弹系统通知(横幅内一键开启/测试,点击通知跳回页面)
```

- **Host 半**:捕获阻塞信号、维护进程内告警日志(纯 JS,无 UI);沙箱拒绝只记录、不参与提醒。
- **Client 半**:只展示**挂起中的可操作阻塞**(审批/提问/审阅,解决或点击即消失);沙箱拒绝静默。数据来自 `shell.overlay` 的标准 prop `useSessions`(快照 `{ ids, byId, … }`,`byId` 条目带 `id` / `title` / `pendingInteraction`)+ Host 详情轮询。
- 正式版与动态版共用同一套逻辑;仅"详情通道"不同(webServer 路由 vs. 动态 RPC),见 `docs/ROUTE-B.md`。

## 常见问题

| 现象 | 处理 |
|---|---|
| 什么提示都没有 | 确认确实存在**可操作**阻塞(提问/审批/审阅;沙箱拒绝已静默);动态版确认 `cordis_inspect_self` 里 host/client 均 running;刷新页面重试 |
| 只有闪烁、没有横幅 | 横幅数据来自 `useSessions` 的 `{ ids, byId }` 结构,确认用的是 **pkg-4 / v0.2.0 及以后**的版本 |
| 只有横幅、没有闪烁 | 闪烁是 CSS 动画(`span[data-state=warning]`),确认用的是 **pkg-3 / v0.2.0 及以后**的版本 |
| 动态版重启后失效 | 正常——动态插件是进程内存态;改走方式二(正式安装) |
| 正式安装后 GUI 看不到 | 新增依赖/层需重启 `dsh web`;插件列表在设置页,重新打开刷新 |
| `dsh plugin add github:...` 第一次失败 | 包有 `prepare` 构建脚本(tsdown),pnpm 需要授权:把报错给出的包键加进 `$DSH_HOME/profiles/web/pnpm-workspace.yaml` 的 `allowBuilds` 后重跑;`lib/` 已提交,也可用 npm 包或 `pnpm pack` tarball 安装(预构建,无需授权) |

## 开发与构建

- `src/` 是 **TypeScript 源码**(按源码仓官方方法编写);`lib/` 是 **tsdown 构建产物**(已提交)——`pnpm build`(tsdown,复刻官方 `clientBundle` 约定)从 `src/` 生成 `lib/index.js`(ESM 命名导出插件)与 `lib/client.js`(官方 `window.__ModuleLoader__.load({ id, factory })` 格式 web bundle)。
- 提交 `lib/` 使 git 安装无需构建授权;`prepare` 脚本(= tsdown)会在 git 安装时重建产物。
- 离线冒烟测试:`node scripts/smoke.mjs`(校验两半导出、`inject`、事件监听、路由 JSON、CSS 注入、槽位注册、渲染、中文文案)。
- `dynamic/` 是动态插件函数体(方式一使用),与 `lib/` 逻辑一致、通道不同。
- 转正路线 B 的完整调研与实施记录:`docs/ROUTE-B.md`;新部署启用指南:`docs/ENABLE.md`。

## 源码结构

```
dsh-blocker-notify/
├── LICENSE               # MIT
├── package.json          # bundle(dsh.bundle)+ client(dsh.client)双 manifest
├── README.en.md          # English readme
├── cordis.patch.yml      # bundle patch:插入 Host 行
├── src/                  # TypeScript 源码(源码仓官方方法)
│   ├── index.ts          # Host 半(ESM name/inject/apply + webServer 路由)
│   └── client/index.ts   # Client 半(构建为 ModuleLoader web bundle)
├── tsdown.config.ts      # 构建配置(复刻官方 clientBundle 约定)
├── tsconfig.json
├── lib/                  # tsdown 构建产物(已提交)
│   ├── index.js          # Host 半
│   └── client.js         # Client 半(ModuleLoader 格式)
├── scripts/
│   └── smoke.mjs         # 离线冒烟测试
├── dynamic/              # 动态插件函数体(方式一)
│   ├── host.js
│   └── client.js
└── docs/
    ├── ENABLE.md         # 全新部署启用指南
    └── ROUTE-B.md        # 转正路线 B 实施记录
```

## License

[MIT](LICENSE)

Install

dsh plugin --profile web add github:Frost-Reed/blocker-notify

Profile: web

  • This package builds from source on install. pnpm will ask you to allow its build script — that is permission to run the package’s code on your machine, outside the agent sandbox. Only allow sources you trust.
  • This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.
Source