dsh-session-notify
August 30, 2026 · View on GitHub
dsh-session-notify
简体中文 · English · 繁體中文 · 日本語 · 한국어
DSH(DeepSeek Harness)会话完成提醒插件 —— 每一轮结束,让完成状态主动找你,而不是你盯着屏幕等。
每轮对话结束时,把「已完成 / 出错 / 被阻塞 / 达到上限」连同用时、token 消耗写入会话日志,并推送浏览器系统通知与页内 toast;AI 向你提问时同样立即弹窗提醒,不必守着会话页面。内置 5 种语言、4 套风格预设(颜文字 / 艾露猫 / 猫娘 / DeepSeek 娘)、可视化文案模板编辑器、自定义预设库,缓存命中率与生成速度取自官方投影,与状态栏同口径。
目录
功能特性
三通道提醒,一条不漏
| 通道 | 形式 | 说明 |
|---|---|---|
| 会话内系统消息 | 可折叠提示行 | 每轮结束把结束原因与用时、消耗作为插件来源的系统消息追加进会话日志,随 JSONL 落盘,恢复或回放会话后依然可见。 |
| 浏览器系统通知 | Web Notification | 原生弹窗。每次完成事件使用独立 tag(dsh-session-notify:<timestamp>),不与前一次互相替换,也不被折叠成一个分组条目;点击通知聚焦回窗口。 |
| 页内 toast | 右下角浮动弹窗 | 永远展示的保底通道:系统通知被平台静默、权限拒绝或环境不支持时仍有可见反馈。同屏最多 3 条(超出移除最旧),10 秒自动消失,点击关闭。 |
后台会话全覆盖
- 宿主为所有会话(含后台、未打开窗口的)维护「最近一条通知正文」的会话投影单元(key =
session-complete-notify),推送正文跨会话一致,不依赖你恰好开着那个窗口。 - 客户端从会话列表快照观测所有会话的
running位,true → false边沿即触发推送,与官方 sidebar 提醒同策略(首次观测只记录基线,已在 idle 的会话不补发)。
提问即时提醒
- AI 调用
ask_user_question向你提问时,宿主立刻把「提问标题 + 正文」写入独立投影单元(key =session-complete-notify-question),客户端实时轮询并弹窗提醒——即使你正看着别的页面,也不会错过提问。 - 提问文案完全可定制:标题走「按原因定制标题 → 全局标题 → 默认标题」链路,正文支持
{question}占位符(注入 AI 的实际提问),媒体开关{image}/{icon}同样生效。
可定制到每一句话
- 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()中并返回clearTimeoutdisposer,注册随 fiber 卸载自动撤销,HMR 热重载安全。 - 安装即挂载:声明官方
dsh.bundlemanifest,dsh plugin add一条命令装完即用,无需手写 patch。
环境要求
| 依赖 | 要求 |
|---|---|
| DSH(DeepSeek Harness) | Web profile 部署。官方 base bundle 默认包含 @deepseek-ai/dsh-settings(设置命名空间)与会话投影,无需额外配置 |
| cordis | >=4.0.0-rc <5(peer dependency,由宿主提供) |
| Node.js | >=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 日志不会被回滚(它们是会话数据的一部分,与官方侧边栏提示同语义)。
快速开始
- 按上面任一方式安装并刷新页面。
- 发起任意一轮对话,等它结束 —— 右下角弹出 toast、浏览器弹系统通知、会话日志里出现可折叠的系统提示行。
- 首次收到完成事件时,浏览器会请求通知权限(每页只问一次),允许后后续完成都有系统通知。
- 打开 设置 → 插件 → 会话完成提醒,切换语言、编辑文案模板、另存预设。保存后点「点击刷新」让宿主与客户端两侧重新读取,新配置即生效。
刚装好时,会话日志里会出现这样一行可折叠提示:
会话「重构登录模块」已完成(用时 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 返回后提醒失效。提问不写会话日志,只弹通知。
推送正文从哪来
客户端在会话列表观测到 running: true → false 边沿时推送,正文按以下优先级获取(最长轮询 6 秒,400ms 间隔):
- 宿主投影(key =
session-complete-notify)—— 每个会话都有,后台会话同样拿到全文; - 会话事件窗口里的 notice 节点(
kind=context+form=notice)—— 正在查看的会话,落盘后立即可用; - 降级 —— 「详情见会话内系统消息」+ 工作区信息(
cwd最后一段)。
提问提醒的正文同样优先取宿主投影(key = session-complete-notify-question,宿主已渲染好标题与正文),老宿主无该投影时客户端自行拼接标题与 {question} 文本兜底。
通知示例
以下均由 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}$ 标签并选择本地图片(编辑器内显示为带缩略图的标签,**自动压缩至 512\text{px} 宽、按通知显示比例 16:9 居中裁切**,随各原因独立保存);**全局大图/图标**——两张上传卡片并排一行(**图标在前**,空态 = 圆角矩形 + 号,点击上传;**大图 512 \times 288(16:9 居中裁切)、图标 128 \times 128(1:1 方形居中裁切)**;已上传则卡片显示缩略图,**点击缩略图可全屏查看完整原图(等比未裁切)**,右上角 \times 删除)。图标留空用站点默认图标,也可在模板中插入 ${icon} 标签按原因指定图标(优先于全局)。仅系统通知通道生效(页内 toast 为文字卡片),「发送」测试按钮同样生效 |
| 标题 | 折叠区(默认收起,点击展开):全局推送标题(所有原因共用,Chip 编辑器——点「+ 插入信息」插入的信息以胶囊标签形式显示,点击胶囊移除;占位提示「通用推送标题,留空时则使用默认标题,优先级低于下方自定义标题」(不可选中/删除);通知发送时标题里的信息位(用时/消耗/错误/缓存命中/速度)会替换为实际值,不再显示代码;留空时各原因用默认标题——完成=任务已完成、出错=任务出错、中止=任务已中止、阻塞=任务被阻塞、上限=任务达到上限、提问=AI 正在向你提问)+ 按原因定制标题(6 条原因各自输入,每行带「+」插入按钮——可插入信息标签(含「提问」,不含图片/图标),插入到光标处;优先于全局标题,留空 = 用全局或语言默认标题) |
| 内容 | 折叠区(默认收起,点击展开);展开后每条结束原因(完成、出错、中止、阻塞、上限、提问)一行式布局(原因标签 + Chip 编辑器 + 「+」插入按钮——菜单展开时变「−」+ 发送箭头按钮,按钮为矩形、垂直居中):空模板(默认预设)时编辑器显示默认文案「会话「{title}」已xx,请点击查看。」,文字 + 内联信息胶囊,光标处插入;{image}/{icon} 标签点击缩略图可预览大图、点 × 才删除(防误删),其他标签点击移除;编辑后删空则显示「留空则使用默认文案」占位(不可选中/删除);提问行的默认文案为「AI 向你提问:{question}」,{question} 会在发送时替换为 AI 的实际提问(插入菜单同样提供「提问」标签,与其他标签同款交互) |
| 跳过子代理会话 | 复选框(保存时一并写入设置文档) |
| 通知权限 | 状态实时显示:已授权(绿)/ 尚未授权(附「请求授权」按钮)/ 已被浏览器屏蔽(附地址栏操作指引)/ 环境不支持 |
| 按原因定制标题 | 折叠区(默认收起):每个结束原因一个独立标题输入框,留空 = 用全局模板或语言默认标题 |
| 保存 | 写入宿主设置文档(language / templates / titleTemplate / titleTemplates / pushMode / skipSubagents);保存后显示「点击刷新」链接 |
| 重置 | 一键还原默认值(语言保留当前选择,标题/模板/推送方式恢复默认)并立即保存 |
Note
「推送方式」的取舍:dual(默认)同时弹 Windows 系统通知与页内 toast,toast 是保底通道,防止系统通知被平台静默(专注助手、通知横幅关闭)。但 QQ 浏览器等国产 Chromium 壳浏览器会把 Notification 渲染成「浏览器内置的页内推送弹窗」(页面顶部/角落的横幅,不经 Windows 通知中心)——此时 dual 会造成页内两个提示(浏览器内置弹窗 + 插件 toast)。这类浏览器请选「仅页内提示」(不再调用 Notification,浏览器内置弹窗不会出现,页内只有插件自己的小 toast);「仅系统通知」模式在 QQ 浏览器无效(它永远渲染为页内弹窗)。设置面板每个原因的「发送」测试按钮同样受此影响。
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 \times 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()中并返回clearTimeoutdisposer —— 插件在重试窗口内被卸载或热重载时定时器随 fiber 拆除,不会对已释放的 ctx 触发注册(极老环境无ctx.effectAPI 时退化为裸定时器 + 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 <session.jsonl.zstd>
调试入口
| 入口 | 内容 |
|---|---|
~/.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.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 | 初始版本:会话内系统消息 + 浏览器推送 + 官方设置面板 |
贡献
欢迎 Issue 与 PR:
- Fork 仓库并新建分支(
feat/xxx) - 改动后运行
npm run build做语法校验 - 提交 PR,说明动机与验证方式
提交前请遵守 Cordis 开发教程 纪律:
- Cordis 之外的资源(定时器、订阅、watcher)必须包装在
ctx.effect()中并返回 disposer; - 配置项显式
id防止编辑漂移; - 插件须声明
dsh.bundlemanifest 才能被dsh plugin add识别安装。
相关链接
- awesome-dsh-plugin —— DSH 插件精选列表(投稿规范:
dsh.bundle是安装唯一凭证) - Cordis 开发教程 —— 插件开发全流程(01-07 章)
- npm 包主页
- GitHub 仓库
许可证
MIT © dsh-session-notify contributors