派生插件开发指南
August 16, 2026 · View on GitHub
派生插件(derived plugin)是依赖 dsh-notifacation-frame 的普通 DSH 插件:它不接触通知的配置持久化、通道分发或 UI,只声明通知什么和配置哪些选项。本文说明注册契约,重点解释派生插件配置项如何被解析。
1. 三步接入
// ① 获得类型(包括 ctx.notificationFrame 的 Context 增广)
import type { NotifierDefinition, NotificationFrameService } from 'dsh-notifacation-frame'
export const name = 'my-notifier'
// ② 等待框架服务挂载(组合行顺序无所谓,cordis 注入解析会等待)
export const inject = ['notificationFrame'] as string[]
// ③ apply 里注册(ctx.notificationFrame 来自框架的 ctx.provide)
export function apply(ctx: MyCtx): void {
const dispose = ctx.notificationFrame.register(myDefinition)
ctx.effect(() => dispose) // 插件卸载时反注册
}
注意:DSH 的 cordis
Context.on按 Events 表做键控泛型,第三方事件名不在表内。 派生插件应像dsh-pref-kit一样自声明窄面结构类型(见 examples/dsh-notif-demo/src/plugin.ts 的DemoCtx)。
2. 通知项定义(NotifierDefinition)
| 字段 | 必填 | 说明 |
|---|---|---|
id | ✅ | 全局唯一(设置文档以它为 key)。重复注册会先 dispose 旧实例。 |
title / description | ✅ | 设置卡片标题与说明。 |
severity | ✅ | info / success / warning / error(toast 与系统通知的样式)。 |
channels | ✅ | 该项允许的通道子集(卡片只渲染这些复选项)。 |
defaultChannels | ✅ | 用户未配置时的默认通道。 |
defaultEnabled | ❌ | 缺省 true;false = 默认关闭(参考内置 tool-error)。 |
defaultSound | ❌ | 用户未配置时的默认音效预设(none/ding/pop/chime/alert/custom,缺省静音;非法值回落静音)。 |
fields | ❌ | 派生插件的配置项(见下)。 |
setup(env) | ✅ | 激活回调:注册事件监听,env.notify(...) 投递通知。 |
const myDefinition: 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', default: 'a',
options: [{ value: 'a', label: 'A' }, { value: 'b', label: 'B' }],
},
{ key: 'enabledExtra', label: '附加提醒', type: 'boolean', default: false },
],
setup(env) {
const ctx = env.ctx as MyCtx
return ctx.on('some/event', (payload) => {
env.notify({
title: 'Something happened',
body: `value=${payload.v},当前阈值 ${String(env.config.minValue)}`,
sessionId: payload.sessionId, // 可选:toast 提供“跳转会话”
meta: { tool: 'some-tool' }, // 可选:标量元数据
})
})
},
}
3. 配置项如何被解析(核心契约)
一条完整的解析链路:
用户设置文档 blob
{ enabled, channels, options: { minValue: 99, greet: 42, mode: 'zzz' } }
│
▼ resolveBlob(def, blob) (src/shared.ts)
enabled —— 非 boolean → def.defaultEnabled ?? true
channels —— 白名单过滤(web/system/log)+ 去重;非数组 → defaultChannels
options —— parseOptions(def, blob.options)
│
▼ parseOptions 逐字段执行 coerceField (src/shared.ts)
boolean —— 非 boolean → 字段 default(无 default → false)
number —— 非有限数 → 字段 default;随后钳制到 [min, max]
string —— 非 string → 字段 default
select —— 值必须在 options 白名单内,否则回落第一个选项
│
▼ 框架重激活该通知项
dispose 旧 setup → env.config = 解析后的 options → setup(新 env)
要点:
- settings 文档里存的是原始 JSON。框架自己的 settings schema 只声明
{ items: { [id]: { enabled, channels, options } } },其中options是 完全开放的z.dict(z.any())——派生插件的字段不进框架 schema,框架 无法也不会替派生插件理解它们。 - 解析发生在框架侧,时机是“激活前”:注册时、用户改卡片时、外部编辑
settings 文档被 watch 到时,框架都会重新
resolveBlob。因此setup(env)拿到的env.config永远是合法值——类型正确、范围已钳制、 缺失项已补默认——派生插件无需任何校验代码。 - 配置修改热生效:
updateItem先把解析后的干净值写回设置文档 (持久化),再 dispose 旧 setup、用新配置重跑 setup。派生插件不用订阅 任何 settings 事件。 - 字段元数据驱动卡片:
fields同时是设置卡片的渲染元数据 (boolean→开关、number→数字输入带 min/max、string→文本、select→下拉) 和解析规则。一个字段一处声明,两端一致。 - setup 抛错不击穿:异常被框架捕获、标记在卡片上(
setupError), 通知项仍在目录中,其他通知项不受影响。
4. setup 的生命周期与纪律
setup(env)在以下时机被调用:注册后、用户修改配置后、外部文档变化被 reconcile 后。返回的 disposer 由框架保管并在下一次激活前调用。env.ctx是框架插件的 cordis 上下文:ctx.on的事件监听会随框架 fiber 清理,但不要在 setup 里用ctx.provide或注册全局服务。- 事件监听用
ctx.on(...)返回 disposer;process.on之类全局监听必须 自己配对process.off(参考内置process-crash的写法)。 env.notify遵守当前生效配置:项被禁用时静默丢弃,通道按卡片选择分发。 需要旁路开关的场合用服务面的dispatch()(如其他宿主插件的直接调用)。
5. 服务面(ctx.notificationFrame)
interface NotificationFrameService {
register(def: NotifierDefinition): () => void // 注册(或覆盖),返回 disposer
list(): NotifierItemView[] // 全部通知项 + 生效配置
dispatch(payload): void // 直接投递(旁路开关)
history(limit?): NotificationRecord[] // 最近通知(新在前)
updateItem(id, blob): Promise<void> // 改配置并热生效
test(id): void // 经该项通道发测试通知
}
6. 参考
- 完整示例(可独立安装运行):examples/dsh-notif-demo
- 内置项写法(含 process 级监听、工具名过滤):src/host/builtins.ts
- 解析实现的单测:tests/shared.test.ts 与 tests/registry.test.ts