dsh-session-notify

August 30, 2026 · View on GitHub

dsh-session-notify

简体中文 · English · 繁體中文 · 日本語 · 한국어

DSH(DeepSeek Harness)会话完成提醒插件 —— 每一轮结束,让完成状态主动找你,而不是你盯着屏幕等。

npm version npm downloads license node DSH PRs Welcome

每轮对话结束时,把「已完成 / 出错 / 被阻塞 / 达到上限」连同用时、token 消耗写入会话日志,并推送浏览器系统通知与页内 toast;AI 向你提问时同样立即弹窗提醒,不必守着会话页面。内置 5 种语言、4 套风格预设(颜文字 / 艾露猫 / 猫娘 / DeepSeek 娘)、可视化文案模板编辑器、自定义预设库,缓存命中率与生成速度取自官方投影,与状态栏同口径。


目录


功能特性

标题编辑器 内容编辑器 任务完成通知 AI 提问通知 任务出错通知

三通道提醒,一条不漏

通道形式说明
会话内系统消息可折叠提示行每轮结束把结束原因与用时、消耗作为插件来源的系统消息追加进会话日志,随 JSONL 落盘,恢复或回放会话后依然可见。
浏览器系统通知Web Notification原生弹窗。每次完成事件使用独立 tagdsh-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-llmcreateUserMessage 契约手工构造;纯逻辑层(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 <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 热重载时:

平面自动释放的资源
hostsession/event 事件订阅、settings 命名空间、会话投影单元、设置注册重试定时器(ctx.effect 包装);置卸载标志抑制已调度的微任务追加
client会话列表订阅、完成推送正文的轮询定时器、window.__dsch_notify_debug 调试钩子(按引用删除,防闭包泄漏)、页内 toast 容器 DOM

卸载后保留的数据

  • 设置配置(语言、文案模板)留在 settings 文档,重装后自动恢复;
  • 自定义预设存于浏览器 localStoragedsh-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 返回后提醒失效。提问不写会话日志,只弹通知。

推送正文从哪来

客户端在会话列表观测到 running: true → false 边沿时推送,正文按以下优先级获取(最长轮询 6 秒,400ms 间隔):

  1. 宿主投影(key = session-complete-notify)—— 每个会话都有,后台会话同样拿到全文;
  2. 会话事件窗口里的 notice 节点kind=context + form=notice)—— 正在查看的会话,落盘后立即可用;
  3. 降级 —— 「详情见会话内系统消息」+ 工作区信息(cwd 最后一段)。

提问提醒的正文同样优先取宿主投影(key = session-complete-notify-question,宿主已渲染好标题与正文),老宿主无该投影时客户端自行拼接标题与 {question} 文本兜底。

通知示例

以下均由 lib/core.jsbuildNotice 实际生成。默认文案统一为「会话「{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.ymlconfig 中配置(跳过子代理在面板中以复选框控制)。

设置面板

面板在官方「设置 → 插件」面板中注册(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.ymlconfig.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
字段类型默认值说明
reasonsstring[][completed, aborted, blocked, error, max-tokens]触发提醒的 turn/end 原因白名单
skipSubagentsbooleantrue跳过子代理会话(origin=subagentdelegationDepth>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.appendingfinally 复位),同步 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 <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 在页面加载时装配。保存后点「点击刷新」让两侧重新读取,新语言、模板即生效。

缓存命中率、速度数据从哪来?为什么有时是空的?

来自官方 sessionProjectionstokenUsagesessionStats),与 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.172026-08-30提问即时提醒(可定制):AI 提问立即弹窗;提问文案支持 {question} 占位符与媒体开关;4 套预设补齐 5 语言提问文案;旧宿主自动兜底
0.1.162026-08-30交互修复:连按 Backspace 不再误删标签(仅当光标与标签间无文字时才删标签)
0.1.152026-08-30交互优化:「内容」折叠区默认展开;删除标签后光标直达真实内容,可连贯删除
0.1.142026-08-30代码审查修复:删除预设确认、预设恢复为已保存配置、媒体「×」清除预览、按原因图片参与预设匹配、同名预设提示、重置仅在有修改时可用、调试日志自动截断
0.1.132026-08-29新增 4 套一键风格预设(颜文字/艾露猫/猫娘/DeepSeek 娘),支持 5 语言
0.1.122026-08-29发布包清理
0.1.112026-08-29自定义通知媒体:模板可插 {image}/{icon} 并上传图片/图标(自动裁切);推送标题支持信息占位符;「正文模板 × 5」改折叠区,布局交互全面优化
0.1.102026-08-29推送标题改原生输入框;新增多语言 README(English/繁體/日本語/한국어)
0.1.92026-08-29推送标题按原因定制;投影升级为对象;重置保留语言;每原因加「发送」测试按钮
0.1.82026-08-29默认标题「任务已完成」;默认文案按结束原因差异化;新增重置按钮
0.1.72026-08-29修复设置卡片崩溃(通知权限行作用域问题)
0.1.62026-08-29新增通知权限状态区;权限改为用户手势内请求
0.1.52026-08-29新增推送方式(双通道/仅系统/仅页内),解决 QQ 浏览器双提示
0.1.42026-08-28完整卸载支持(dispose 生命周期收尾)
0.1.32026-08-28声明 dsh.bundle manifest;settings 重试定时器改 ctx.effect()
0.1.22026-08-27包更名至 @telosmaylx scope
0.1.12026-08-27GitHub、npm 安装方式文档化
0.1.02026-08-26初始版本:会话内系统消息 + 浏览器推送 + 官方设置面板

贡献

欢迎 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 识别安装。

相关链接


许可证

MIT © dsh-session-notify contributors