Bundle
dsh-notifacation-frame
DSH notification framework: unified notification event management, per-item config cards on the Settings page, built-in notifiers, and a registration API for derived plugins.
- Source
- gameswu
- stars
- 6 stars
- License
- MIT
- Updated
- Updated 2 hours ago
Readme
# dsh-notifacation-frame
DSH 通知框架:统一管理通知事件,允许派生插件注册自己的通知项,并在设置页为每个通知项提供配置卡片。内置了会话任务完成、agent 提问、进程崩溃与若干报错事件的通知实现。
> **DSH 0.1.5-rc.1 兼容(v0.4.0)**:要求 DSH >= 0.1.5-rc.1。
> 平台基线仍提供 `@deepseek-ai/dsh-client-ui-primitives`,但本插件选择在
> `src/client/ui.ts` 本地供应商化 Button / Input / Pill / StateDot / Toast
> (样式只经 `--dsw-*` 主题令牌),让客户端 bundle 自足、避免跟随不同 rc
> 版本的原语 prop 面变化。客户端模块图注入
> `@deepseek-ai/dsh-api-session-controller`(会话跳转)与
> `@deepseek-ai/dsh-client-ui-renderer`(`slots` 服务);
> `settings.section` / `shell.overlay` 插槽在 0.1.5 未变。
> `settingsNamespace` 助手自 0.1.2-alpha.2 起不再从
> `@deepseek-ai/dsh-settings` 导出,本插件的设置命名空间直接使用字面量
> `dsh-notifacation-frame`。提问通知在**提问呈现时**触发:新版走
> `user-questions/request` 瀑布、旧版走 `tools/pre-execute` 工具名过滤,
> 同一会话 3s 内去重(不再等到用户回答完才弹)。
> **Agent Teams**:teammate 属于子会话,`session-complete` 的 `rootsOnly`
> 默认经 `agents.roots()` 过滤,不打扰 teammate 完成;通知历史的“跳转”
> 优先走 `sessions.subagentAddress()` / `openSubagent()`,未知或已失效会话
> 安全降级为 no-op。平台新增的 `@deepseek-ai/dsh-client-ui-dockkit` 与本插件无关。
## 功能
- **统一通知事件管理**:所有通知项(内置 + 派生插件)走同一注册表、同一配置模型、同一投递管线(通道分发 + 历史环形缓冲)。
- **设置页配置卡片**:Settings → 通知 页为每个通知项渲染一张卡片——启用开关、通道复选(网页内提示 / 系统通知 / 日志)、音效选择(内置合成音效 / 自定义 URL + 试听)、由字段元数据驱动的派生插件配置项、测试按钮、最近通知历史。修改热生效,无需重启。
- **自定义音效**:每个通知项可独立配置音效——内置 4 种简短合成音效(叮 / 啵 / 钟声 / 警报,Web Audio 实时合成、零资源文件)或自定义音频 URL;通知横幅出现时播放,测试按钮可试听。
- **派生插件支持**:派生插件只需 `inject: ['notificationFrame']` 并调用 `ctx.notificationFrame.register(definition)`;`definition.fields` 声明的配置项由框架解析、校验、补默认后经 `env.config` 传入,配置修改时框架自动重激活(先 dispose 旧 setup,再以新配置重跑)。详见 [派生插件开发指南](docs/derived-plugins.md) 与 [examples/dsh-notif-demo](examples/dsh-notif-demo)。
- **页内 toast + 系统通知 + 日志**:host 经 WebSocket 推送通知到浏览器(DSH 原生 Toast 横幅,逐条队列展示);Windows / Linux / macOS 系统通知尽力而为(带冷却防刷屏);可选通知日志文件。
### 内置通知项
| 通知项 | 事件 | 默认通道 | 默认音效 | 默认状态 |
|---|---|---|---|---|
| `session-complete` | 会话 agent 完成任务(`agent/status` running→idle,可只通知顶层会话) | web | 钟声 | 开 |
| `ask-user-question` | agent 调用询问问题工具(`user-questions/request` 呈现即触发;旧版 `tools/pre-execute` 工具名过滤,正文可带问题) | web + system | 叮 | 开 |
| `process-crash` | dsh 进程意外中止(`uncaughtException` / `unhandledRejection` / 非零退出码) | system + log | 警报 | 开 |
| `agent-error` | agent 的 step/turn 报错(`agent/error`) | web + log | 警报 | 开 |
| `agent-loop-config-start-failed` | 声明式 agent 启动失败 | web + system | 警报 | 开 |
| `tool-error` | 工具调用失败(`tools/result` isError,可按名单排除噪声工具) | web | 啵 | **关**(工具失败通常由模型自愈,按需开启) |
| `dsh-update` | dsh 包新版本检查(npm registry dist-tags 对比本机安装版本,启动 + 每 24h 周期) | web + system | 叮 | 开 |
报错事件的选择理由:`agent/error`、`agent-loop/config-start-failed` 直接影响任务成败,默认通知;`tool-error` 高频且模型通常自动重试,默认关闭;`agent/request-error`(单次请求失败、会重试)过于嘈杂,不接入。
## 安装
```bash
# 1. 构建插件模块
cd dsh-notifacation-frame
pnpm install
pnpm run build # 产出 lib/index.js(插件入口)+ lib/*.d.ts + lib/client.js(浏览器 bundle)
# 2. 将包装入目标 profile(以 web 为例)——一步完成,无需手改任何配置:
# dsh plugin 是 pnpm 转发器:安装后自动把声明了 dsh.bundle 的依赖登记进
# profile 的 dsh.profile.bundles,启动时按 bundle 顺序合并包自带的
# cordis.patch.yml(挂载行就在本仓库根目录的 cordis.patch.yml 里)
dsh plugin --profile web add "file:$(pwd)"
# 3. 重启 DSH 生效(启动日志出现 [dsh-notifacation-frame] notification framework active)
```
profile 自己的 `cordis.patch.yml` 是**覆盖层**,不是挂载行——只有当你需要覆盖
框架级配置(如 `logFile`)时才在里面按 id 重述该行:
```yaml
- id: notifacation-frame
config:
logFile: /path/to/notifications.log
```
卸载:`dsh plugin --profile web remove dsh-notifacation-frame`(bundles 登记同步移除)。
## 配置
| 配置项 | 位置 | 默认 | 说明 |
|---|---|---|---|
| 各通知项开关 / 通道 / 音效 / 派生字段 | Settings → 通知 页卡片 | 见内置表 | 热生效,写入用户设置层 |
| `logFile` | 组合行 config | `''` | 通知日志文件(`''` = 仅控制台) |
| `historyLimit` | 组合行 config | 100 | 内存历史环形缓冲长度 |
| `systemCooldownMs` | 组合行 config | 5000 | 系统通知冷却(防刷屏) |
| `systemTimeoutMs` | 组合行 config | 8000 | 系统通知气泡展示时长 |
| `webSocketPath` | 组合行 config | `/notification-frame/ws` | host→client 推送路径 |
### 音效
每张通知卡片带音效选择:`静音`(默认)/ `叮`(双音提示)/ `啵`(短促提示)/
`钟声`(三音上行)/ `警报`(双短警告)/ `自定义 URL`(音频文件,如
`https://…/sound.mp3`)。内置预设由客户端 Web Audio API 实时合成,无资源文件;
卡片上的「试听」按钮即时预览,通知横幅出现时按该项配置播放。自动播放策略下
AudioContext 可能被浏览器挂起,首次手势后自动恢复;播放失败静默,不影响通知。
组合行 `config.*` 是框架级 base 层;各通知项的用户配置存在 settings 命名空间
`dsh-notifacation-frame`(`{ items: { [id]: { enabled, channels, options, sound, soundUrl } } }`),
设置卡片经 Host 的 fenced JSON 路由 `/notification-frame/api` 读写(与
dsh-plugin-vscode-sidebar 的 `/sidebar/api` 同款通道:POST + `{ok, value|error}`
信封,信任栅栏与 /api 网关同源)。
## 派生插件(一分钟上手)
```ts
// 派生插件 host/plugin.ts
import type { NotifierDefinition } from 'dsh-notifacation-frame'
export const name = 'my-notifier'
export const inject = ['notificationFrame'] as string[]
const myItem: NotifierDefinition = {
id: 'my-event',
title: '我的事件',
description: '某个事件发生时提醒我。',
severity: 'info',
channels: ['web', 'system', 'log'],
defaultChannels: ['web'],
fields: [
{ key: 'minValue', label: '最小阈值', type: 'number', default: 10, min: 0, max: 100 },
{ key: 'greet', label: '问候语', type: 'string', default: 'hello' },
{ key: 'mode', label: '模式', type: 'select', options: [{ value: 'a', label: 'A' }, { value: 'b', label: 'B' }], default: 'a' },
],
setup(env) {
// env.config = { minValue, greet, mode } —— 已解析/校验/补默认,直接使用
const ctx = env.ctx as MyCtx
return ctx.on('some/event', (payload) => {
env.notify({ title: 'Something happened', body: 'value=' + payload.v })
})
},
}
export function apply(ctx: MyCtx): void {
ctx.effect(() => ctx.notificationFrame.register(myItem))
}
```
`fields` 就是派生插件的配置项:设置卡片自动渲染对应控件,用户修改后框架用新配置重跑 `setup`(热生效)。完整的解析规则、数据类型、生命周期与示例见 [docs/derived-plugins.md](docs/derived-plugins.md) 和 [examples/dsh-notif-demo](examples/dsh-notif-demo)。
## 项目结构
```
dsh-notifacation-frame/
├── package.json # main = lib/index.js;dsh.client = web bundle
├── tsconfig.build.json # 宿主声明构建(tsc:lib/*.js + lib/*.d.ts,标准装饰器语义)
├── tsdown.config.ts # 客户端打包(lib/client.js + *.svg?raw 虚拟模块内联)
├── cordis.patch.yml # bundle 挂载层(插入框架行)
├── assets/
│ └── icons/ # 图标资源(bell-outline-16.svg 等,随包发布)
├── src/
│ ├── index.ts # 宿主打包入口(再导出 host/plugin.js 的类型与运行时面)
│ ├── shared.ts # 线缆模型:类型 + 派生配置解析(同构,纯函数)
│ ├── host/
│ │ ├── plugin.ts # 入口:Config / settings 命名空间 / fenced JSON API / WS 推送
│ │ ├── registry.ts # 通知注册表(注册、配置热生效、投递过滤、历史)
│ │ ├── channels.ts # 通道中枢(web 广播 / log 文件 / 系统通知)
│ │ ├── builtins.ts # 7 个内置通知项
│ │ ├── update-check.ts # dsh 新版本检查(semver 比较 + registry dist-tags)
│ │ ├── wire.ts # JSON API 线缆助手({ok, value|error} 信封,fenced 路由用)
│ │ ├── trust-fence.ts # WS/API 信任栅栏(回环 / --trusted-host)
│ │ └── types.ts # 宿主结构类型 + ctx.notificationFrame 增广
│ └── client/
│ ├── entry.ts # 设置页卡片(settings.section)+ Toast 横幅 + WS 客户端 + 导航图标覆盖
│ ├── ui.ts # 本地供应商化的 DSH 原子(Button/Input/Pill/StateDot/Toast,--dsw 令牌)
│ ├── sounds.ts # 内置音效合成(Web Audio,零资源文件)与自定义 URL 播放
│ └── vendor.d.ts # *.svg?raw 模块声明
├── examples/
│ └── dsh-notif-demo/ # 派生插件完整示例(可独立安装)
├── docs/
│ └── derived-plugins.md # 派生插件开发与配置解析指南
└── tests/ # vitest:解析契约 / 注册表 / 通道 / 内置项行为 / 入口集成
```
> 构建管线与 dsh-plugin-vscode-sidebar 同源:宿主 JS 由 tsc 产出(`lib/` 下
> 与 .d.ts 并排,`@Remote` 装饰器按标准语义编译),客户端由 tsdown 打包为
> module-loader closure。
## 开发
```sh
pnpm install
pnpm run typecheck # 严格模式类型检查(含示例 workspace)
pnpm run build # 编译宿主 + 客户端 bundle
pnpm run test # vitest(71 个用例:配置解析、注册表热生效、通道、内置项、入口集成)
```
## 文档
- [派生插件开发指南](docs/derived-plugins.md)
- [派生插件示例](examples/dsh-notif-demo/README.md)
- 官方插件开发文档:https://deepseek-harness.github.io/deepseek-harness/develop/basic/
## 许可证
[MIT](LICENSE) © 2026 gameswu
Install
dsh plugin --profile web add github:gameswu/dsh-notifacation-frame
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install dsh-notifacation-frame from the hub
- 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.