Notice Card
给 DeepSeek Harness 桌面端的通知卡:任务跑完、等你审批、等你回答时,在屏幕角落弹一张卡片。
只做桌面端。 卡片是 Windows 原生窗口,不依赖浏览器,也不提供网页端那一套(浏览器通知 / 页面浮层 / 标签页标题标记)。
简体中文 · English


功能
三件事发生时弹一张卡:
- 一轮任务跑完
- 等你审批工具调用
- 等你回答一个问题
卡片盖在其他窗口上,但不抢焦点。你可以继续打字;屏幕被一个程序占满的时候不弹,退出全屏后补上。
外观
- 跟随系统主题 —— 深色模式深底浅字,浅色模式反过来。每次弹卡读一次主题,刚切过之后弹出的卡立刻是新配色。两套都是固定常量:深色
#2B2B2B表面 /#FFFFFF主文字 /#CFCFCF次要文字,浅色#FFFFFF/#1A1A1A/#5F5F5F——照 Windows「设置」应用实测取的。不再读 uxtheme,它只能给纯黑纯白,给不出设置应用那种浮起表面。 - 高对比度让位 —— 开了高对比度就用系统自己的窗口色和文字色,不猜变体。
- 文字用灰阶抗锯齿画到黑底,再压成纯灰阶上屏 —— 白字画到黑底(按 2 倍采样画,回落后再算覆盖度),读回每个像素的覆盖度,按覆盖度在底色和字色之间插值。不用 ClearType,是因为它把一根竖笔的覆盖度放在一条子像素列上:同一根笔落在红列、绿列、蓝列上亮度能差 5 倍,压成灰度后就是「相邻竖笔一深一浅」,这正是小字看起来不均匀、发糊的原因。灰阶是真正的逐像素面积,竖笔峰值标准差 0.05–0.08(旧方案 0.11–0.13)。2 倍采样让字形轮廓有真实的过渡带,硬跳变占比 0.25–0.28(旧方案 0.44–0.48),边缘不再是一级级台阶。字号只取整数像素,行高和换行宽度都用同一套引擎算,不会一边量得下一边画不下。
- 字体跟界面语言走 —— 中文 Noto Sans SC(回退微软雅黑 UI)、日文 Noto Sans JP / Yu Gothic UI、韩文 Noto Sans KR / Malgun Gothic、其它 Segoe UI。标题取同族更重一档的 master(SemiBold,没有就用 Medium),正文用 Regular master,都是字体本来画出来的重,不是把轮廓加厚。只加一档:Black 标题在这个字号下是糊不是粗,Medium 正文会把每根竖笔都加宽到看起来像加粗。从右往左的语言(阿拉伯语、希伯来语)右上角两个按钮镜像到左上角。
- 正文框按行数定,能放六行 —— 卡片高度不随正文长短伸缩:六行是常数,短正文下方留白。这样一叠卡高度一致、边缘齐平,每张卡算出来的「这里能放几张」也一致。行距是 1.4 倍字身(字体自带行盒是 1.45,按它排一段话显散),取整数像素,第十行仍落在第一行预测的位置。
- 描边是上淡下重的方向边 —— 卡片被圆角
Region裁掉,阴影画不进窗口里,所以浮起感靠一圈 1px 竖渐变:上沿接近表面色,下沿压到近黑,四边同一圈渐变。这比四面一样重的发丝边更像一张纸,也不会在深色卡上出现「白边」那种一眼假的玻璃感。渐变是对外圈圆角矩形填色、再把表面挖回去做出来的,不是用渐变笔描边——GDI+ 的渐变笔只肯在一段路径上给色,描一圈会有三条边是空的。 - 叠卡逐级沉下去 —— 一摞里第二张、第三张的表面比前排再暗一档(浅色约 4%、深色约 8.5% 一档),第四张起不再变暗,免得半排只剩一条发丝。一摞因此读得出前后,而不是一张卡画了三条边。
- 滚动条会淡出 —— 正文超出六行时右侧出现 2px 轨道加 4px 滑块,滚完 400ms 后开始淡出,260ms 内消失;再滚、再拖动又会淡回来。滚动条不再是一条长住不走的灰杠。
- 图标按钮带常态底 —— 右上角窗口图标与 ✕ 平时就有一层极淡的圆角底(浅色 0x06、深色 0x0A 的 alpha),鼠标移上去才加深到原来的 hover 色。淡到不抢标题,但看得出那里能点。
- 按钮跟时间戳同一行 —— 两个按钮坐在时间戳那一行、与行首顶格对齐,不再占用标题行右端,所以时钟一行的右端和标题一行的右端都是空的。底部角落时这一行在卡片最上面,顶部角落时在最下面,两种布局都跟着时间戳走。
位置与大小
- 落在鼠标那块屏幕 —— 副屏不跑丢。
corner: auto跟系统通知同一个角:任务栏在下面就在右下,在上面就在右上,在左边就跟到左下。 - 也可以钉死四角 ——
top-left/top-right/bottom-left/bottom-right。上边两个顶格贴屏幕顶,下边两个贴任务栏上沿,按工作区算,任务栏在左边或上边也一样对齐。 - 按屏幕宽度缩放 —— 小屏笔记本上不会盖掉三分之一桌面,4K 上也不会缩成邮票。字号有下限。
- 跟文本大小一起长 —— 「设置 → 辅助功能 → 文本大小」调过,整张卡放大,不把文字挤进原来的框。
- 叠成一摞 —— 最早那张在最前、完整显示,后面的只露标题。放得下几张叠几张,绝不会有一张落到屏幕外。
时间与会话
叠成一摞时每张卡只露出贴着屏幕边的那一条窄带,所以弹出时刻、会话名和卡片标题永远一起写在那条窄带上:卡片落在下边两个角,窄带是卡片最上面两行(第一行时钟在前,14:37 · 会话名,会话没标题时显示会话 id 前 6 位;第二行是标题);卡片落在上边两个角,窄带在卡片底部,这两行就写在最下面。窄带的高度按这两行实际占的空间算,所以字调大、卡变宽变窄,三样都还在。右到左的语言里整条跟着镜像。
- 时刻 —— 按系统的短时间格式(12 小时制
2:35 PM,24 小时制14:35)。 - 会话名 —— 和时刻同一行,从时刻后面一直排到那一行右端的两个按钮为止,太长就截断成
...。 - 权限范围 —— 跟应用里那个下拉条同名,只显示不可点,写在离窄带最远的那一行,完全展开时才看得到。
行为
- 不抢焦点 —— 焦点留在你原来那个窗口,打字、放视频都不受影响。
- 全屏时一张卡都不画,退出后补上 —— D3D 独占、演示模式,以及已经拿走呈现路径的无边框全屏游戏都算。在这种画面上让任何窗口出现或消失,合成器都得退出那条路径重新合成:屏幕白闪一下、画面掉一帧、鼠标指针弹出来。所以全屏期间不建窗,通知排进队列等着;退出全屏后按到达顺序一张张补弹,卡与卡之间留出间隔,不会一次糊满屏幕。中途又进全屏,已经弹出的卡自己收掉,队列继续等。全屏期间一直不弹的也没有丢:审批卡和提问卡的请求还在 DSH 自己窗口里,任务结束卡会一直等到你退出全屏。
- 免打扰时不打扰 —— 系统进「请勿打扰」(专注模式、演示模式)时任务结束卡不弹;审批卡和提问卡照弹,那是对话在等你。
- 关动画就真不动 —— 系统关掉「在 Windows 中显示动画」或进省电模式,卡片直接落到位置,不滑入淡出。每张卡判断一次,中途改设置下一张就换行为。
- 按钮尺寸跟那一行 —— 按钮高度取时间戳行的行高(最小 16 单位),所以两种布局里都和时钟一般高,不会比那一行更高、吊在行外面。触摸屏上不做悬停高亮,滚轮行数读系统鼠标设置,不是写死三行。
- 压在最上层 —— 跟别的置顶窗口抢顶层时可能被挤到后面,这是正常的。
什么时候消失
- 任务结束卡 —— 停留
seconds秒后淡出,不点也会消失。 - 审批卡和提问卡 —— 不自己消失。请求可能在你没看见时就被答掉了(回合中止、审批策略自动拒绝、远程客户端替你答),卡片跟着消失会让你以为对话卡住。所以必须同时满足「请求已答完」和「DSH 窗口在这前后出现在过最前面」才淡出,最少显示 1.2 秒,不会闪一下就没了。
- 点 ✕ —— 立即收起。点窗口图标则把 DSH 切到前台。
- 超出叠放上限 —— 最早那张被新卡关掉。
环境要求
只做桌面端,不做网页端。 卡片是画在运行 DSH 的那台 Windows 机器桌面上的原生窗口,不经过浏览器。包里没有网页那一半,所以浏览器通知、页面浮层、标签页标题标记这些一个都没有。想在浏览器里收提醒,用 dsh-notify-me 那种带 client 半的网页插件。
| 项目 | 要求 |
|---|---|
| 形态 | 桌面端(DeepSeek Harness.exe),或 DSH 宿主就跑在你要看的那台 Windows 上 |
| 系统 | Windows |
| DSH | 0.1.0-rc.1 或更高 |
| Node | 20 或更高(只在 DSH 加载插件时用得到) |
| .NET Framework | 4.x,Windows 自带,不用另外装 |
浏览器只用来操作 DSH 的话,卡片会画在 DSH 宿主那台机器上。宿主在远程服务器上,卡片就落在服务器的桌面,你在这边看不见。
安装
四种方式,按你的 DSH 是哪种形态选一种。不要同时用两种,否则每件事弹两张卡。
方式一:从 npm 装
npm i dsh-notice-card@0.3.1
在 profile 目录的 cordis.patch.yml(默认 %USERPROFILE%\.dsh\profiles\<profile 名>\)末尾加上:
- insert:
- id: notice-card
name: C:/你执行 npm i 的目录/node_modules/dsh-notice-card/lib/index.mjs
config: {}
name: 要写完整绝对路径,斜杠用 /。
方式二:clone 源码
想改代码就用这种。
git clone --branch v0.3.1 https://github.com/Maple-Yoli/dsh-notice-card.git
- insert:
- id: notice-card
name: C:/你克隆的目录/dsh-notice-card/lib/index.mjs
config: {}
新加的 id 会被 DSH 立即加载,不用重启。没有反应就完全退出 DSH 再打开。
方式三:官方桌面端
桌面端的 desktop profile 由应用自己管理,命令行的 dsh plugin --profile desktop add 会被拒绝。
在应用里打开 设置 → 插件,填仓库地址:
Maple-Yoli/dsh-notice-card
装完自动挂上,不用改 patch 文件。
方式四:自己建的 profile
dsh plugin --profile <你的 profile> add dsh-notice-card@0.3.1
也可以直接走 GitHub 源,不经 npm:
dsh plugin --profile <你的 profile> add github:Maple-Yoli/dsh-notice-card#v0.3.1
包里的 dsh.bundle.patch 会被自动加进 profile 的 dsh.profile.bundles。
这种方式只适合宿主也跑在本机的场合。宿主跑在远程服务器上,装到哪个 profile 都一样:卡片画在服务器的桌面上。
配置
配置写在 patch 的 config: 里。比如只关掉「任务结束」提示、把停留时间改成 20 秒:
- insert:
- id: notice-card
name: C:/你克隆的目录/dsh-notice-card/lib/index.mjs
config:
onTurnEnd: false
seconds: 20
| 键 | 默认值 | 说明 |
|---|---|---|
onTurnEnd |
true |
一轮任务结束时弹卡 |
onApproval |
true |
等你审批工具调用时弹卡 |
onQuestion |
true |
等你回答问题时弹卡 |
seconds |
10 |
任务结束卡停留几秒。审批卡和提问卡不看这个值 |
turnEndTitle |
任务已结束 |
任务结束卡的标题 |
turnEndBody |
DSH 已完成本轮任务,等你下一步。 |
任务结束卡的正文 |
approvalTitle |
等你审批 |
审批卡的标题 |
approvalBody |
工具:{tool}{reason} |
审批卡正文模板。{tool} 工具名,{reason} 审批理由 |
questionTitle |
等你回答 |
提问卡的标题 |
questionBody |
问题:{question} |
提问卡正文模板。{question} 问题内容 |
processName |
自动推断 | 点窗口图标时切到哪个进程 |
appPath |
自动推断 | 那个进程没开时启动哪个 exe |
corner |
auto |
auto 跟任务栏走,或钉死 top-left / top-right / bottom-left / bottom-right |
maxStack |
3 |
最多同时显示几张卡(1–8),超出关掉最老的。实际张数还受屏幕高度限制 |
fontSize |
100 |
字号百分比(80–160)。只放大字,卡片的宽、高、行距与各段位置都不变 |
theme |
auto |
auto 跟系统深浅色,或钉死 light / dark。浅色是白底黑字,深色是黑底白字 |
animations |
follow |
follow 跟系统动画开关,或钉死 on / off |
scale |
auto |
auto 按屏宽取 17.5%,或写百分比("60"–"200",也认旧写法 "1.25")。100 就是这块屏幕自己会选的大小 |
language |
auto |
auto 跟系统语言,或钉死 zh / en,决定文案与字体 |
contrast |
50 |
对比度 "0"–"100",50 是不动。也认 auto / high(=100)/ soft(=0) |
processName 和 appPath 默认从 DSH 进程推出来,桌面端下就是 DeepSeek Harness 和它的 exe 路径。两个都留空,点窗口图标只关卡片、不切窗口。
这些键全在 设置 → 通知卡 里有控件,改完下一张卡就按新值画,不用重启。直接写 patch 的 config: 属于改已有配置,要完全退出 DSH 再启动才读得到。
设置页改任何一项都会在你桌面右下角(或当前角落)起一叠预览卡,按当前设置画,改一下就跟着变:缩放、字号和对比度拖滑块,拖到哪张卡片就变到哪;滑块旁边的数字可以直接敲,范围写死在那里。每角最多几张拖数字,能直接看出改的是哪里。关掉设置页预览就收走。
预览卡是「任务结束 / 等你审批 / 等你回答」各一张,按上面三个开关来。点哪张就关掉哪张,空出来的位置立刻补一张另外的种类,所以你关几次都还是一叠三张;三个开关全取消勾选,三张就都换成空白卡,页眉依次写「第一张」「第二张」「第三张」,时间戳也各差一分钟,好让三张空白卡看起来仍是三张。第一张就在角落上、整张摊开,后面出现的往远离角落的方向一张张压上来,编号和时间也照着这个顺序走:贴着角落那张最早,越往外越晚。
这叠预览卡由一个常驻的预览进程画,不是每改一次就新开一个进程:进程起来要 300ms 上下,而在这个进程里重画一叠只要 40ms 上下,所以设置页只把当前设置写进一个小文件,那个进程看到就当场重画。
重画是逐张来的,不是整叠换掉。几何没变的卡原地保留,换主题时从左上角到右下角扫过一道(旧配色盖在上面,沿对角线退场)。每角最多几张往上加时,新卡从前一张后面滑出来,看得出它是从哪儿来的;往下减就直接收走。
对比度不属于换配色:它只挪正文和页眉页脚的墨色深浅,卡面本身不动,所以拖动时不扫那道,字一直在,只是颜色一路变深或变浅。这道扫过只留给纸面真的换了的时候(浅色换深色),否则每拖一格都把字盖住一下,等于盯着卡反复变空白。
实现
- 三个事件 ——
agent/turn-stopping(一轮结束)、approval/request(等你审批)、user-questions/request(等你回答)。 - 只弹卡,不代替你回答 —— 审批和提问的监听器挂在最前面,卡片弹出后立刻调
next(),把请求交回原本的应答方(应用里的面板)。你还是在应用里作答,卡片盯着请求,答完就收走。 - 底部两行从宿主读 —— 会话名走
sessionTitle(没标题退回会话 id 前 6 位),权限名走permissionPresets的预设目录。任一服务读不到就不写那行,卡片照常弹。 - 其余全是当场读的系统状态 —— 主题
AppsUseLightTheme、高对比度、动画开关SPI_GETCLIENTAREAANIMATION、省电GetSystemPowerStatus、文本大小TextScaleFactor、触摸SM_DIGITIZER、滚轮行数SPI_GETWHEELSCROLLLINES、任务栏在哪边、界面语言、免打扰SHQueryUserNotificationState。任何一项读不到都退回安全默认值,不会因此不弹卡。 - 每个角是独立队列 —— 卡片文件名带角标签(
-br/-tl),不同角的卡互不计数。 - 一次一进程 —— 每张卡是独立的
card-<校验和>.exe进程,没有常驻进程。一张卡崩了只崩它自己。从起进程到出现在屏幕上约 100ms;第一次还要先编译一次 exe,多约 150ms。 - 全屏时排队而不是丢弃 —— 排队的卡不建窗,进程留一个
<到达时刻>-<pid>.wait文件在原地等,屏幕空出来才按到达顺序建窗。等的时候一个窗口都没有,所以游戏画面不会被它打断。等待没有时限,会话结束就一起退出。 - 动画 —— 4ms 一帧的私有消息泵,不是
WM_TIMER,180Hz 屏上也是连续滑动。 - 编译一次就复用 ——
lib/card.cs第一次弹卡时用系统自带的csc.exe编成 exe,存到%LOCALAPPDATA%\dsh-notice-card\。源码内容变了才重编。
单独试一下卡片
$card = (Get-ChildItem "$env:LOCALAPPDATA\dsh-notice-card\card-*.exe" | Select-Object -First 1).FullName
& $card --title "标题" --body "正文" --seconds 8 --corner top-left --session "演示" --preset "工作区内修改"
前提是缓存目录里已经有一个 card-*.exe,没有就先随便跑一轮 DSH 任务让它编一次。--corner、--max-stack、--session、--preset 都可以省略。
常见问题
在浏览器里用 DSH,能收到卡片吗
收不到。卡片画在跑 DSH 的那台 Windows 机器上,不经过浏览器。dsh web 开在另一台设备上,或者宿主在远程服务器上,卡片都落在宿主那台机器的桌面,你看不见。想在浏览器里收提醒,换 dsh-notify-me 这类带网页端一半的插件。
卡片不弹
- 用上面那条命令单独跑一次。能弹说明卡片本身是好的,问题在插件没挂上
- 检查
cordis.patch.yml里name:的路径是否存在,结尾是不是lib/index.mjs - 改了 patch 没反应:DSH 只对新增的
id热加载,改已有的id要完全退出 DSH 再打开 - 只弹一张、后面的不弹:检查是不是用两种方式挂了两次
玩游戏的时候一张都不弹
这是有意的,不是坏了。全屏时让任何窗口出现或消失都会让画面白闪一下、掉一帧、把鼠标指针弹出来,所以那段时间不建窗,通知排队等着;退出全屏后按到达顺序一张张补上,等多久都不丢。想看回放就把游戏切成窗口化,或者退出全屏。
中文变成乱码
lib/card.cs 是 UTF-8 无 BOM 的源码,编译时按文件里的字面量来。乱码一般是编辑器先按 GBK 读、又按 UTF-8 存回去,把中文压坏了。用 VS Code 以「UTF-8 without BOM」重新打开、保存即可。
想改卡片的样子
颜色、字号、圆角、动画时长、叠放间距都在 lib/card.cs 里。改完不用重装,文件内容一改,下次弹卡自动重编。
卸载
- 删掉
cordis.patch.yml里那段- insert:(方式一、二、四),或在 设置 → 插件 里卸载(方式三) - 删掉缓存目录
%LOCALAPPDATA%\dsh-notice-card\ - 顺带删掉
node_modules/dsh-notice-card(方式一)或克隆出来的目录(方式二)
隐私
- 不发网络请求,不写统计
- 不收集你的路径、用户名、会话内容
- 跟 DSH 的交互只有那三个事件。卡片上显示的只有事件自带的工具名和问题文本
- 编译出来的 exe 放在
%LOCALAPPDATA%\dsh-notice-card\,卸载时一起删掉
更新日志
每个版本改了什么、修了什么,全部写在 Releases,中文和英文都有。装之前可以先看一眼。
反馈
有 Bug、有想法,到 Issues 提。
附上:Windows 版本、DSH 版本、是哪种卡片(任务结束 / 审批 / 提问)、复现步骤。有截图或录屏最好。
No comments yet. Be the first to write one.