← 返回列表
⚠ 装前注意
DSHDeepSeek Harness会话完成提醒插件 —— 每一轮结束,让完成状态主动找你,而不是你盯着屏幕等。
基本兼容但装前注意:未发布到 npm registry,仅可从源码安装 · 最近上游提交 2026/9/14 · 已提供中文文档
当dsh任务完成/出错/提问等情况出现时,自动推送windows消息进行提醒,支持针对不同场景自定义文案和图片,并可在推送内容中显示会话用时、消耗token、速度tps等指标。Windows desktop notifications for DSH tasks — get alerted on completion, errors, or when input is needed, with per-scenario text and images, plus session duration, token usage, and TPS stats.
综合分
35.4
GitHub 分
35.4
用户评分
—
★ Stars
1
周下载量
—
安装插件(需先安装 dsh CLI 引擎:npm install -g @deepseek-ai/dsh)
dsh plugin --profile web add TelosmaYLX/dsh-session-notify未发布到 npm registry,仅可从源码安装,改用 GitHub 源安装
数据截至 2026/9/18(元数据每日更新 · 实装验证按队列轮转,单条结论的验证时间见上方)
安装兼容性检查⚠ 装前注意
以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。
✗npm 包@telosmaylx/dsh-session-notify(未发布到 npm,仅可源码安装)
✓Node 引擎要求 >=22 · 基线 Node 22.19 满足
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明
未发布到 npm registry,仅可从源码安装
验证方式:npm registry 存在性 + package.json 静态校验 · 最后验证 2026/9/18 16:16:13
用户评分
还没有人投票,来当第一个
订阅周报,不错过优质插件更新
每周一封 · 高评分插件 + 新用户活动
README
dsh-session-notify
简体中文 · English · 繁體中文 · 日本語 · 한국어
DSH(DeepSeek Harness)会话完成提醒插件 —— 每一轮结束,让完成状态主动找你,而不是你盯着屏幕等。
npm version
npm downloads
license
node
DSH
PRs Welcome
每轮对话结束时,把「已完成 / 出错 / 被阻塞 / 达到上限」连同用时、token 消耗写入会话日志,并推送浏览器系统通知与页内 toast(窗口失焦 / 聚焦可分别指定通道,两路都含「不通知」);AI 向你提问、请求审批时同样立即弹窗提醒,不必守着会话页面。内置 5 种语言、4 套风格预设(颜文字 / 艾露猫 / 猫娘 / DeepSeek 娘)、可视化文案模板编辑器、自定义预设库,缓存命中率与生成速度取自官方投影,与状态栏同口径。
目录
- 功能特性
- 环境要求
- 安装
- 卸载
- 快速开始
- 通知行为
- 触发条件
- 推送正文从哪来
- 通知示例
- 通知权限
- 配置
- 设置面板
- 文案模板与占位符
- 预设系统
- 宿主配置项
- 工作原理
- 项目结构
- 开发与调试
- 常见问题
- 更新日志
- 致谢
- 贡献
- 相关链接
- 许可证
功能特性
三通道提醒,一条不漏
| 通道 | 形式 | 说明 |
| --- | --- | --- |
| 会话内系统消息 | 可折叠提示行 | 每轮结束把结束原因与用时、消耗作为插件来源的系统消息追加进会话日志,随 JSONL 落盘,恢复或回放会话后依然可见。 |
| 浏览器系统通知 | Web Notification | 原生弹窗。每次完成事件使用独立 tag(dsh-session-notify:),不与前一次互相替换,也不被折叠成一个分组条目;点击通知聚焦回窗口。 |
| 页内 toast | 右下角浮动弹窗 | 永远展示的保底通道:系统通知被平台静默、权限拒绝或环境不支持时仍有可见反馈。同屏最多 3 条(超出移除最旧),10 秒自动消失,点击关闭。 |
[!NOTE]
后两条通道按窗口聚焦状态分流:设置面板中「失焦时」「聚焦时」各有一个下拉,可选 双通道 / 仅系统通知 / 仅页内提示 / 不通知。判定失焦的条件是 document.visibilityState === 'hidden' 或 !document.hasFocus(),也就是标签页被切走、窗口被最小化或点到别处时,走「失焦时」那一路。
后台会话全覆盖
- 宿主为所有会话(含后台、未打开窗口的)维护「最近一条通知正文」的会话投影单元(key = session-complete-notify),推送正文跨会话一致,不依赖你恰好开着那个窗口。
- 客户端从会话列表快照观测所有会话的 running 位,true → false 边沿即触发推送,与官方 sidebar 提醒同策略(首次观测只记录基线,已在 idle 的会话不补发)。
提问即时提醒
- AI 调用 ask_user_question 向你提问时,宿主立刻把「提问标题 + 正文」写入独立投影单元(key = session-complete-notify-question),客户端实时轮询并弹窗提醒——即使你正看着别的页面,也不会错过提问。
- 提问文案完全可定制:标题走「按原因定制标题 → 全局标题 → 默认标题」链路,正文支持 {question} 占位符(注入 AI 的实际提问),媒体开关 {image} / {icon} 同样生效。
审批即时提醒
- 会话请求权限审批时(approval/asked)立刻提醒,approval/decided 后失效——切到别的标签页也不会漏掉审批。
- 三路信号兜底:harness 原生 pendingInteractions(宿主提供时最准)→ 宿主审批投影(key = session-complete-notify-approval,标题与正文由宿主按当前语言渲染)→ 会话列表快照的 pendingInteraction === 'approval'。
- 文案只含工具名与可选原因(如「会话请求使用 Bash,请前往审批。(原因:…)」),绝不含命令参数等敏感内容;推送通道与媒体设置同样生效。
可定制到每一句话
- 5 种语言:简体中文、繁體中文、English、日本語、한국어 —— 通知文案、时长与用量措辞、设置面板界面全部随语言切换(切换即时重渲染)。
- 可视化模板编辑器(Chip 胶囊编辑器):动态信息渲染为内联胶囊(占位符代码不露出),「+ 插入信息」在光标处插入(可插到文字中间),点击胶囊移除,每栏带实时预览(信息以示例值流入正文)。
- 预设系统:内置「默认」基线 + 4 套一键风格预设(颜文字 / 艾露猫 / 猫娘 / DeepSeek 娘——标题与 5 结束原因 + 提问正文整套风格化文案);当前配置可另存为自定义预设(localStorage 持久化),支持自动编号的未命名预设(未命名、未命名 2…)、「来自:xxx · 已修改」来源指示、删除预设。
- 推送标题模板:留空时各原因用默认标题(完成=任务已完成 / 出错=任务出错 / … / 提问=AI 正在向你提问);{title} 引用会话标题。
与官方口径同源
- 缓存命中率取自官方 tokenUsage 投影:缓存读 /(未缓存输入 + 缓存读 + 缓存写)。
- 生成速度取自官方 sessionStats 投影:输出 token ÷ 解码耗时。
- 两者与 dsh-web-ui 状态栏完全同口径,不含排队、准备、工具时间;投影不可用或数据未就绪时自动退回本地用量聚合估算。
[!NOTE]
缓存命中率与速度只在自定义模板中通过 {cache}、{tps} 占位符插入时才显示。使用内置默认文案时,正文不含用时与消耗(要显示数据需在自定义模板中插入对应占位符)。
工程质量
- 只响应实时事件:resume、replay 不重放旧通知,加载会话不刷屏。
- 自免疫循环:插件追加的消息类型(user/message)与自身监听目标(turn/)不相交。
- 零外部依赖:宿主平面零裸 import,UserMessage 按 dsh-llm 的 createUserMessage 契约手工构造;纯逻辑层(lib/core.js)零依赖,可独立测试。
- Cordis effect 纪律:重试定时器包装在 ctx.effect() 中并返回 clearTimeout disposer,注册随 fiber 卸载自动撤销,HMR 热重载安全。
- 安装即挂载:声明官方 dsh.bundle manifest,dsh plugin add 一条命令装完即用,无需手写 patch。
环境要求
| 依赖 | 要求 |
| --- | --- |
| DSH(DeepSeek Harness) | Web profile 部署。官方 base bundle 默认包含 @deepseek-ai/dsh-settings(设置命名空间)与会话投影,无需额外配置 |
| cordis | >=4.0.0-rc =22(宿主侧) |
| 浏览器 | 支持 Web Notification 则有系统通知;不支持、权限拒绝或被静默时由 toast 兜底 |
安装
[!WARNING]
裸 npm install 只会把包装进依赖树,不会注册插件 —— 这是 DSH 官方设计(npm install only adds the dependency; it does not register the plugin)。自动挂载的唯一官方途径是 dsh plugin add:它读取包内 dsh.bundle manifest(本插件自 0.1.3 起声明,指向仓库根 cordis.patch.yml)并自动应用。
方式一:dsh plugin add(推荐)
安装包的同时自动应用 cordis.patch.yml,把插件挂载进 profile 装配(host 事件订阅 + client 启动图注入)。
dsh plugin --profile web add @telosmaylx/dsh-session-notify
方式二:从 GitHub 仓库安装
dsh plugin add github:TelosmaYLX/dsh-session-notify
也可以在 DSH Web GUI 会话内执行:
dev_install_package github=TelosmaYLX/dsh-session-notify
方式三:本地目录热装配(开发用)
把路径换成你的克隆目录,在 DSH Web GUI 会话内执行:
dev_install_package dir=/你的/克隆目录/dsh-session-notify
方式四:npm 包手动安装
先打包:
npm pack @telosmaylx/dsh-session-notify
解压后指定目录安装(在 DSH Web GUI 会话内执行):
dev_install_package dir=/解压/目录/package
方式五:手动 cordis patch(不依赖安装器)
在 ~/.dsh/profiles/web/cordis.patch.yml 追加:
- insert:
- id: dsh-session-notify
name: '@telosmaylx/dsh-session-notify'
config: {}
[!IMPORTANT]
无论用哪种方式,装完都需要刷新一次浏览器页面 —— 客户端 bundle 通过 __DSH_BOOT__ 启动图注入。
卸载
一条命令移除插件及其挂载(自动从 cordis.patch.yml 移除 insert 条目):
dsh plugin --profile web remove @telosmaylx/dsh-session-notify
[!NOTE]
手动安装(方式四/五)的用户,需同步从 ~/.dsh/profiles/web/cordis.patch.yml 删除对应 insert 条目,再刷新页面。
卸载时自动清理的内容
插件实现了完整的生命周期收尾(Cordis effect 纪律),卸载/禁用/HMR 热重载时:
| 平面 | 自动释放的资源 |
| --- | --- |
| host | session/event 事件订阅、settings 命名空间、会话投影单元、设置注册重试定时器(ctx.effect 包装);置卸载标志抑制已调度的微任务追加 |
| client | 会话列表订阅、完成推送正文的轮询定时器、window.__dsch_notify_debug 调试钩子(按引用删除,防闭包泄漏)、页内 toast 容器 DOM |
卸载后保留的数据
- 设置配置(语言、文案模板)留在 settings 文档,重装后自动恢复;
- 自定义预设存于浏览器 localStorage(dsh-scn-custom-presets),重装后仍在;
- 历史会话中已追加的系统消息与 JSONL 日志不会被回滚(它们是会话数据的一部分,与官方侧边栏提示同语义)。
快速开始
1. 按上面任一方式安装并刷新页面。
2. 发起任意一轮对话,等它结束 —— 右下角弹出 toast、浏览器弹系统通知、会话日志里出现可折叠的系统提示行。
3. 首次收到完成事件时,浏览器会请求通知权限(每页只问一次),允许后后续完成都有系统通知。
4. 打开 设置 → 插件 → 会话完成提醒,切换语言、编辑文案模板、另存预设。保存后点「点击刷新」让宿主与客户端两侧重新读取,新配置即生效。
刚装好时,会话日志里会出现这样一行可折叠提示:
会话「重构登录模块」已完成(用时 1 分 12 秒,消耗 1,240 输入 / 3,560 输出)。
默认文案在「会话」后内嵌会话标题标签({title});会话无标题时自动退回「会话已完成」。
通知行为
触发条件
每轮对话结束(turn/end)时按结束原因判断,命中白名单即提醒:
| 结束原因 | 含义 | 默认 |
| --- | --- | --- |
| completed | 会话正常完成 | 提醒 |
| aborted | 会话中止 | 提醒 |
| blocked | 会话被阻塞 | 提醒 |
| error | 会话出错(附错误详情,超长截断) | 提醒 |
| max-tokens | 达到输出 token 上限 | 提醒 |
| interrupted | 中断(崩溃恢复后由持久化后端补写的孤儿轮次关闭标记) | 不提醒(可配置加入) |
子代理会话默认跳过(header.origin === 'subagent' 或 delegationDepth > 0)—— 子代理由父会话编排,逐轮提醒是噪音;可在宿主配置关闭跳过。
提问是独立通道,不走上面的白名单:AI 调用 ask_user_question 等待你回答时(tool/call 事件)立即提醒,tool/result 返回后提醒失效。提问不写会话日志,只弹通知。
审批也是独立通道:会话请求权限审批时(approval/asked)立即提醒,approval/decided 后失效。同样不写会话日志,只弹通知;标题与正文只含工具名与可选原因,不含命令参数。
推送正文从哪来
客户端在会话列表观测到 running: true → false 边沿时推送,正文按以下优先级获取(最长轮询 6 秒,400ms 间隔):
1. 宿主投影(key = session-complete-notify)—— 每个会话都有,后台会话同样拿到全文;
2. 会话事件窗口里的 notice 节点(kind=context + form=notice)—— 正在查看的会话,落盘后立即可用;
3. 降级 —— 「详情见会话内系统消息」+ 工作区信息(cwd 最后一段)。
提问提醒的正文同样优先取宿主投影(key = session-complete-notify-question,宿主已渲染好标题与正文),老宿主无该投影时客户端自行拼接标题与 {question} 文本兜底。
审批提醒按可用性三路取源:harness 原生 pendingInteractions → 宿主审批投影(key = session-complete-notify-approval)→ 会话列表快照的 pendingInteraction 字段;取到即提醒,同一审批只推一次。
通知示例
以下均由 lib/core.js 的 buildNotice 实际生成。默认文案统一为「会话「{title}」已xx,请点击查看。」句式(按结束原因差异用词;不含用时与消耗):
简体中文默认文案:
会话「重构登录模块」已完成,请点击查看。 ← 完成
会话「重构登录模块」已中止,请点击查看。 ← 中止
会话「重构登录模块」被阻塞,请点击查看。 ← 阻塞
会话「重构登录模块」达到上限,请点击查看。 ← 上限
会话「重构登录模块」出错,请点击查看。 ← 出错
会话无标题(titleValue 为空)时自动回退「会话已完成,请点击查看。」;用时/消耗/缓存命中/速度等数据只在自定义模板中通过 {duration} {usage} {cache} {tps} 占位符插入时显示。
自定义模板(在设置面板编辑,本例用到全部信息位):
{title} 干完了!用时 {duration},消耗 {usage},缓存命中 {cache},速度 {tps}
渲染结果:
重构登录模块 干完了!用时 3 分 25 秒,消耗 103,600 输入 / 35,600 输出,缓存命中 96.5%,速度 92 tok/s
五种语言的同一事件:
会话「重构登录模块」已完成(用时 3 分 25 秒,消耗 1,240 输入 / 3,560 输出)。
會話「重構登入模組」已完成(用時 3 分 25 秒,消耗 1,240 輸入 / 3,560 輸出)。
Session "重构登录模块" completed (took 3m25s, used 1,240 in / 3,560 out).
セッション「重构登录模块」完了(所要 3 分 25 秒、消費 1,240 入力 / 3,560 出力)。
세션「重构登录模块」 완료(소요 3분 25초, 소모 1,240 입력 / 3,560 출력)。
通知权限
| 权限状态 | 行为 |
| --- | --- |
| default(未决定) | 完成事件只发 toast;设置面板「通知权限」区提供「请求授权」按钮(用户手势内请求——Chromium 会忽略非手势的自动请求,因此插件不再自动请求) |
| granted | 按「失焦时」「聚焦时」各自所选通道发系统通知(独立 tag,互不覆盖;选「不通知」则不弹) |
| denied(被浏览器屏蔽) | 仅 toast;设置面板显示地址栏操作指引(权限图标 → 网站设置 → 通知 → 允许) |
| undefined(非安全上下文 / 不支持) | 仅 toast;建议改用「仅页内提示」 |
配置
绝大多数配置在 DSH Web UI → 设置 → 插件 → 会话完成提醒 面板完成(保存后点「点击刷新」生效)。仅「触发原因白名单」在宿主 cordis.patch.yml 的 config 中配置(跳过子代理在面板中以复选框控制)。
设置面板
面板在官方「设置 → 插件」面板中注册(settings.plugin.item keyed slot,key = session-complete-notify),样式逐值复刻原生插件卡片(12px 圆角、展开收起、旋转 chevron、footer 状态位 + 弃置 ghost + 主色保存按钮):
| 区域 | 内容 |
| --- | --- |
| 预设 | 下拉选择内置或自定义预设;「新增」把当前配置另存为自定义预设;当前预设可「删除」 |
| 语言 | 5 种语言单选,切换即时重渲染整个面板 |
| 失焦时 | 四选一:不通知(该时机完全静默)/ 双通道(系统通知 + 页内提示,默认)/ 仅系统通知 / 仅页内提示 —— 窗口失焦(切走标签页、最小化、点到别处)时生效 |
| 聚焦时 | 四选一:同上(默认双通道)—— 窗口聚焦时生效。两路互相独立,可自由组合成「失焦系统通知 + 聚焦不通知」等搭配 |
| 通知图片 | 大图两种来源:按原因上传——在模板中通过「+ 插入信息 → 图片」插入 {image} 标签并选择本地图片(编辑器内显示为带缩略图的标签,自动压缩至 512px 宽、按通知显示比例 16:9 居中裁切,随各原因独立保存);全局大图/图标——两张上传卡片并排一行(图标在前,空态 = 圆角矩形 + 号,点击上传;大图 512×288(16:9 居中裁切)、图标 128×128(1:1 方形居中裁切);已上传则卡片显示缩略图,点击缩略图可全屏查看完整原图(等比未裁切),右上角 × 删除)。图标留空用站点默认图标,也可在模板中插入 {icon} 标签按原因指定图标(优先于全局)。仅系统通知通道生效(页内 toast 为文字卡片),「发送」测试按钮同样生效 |
| 标题 | 折叠区(默认收起,点击展开):全局推送标题(所有原因共用,Chip 编辑器——点「+ 插入信息」插入的信息以胶囊标签形式显示,点击胶囊移除;占位提示「通用推送标题,留空时则使用默认标题,优先级低于下方自定义标题」(不可选中/删除);通知发送时标题里的信息位(用时/消耗/错误/缓存命中/速度)会替换为实际值,不再显示代码;留空时各原因用默认标题——完成=任务已完成、出错=任务出错、中止=任务已中止、阻塞=任务被阻塞、上限=任务达到上限、提问=AI 正在向你提问)+ 按原因定制标题(6 条原因各自输入,每行带「+」插入按钮——可插入信息标签(含「提问」,不含图片/图标),插入到光标处;优先于全局标题,留空 = 用全局或语言默认标题) |
| 内容 | 折叠区(默认收起,点击展开);展开后每条结束原因(完成、出错、中止、阻塞、上限、提问)一行式布局(原因标签 + Chip 编辑器 + 「+」插入按钮——菜单展开时变「−」+ 发送箭头按钮,按钮为矩形、垂直居中):空模板(默认预设)时编辑器显示默认文案「会话「{title}」已xx,请点击查看。」,文字 + 内联信息胶囊,光标处插入;{image}/{icon} 标签点击缩略图可预览大图、点 × 才删除(防误删),其他标签点击移除;编辑后删空则显示「留空则使用默认文案」占位(不可选中/删除);提问行的默认文案为「AI 向你提问:{question}」,{question} 会在发送时替换为 AI 的实际提问(插入菜单同样提供「提问」标签,与其他标签同款交互) |
| 跳过子代理会话 | 复选框(保存时一并写入设置文档) |
| 通知权限 | 状态实时显示:已授权(绿)/ 尚未授权(附「请求授权」按钮)/ 已被浏览器屏蔽(附地址栏操作指引)/ 环境不支持 |
| 按原因定制标题 | 折叠区(默认收起):每个结束原因一个独立标题输入框,留空 = 用全局模板或语言默认标题 |
| 保存 | 写入宿主设置文档(language / templates / titleTemplate / titleTemplates / pushModeBlur / pushModeFocus / skipSubagents);保存后显示「点击刷新」链接 |
| 重置 | 一键还原默认值(语言保留当前选择,标题/模板/失焦聚焦通道恢复默认)并立即保存 |
[!NOTE]
「失焦时」「聚焦时」的通道取舍:dual(默认)同时弹 Windows 系统通知与页内 toast,toast 是保底通道,防止系统通知被平台静默(专注助手、通知横幅关闭)。两路互相独立,可组合成「失焦时系统通知、聚焦时不打扰」。老配置不受影响——设置文档里没有 pushModeBlur / pushModeFocus 时,两路都沿用旧版单一 pushMode 的值。但 QQ 浏览器等国产 Chromium 壳浏览器会把 Notification 渲染成「浏览器内置的页内推送弹窗」(页面顶部/角落的横幅,不经 Windows 通知中心)——此时 dual 会造成页内两个提示(浏览器内置弹窗 + 插件 toast)。这类浏览器请选「仅页内提示」(不再调用 Notification,浏览器内置弹窗不会出现,页内只有插件自己的小 toast);「仅系统通知」模式在 QQ 浏览器无效(它永远渲染为页内弹窗)。设置面板每个原因的「发送」测试按钮同样受此影响——测试通知优先走「聚焦时」那一路,该路为「不通知」时改用「失焦时」那一路,两路都关则回落 dual,保证预览总有反馈。
[!NOTE]
系统通知(Notification API)能否弹出由浏览器与站点访问方式共同决定:Edge/Chrome 对"不熟悉"的站点会自动屏蔽通知(地址栏出现「通知已屏蔽」)——点击地址栏左侧权限图标 → 网站设置 → 通知 → 允许即可恢复;http://IP 这类非安全上下文访问时 Notification 根本不存在,请改用「仅页内提示」。设置面板「通知权限」区域会实时显示当前状态并给出对应操作指引(可一键请求授权)。Firefox 窗口聚焦时通知显示为页内横幅、失焦才进系统通知中心。
[!NOTE]
面板中「跳过子代理会话」保存的是设置文档里的布尔值;宿主 cordis.patch.yml 的 config.skipSubagents 是其启动默认值,两者任一为真即跳过。
文案模板与占位符
每条结束原因独立一个模板输入框,标签即开关 —— 在模板里插入对应信息标签,该项数据才会显示:
| 占位符 | 含义 | 示例值 |
| --- | --- | --- |
| {title} | 会话标题(推送标题模板也可用) | 重构登录模块 |
| {duration} | 本轮用时(turn/start 起表 → turn/end 结束) | 3 分 25 秒 / 3m25s |
| {usage} | token 消耗(输入 = 未缓存 + 缓存读 + 缓存写) | 1,240 输入 / 3,560 输出 |
| {error} | 错误信息(无错误时显示 none;单行化,80 字符截断) | connection timeout |
| {cache} | 缓存命中率(官方投影口径,无数据为空) | 96.5% |
| {tps} | 生成速度(官方投影口径,无数据为空) | 92 tok/s |
| {image} | 自定义通知大图开关:从「+ 插入信息」插入并选择本地图片(自动压缩至 512px),按原因独立;正文渲染时剥除,不进会话日志;删除标签时该原因图片数据一并清除 | — |
| {icon} | 自定义通知图标开关:从「+ 插入信息」插入并选择本地图片(自动压缩至 128×128 方形),按原因独立;正文渲染时剥除,不进会话日志;优先于全局「通知图标」;删除标签时该原因图标数据一并清除 | — |
| {question} | 提问行的专属占位符:发送时替换为 AI 的实际提问文本;与「+ 插入信息」菜单联动(可直接选「提问」标签,手输 {question} 同样识别为胶囊),仅提问通道可用,其他原因行插了也会被替换为空(防字面量泄漏) | 要继续生成报告吗? |
| {label} | 已废弃 —— 渲染时自动剥除,旧模板仍兼容(插入菜单已移除该选项) | — |
模板留空即使用内置默认文案(「会话「{title}」已xx,请点击查看。」句式,不含用时与消耗)。折叠行 summary 与正文同源(渲染结果截断至 120 字符)—— 只看折叠行的用户也能看到真实标题与用时、消耗。
预设系统
- 内置预设:仅「默认」,作为基线。
- 自定义预设:保存在 localStorage(key = dsh-scn-custom-presets):
- 「新增」命名后保存为自定义预设;保存后可「修改」自动同步、「删除」移除;
- 自动编号的未命名预设:从「默认 / 空白」直接保存时,自动生成 未命名、未命名 2、未命名 3…(编号取当前最大值 + 1);
- 表单显示「来自:xxx · 已修改」来源指示(来自预设但内容已改动时)。
- 保存即同步:保存时若表单来源是自定义预设则更新该预设,否则新建或继续编号未命名预设。
宿主配置项
- insert:
- id: dsh-session-notify
name: '@telosmaylx/dsh-session-notify'
config:
reasons: [completed, aborted, blocked, error, max-tokens]
skipSubagents: true
| 字段 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| reasons | string[] | [completed, aborted, blocked, error, max-tokens] | 触发提醒的 turn/end 原因白名单 |
| skipSubagents | boolean | true | 跳过子代理会话(origin=subagent 或 delegationDepth>0) |
工作原理
插件分宿主平面(Node)与客户端平面(浏览器),中间靠会话日志(JSONL)与官方会话投影衔接:
┌─────────────────── 宿主平面(lib/index.js,Node)──────────────────┐
│ │
│ session/event 火线 │
│ ├─ turn/start → tracker 起表(key: sessionId:turn) │
│ ├─ assistant/message → 累加该轮 token 用量 │
│ ├─ tool/call → ask_user_question?写提问投影(标题+正文) │
│ └─ turn/end → reason.kind ∈ reasons ? │
│ ├─ 子代理会话?跳过 │
│ ├─ 读官方投影:cache / tps / title │
│ ├─ 按语言+模板构建通知(summary ≤120 字) │
│ └─ queueMicrotask 追加系统消息 │
│ (避开 append 重入窗口) │
│ │
│ settings.register → 官方「设置 → 插件」命名空间(失败退避重试) │
│ sessionProjections → 注册投影单元(key=session-complete-notify) │
│ + 提问投影(key=session-complete-notify- │
│ question,等待回答期间持续推送) │
└──────────────────────────────┬──────────────────────────────────────┘
│ user/message (source: plugin, form: notice)
▼ JSONL 持久化 + 投影推送
┌─────────────────── 客户端平面(lib/client.js,浏览器)──────────────┐
│ │
│ 会话列表订阅:running true → false 边沿 → pushCompletion │
│ ├─ 取正文:投影 → 事件窗口 notice → 降级(轮询 ≤6s) │
│ ├─ Web Notification(独立 tag,点击聚焦) │
│ └─ 页内 toast(永远展示,≤3 条,10s 自动消失) │
│ 提问投影轮询(key=session-complete-notify-question): │
│ 有值 → 立即弹提醒(标题+正文),无值清空 │
│ │
│ slots.inject('settings.plugin.item') → 设置卡片(预设/语言/模板) │
└─────────────────────────────────────────────────────────────────────┘
关键设计决策
- 不重放:只处理实时事件,resume、replay 不会补发历史通知。
- 无自我循环:插件追加 user/message,自身只监听 turn/,事件类型不相交。
- 零外部 import:插件从仓库目录以 realpath 加载,@deepseek-ai/* 无法裸解析 —— 宿主平面用 createRequire 锚定 profile 共享依赖枢纽(.dsh/profiles/node_modules)取 schemastery(设置 schema)与 zod(投影 schema);UserMessage 按 dsh-llm 契约手工构造(id = crypto.randomUUID(),deep-freeze 由 session.append 的 adopt 快照阶段完成)。
- append 重入规避:session/event 观察者回调运行在 turn/end 那次 append 的发布边界之内(dsh-session 在 dispatch 前置 entry.appending、finally 复位),同步 append 会被拒绝 —— 因此推迟到 queueMicrotask(微任务在本次同步栈含 finally 复位之后才执行)。
- effect 纪律:设置注册的退避重试定时器包装在 ctx.effect() 中并返回 clearTimeout disposer —— 插件在重试窗口内被卸载或热重载时定时器随 fiber 拆除,不会对已释放的 ctx 触发注册(极老环境无 ctx.effect API 时退化为裸定时器 + ctx 已拆除兜底捕获)。
- HMR 安全:core.js 导入带 ?v=1 缓存破坏(HMR 重载按 URL 键控);设置注册遇到热重载竞态(duplicate)时自动退避重试(最多 8 次,间隔 400ms × attempts)。
- 投影注册双轨:优先 ctx.root.get('sessionProjections')(最靠近宿主根的一份),拿不到时回退注入实例;只注册进注入实例时客户端可能读不到投影单元,推送正文走降级路径 —— 属尽力而为,不影响会话内系统消息。
项目结构
dsh-session-notify/
├── lib/
│ ├── index.js # 宿主平面(Node):session/event 订阅 → 系统消息落盘;
│ │ # settings 命名空间注册(schemastery schema,退避重试);
│ │ # sessionProjections 投影单元(后台会话推送正文)
│ ├── core.js # 纯逻辑层(零依赖,可独立测试):轮次计时与用量聚合、
│ │ # 5 语言文案表、时长/用量/缓存/速度格式化、
│ │ # 模板渲染({title}{duration}{usage}{error}{cache}{tps})、
│ │ # 提问正文构建(buildQuestionBody,{question} + 媒体剥除)
│ └── client.js # 浏览器平面:完成推送(系统通知 + toast)、
│ # 设置卡片(Chip 模板编辑器 + 预设系统 + 实时预览)
├── scripts/
│ ├── build.sh # 零构建:仅 node --check 语法校验
│ ├── verify-notice.mjs # 校验会话日志落盘证据(zstd 多帧逐帧解压)
│ ├── probe-client.mjs # 探针:客户端装配
│ ├── probe-client-e2e.mjs # 探针:客户端端到端
│ ├── probe-card-render.mjs # 探针:设置卡片渲染
│ ├── probe-settings-card.mjs # 探针:设置面板卡片
│ ├── probe-settings-check.mjs# 探针:设置面板检查
│ └── probe-diag-settings.mjs # 探针:settings 诊断
├── cordis.patch.yml # dsh.bundle manifest —— dsh plugin add 自动挂载的凭证
├── package.json # dsh.bundle(patch)+ dsh.client(web 注入)双 manifest;
│ # exports: "." / "./client" / "./core"
├── LICENSE # MIT
└── README.md # 本文档
开发与调试
语法校验(零构建,prepublishOnly 同款检查):
npm run build
发布(发布前自动执行 prepublishOnly 语法校验):
npm publish --registry=https://registry.npmjs.org --access public
离线校验:解出会话日志中所有 plugin-source 事件与 turn/end 尾部序列(不传路径则自动选 ~/.dsh/sessions 下最新会话):
node scripts/verify-notice.mjs
调试入口
| 入口 | 内容 |
| --- | --- |
| ~/.dsh/session-complete-notify.log | 宿主诊断日志:设置注册、重试与失败、投影注册、追加失败堆栈 |
| 浏览器 console [dsh-session-notify-client] | 客户端日志:权限状态、通知展示、设置保存 |
| window.__dsch_notify_debug.readNotice(id) | 手动读取指定会话的最新通知正文 |
| window.__dsch_notify_debug.snapshotDebug(id) | 会话尾部节点类型 + notice 数量 + 最近正文(前 200 字) |
常见问题
npm install 之后为什么不自动挂载?
这是 DSH 官方设计:npm install 只把包装进依赖树,不注册插件。自动挂载的唯一途径是 dsh plugin add —— 它读取包内 dsh.bundle manifest(本插件自 0.1.3 起声明)并自动应用 cordis.patch.yml。参见安装。
AI 向我提问时也会弹窗提醒吗?
会。AI 调用 ask_user_question 等待你回答时,宿主立刻把「提问标题 + 正文」写入独立投影(key = session-complete-notify-question),客户端轮询到后立即弹提醒——即使你正看着别的页面也不会错过。提问文案与完成通知一样完全可定制:设置面板的「标题 / 内容」折叠区各有「提问」一行,正文支持 {question} 占位符(注入 AI 的实际提问),{image} / {icon} 媒体开关同样生效。回答后(tool/result)提醒失效,不会残留。
为什么「中断」(interrupted)不提醒?
interrupted 是崩溃恢复后由持久化后端补写的孤儿轮次关闭标记,用户视角的「完成」不包含它(否则恢复会话会刷一屏误报)。确有需要可在宿主配置的 reasons 中加入。
后台会话(没打开窗口的)也会推送吗?
会。客户端从会话列表快照观测所有会话的 running 边沿;正文优先取宿主投影 —— 宿主为所有会话(含后台)维护投影单元,因此推送正文跨会话一致。投影不可用时降级为事件窗口或工作区信息。
保存设置后为什么提示刷新页面?
宿主在注册命名空间时读取一次设置,客户端 bundle 在页面加载时装配。保存后点「点击刷新」让两侧重新读取,新语言、模板即生效。
缓存命中率、速度数据从哪来?为什么有时是空的?
来自官方 sessionProjections(tokenUsage、sessionStats),与 dsh-web-ui 状态栏同口径。宿主读取投影快照失败或数据尚未就绪时,退回本地用量聚合估算,仍无数据则该项留空(标签插了也不显示)。另外,这两项只在自定义模板中通过 {cache}、{tps} 插入时才出现,默认文案不含。
通知正文里的错误信息太长、有换行怎么办?
摘要行(折叠行)与错误详情都会单行化并截断:摘要 120 字符、模板 {error} 80 字符、默认文案的错误详情 40 字符,超长以省略号结尾。
可以自定义系统通知的图标或声音吗?
图标可以自定义:设置面板「通知图片」区可上传通知大图与通知图标(全局),也可在各原因模板中插入 {icon} 标签为该原因单独指定图标(优先于全局);声音暂不支持自定义(沿用系统/浏览器默认),toast 为固定深色卡片。如有其他需求欢迎提 Issue 或 PR。
为什么 Edge 推不了系统通知?QQ 浏览器为什么只有页内横幅(内置推送弹窗)?
两者都是浏览器行为,插件无法强制:
- Edge / Chrome:对"不熟悉"的站点会自动屏蔽通知(地址栏出现「通知已屏蔽」)。点击地址栏左侧权限图标 → 网站设置 → 通知 → 允许即可恢复,之后正常弹 Windows 通知中心。也可在浏览器通知设置中关闭「自动屏蔽」。
- QQ 浏览器等国产 Chromium 壳:把 Notification 固定渲染为浏览器内置的页内推送弹窗(页面顶部/角落横幅,不经 Windows 通知中心),且无系统通知选项。「失焦时」「聚焦时」两个下拉在这类浏览器上表现一致:
- 双通道 → 浏览器内置弹窗 + 插件 toast,页内两个提示;
- 仅系统通知 → 无效(QQ 浏览器永远渲染为页内弹窗);
- 仅页内提示 → 浏览器内置弹窗不出现,页内只有插件自带的小 toast(推荐);
- 不通知 → 该时机完全静默。
设置面板每个原因的「发送」测试按钮同样按此规则渲染。
- Firefox:窗口聚焦时通知显示为页内横幅,失焦/最小化才进系统通知中心;权限需在地址栏手动允许。
- 另注意:http://IP 访问(非安全上下文)时 Notification 不存在,任何浏览器都弹不了系统通知。
设置面板「通知权限」区域会实时显示当前状态与对应操作指引。
更新日志
| 版本 | 日期 | 变更 |
| --- | --- | --- |
| 0.1.21 | 2026-09-14 | 推送通道按失焦/聚焦分流(PR #3 由 @YiHui-Liu 贡献):新增「失焦时」「聚焦时」两个独立下拉,各自可选 不通知 / 双通道 / 仅系统通知 / 仅页内提示;移除旧的单一「推送方式」设置项,未设置新项时两路自动沿用旧 pushMode 值(老配置行为不变);某时机设为「不通知」时该时机完全静默(提问与审批不记去重,页面切到另一时机后同一次事件仍会提醒;完成属边沿事件,静默即不补发);「发送」测试通知优先走聚焦那一路,其为「不通知」时改用失焦那一路 |
| 0.1.20 | 2026-09-09 | 审批即时提醒 + 历史会话加载修复:新增权限审批提醒(PR #2 由 @YiHui-Liu 贡献——approval/asked 投影 + 客户端三路信号兜底);修复 0.1.19 回归:投影注册契约改双代并存(schema/view 与 stateSchema/wire 同时注册),旧宿主打开历史会话不再因 undefined.parse 失败;客户端 uiSession 移出 inject 改 ctx.get 可选查找,避免服务缺席时通知与设置面板整体失效 |
| 0.1.19 | 2026-09-07 | 修复提问弹窗(宿主投影断链):投影单元注册迁移到 stateSchema + wire: { viewSchema, view } 契约——旧形状(顶层 schema/view)在新宿主(dsh-session-projection)下是 host-only 单元,值永不送达客户端,完成/提问投影均失效;tool/call 的 callId 为空串时(部分 OpenAI 兼容代理路由)回退 turn:step 作提问 id,tool/result 同步按 turn/step 匹配清除;顺带修复 stateSchema 缺失在投影 checkpoint restore 路径的潜在崩溃 |
| 0.1.18 | 2026-09-01 | 修复提问弹窗失效:部分 dsh 版本(0.1.2)宿主投影未送达客户端导致提问不弹窗;客户端提问推送新增 harness 原生「待提问」标记兜底触发,宿主投影缺失时仍提醒;完成推送/设置面板行为不变 |
| 0.1.17 | 2026-08-30 | 提问即时提醒(可定制):AI 提问立即弹窗;提问文案支持 {question} 占位符与媒体开关;4 套预设补齐 5 语言提问文案;旧宿主自动兜底 |
| 0.1.16 | 2026-08-30 | 交互修复:连按 Backspace 不再误删标签(仅当光标与标签间无文字时才删标签) |
| 0.1.15 | 2026-08-30 | 交互优化:「内容」折叠区默认展开;删除标签后光标直达真实内容,可连贯删除 |
| 0.1.14 | 2026-08-30 | 代码审查修复:删除预设确认、预设恢复为已保存配置、媒体「×」清除预览、按原因图片参与预设匹配、同名预设提示、重置仅在有修改时可用、调试日志自动截断 |
| 0.1.13 | 2026-08-29 | 新增 4 套一键风格预设(颜文字/艾露猫/猫娘/DeepSeek 娘),支持 5 语言 |
| 0.1.12 | 2026-08-29 | 发布包清理 |
| 0.1.11 | 2026-08-29 | 自定义通知媒体:模板可插 {image}/{icon} 并上传图片/图标(自动裁切);推送标题支持信息占位符;「正文模板 × 5」改折叠区,布局交互全面优化 |
| 0.1.10 | 2026-08-29 | 推送标题改原生输入框;新增多语言 README(English/繁體/日本語/한국어) |
| 0.1.9 | 2026-08-29 | 推送标题按原因定制;投影升级为对象;重置保留语言;每原因加「发送」测试按钮 |
| 0.1.8 | 2026-08-29 | 默认标题「任务已完成」;默认文案按结束原因差异化;新增重置按钮 |
| 0.1.7 | 2026-08-29 | 修复设置卡片崩溃(通知权限行作用域问题) |
| 0.1.6 | 2026-08-29 | 新增通知权限状态区;权限改为用户手势内请求 |
| 0.1.5 | 2026-08-29 | 新增推送方式(双通道/仅系统/仅页内),解决 QQ 浏览器双提示 |
| 0.1.4 | 2026-08-28 | 完整卸载支持(dispose 生命周期收尾) |
| 0.1.3 | 2026-08-28 | 声明 dsh.bundle manifest;settings 重试定时器改 ctx.effect() |
| 0.1.2 | 2026-08-27 | 包更名至 @telosmaylx scope |
| 0.1.1 | 2026-08-27 | GitHub、npm 安装方式文档化 |
| 0.1.0 | 2026-08-26 | 初始版本:会话内系统消息 + 浏览器推送 + 官方设置面板 |
致谢
感谢 @YiHui-Liu 的两份贡献:PR #2——权限审批即时提醒(approval/asked 投影 + 客户端三路信号兜底);PR #3——推送通道按失焦 / 聚焦分流,两路各含「不通知」。
贡献
欢迎 Issue 与 PR:
1. Fork 仓库并新建分支(feat/xxx)
2. 改动后运行 npm run build 做语法校验
3. 提交 PR,说明动机与验证方式
提交前请遵守 Cordis 开发教程 纪律:
- Cordis 之外的资源(定时器、订阅、watcher)必须包装在 ctx.effect() 中并返回 disposer;
- 配置项显式 id 防止编辑漂移;
- 插件须声明 dsh.bundle manifest 才能被 dsh plugin add 识别安装。
相关链接
- awesome-dsh-plugin —— DSH 插件精选列表(投稿规范:dsh.bundle 是安装唯一凭证)
- Cordis 开发教程 —— 插件开发全流程(01-07 章)
- npm 包主页
- GitHub 仓库
许可证
MIT © dsh-session-notify contributors扫码进群