dsh-session-attention
English | 中文说明
Attention reminders for DeepSeek Harness — a client-only plugin. No DSH core file is patched.
When a session needs you, three signals light up together:
| Signal | What it is |
|---|---|
| Desktop notification | A real system notification — title 回复已完成 / Reply finished, body names the session |
| Tab-title dot | ● prefixed to the browser tab title |
| PWA taskbar badge | navigator.setAppBadge(n) — the app-icon badge of an installed PWA |
It fires at exactly two moments, and only while the window is in the background
(document.hidden || !document.hasFocus()), so it never interrupts you while you are
already looking at the app:
- A reply finished — a session's
runninggoestrue → false. - A session is waiting for you — a session's
pendingInteractionappears:approval,plan-revieworquestion.
All three signals clear the moment you return to the window. Subagent sessions are skipped on purpose: they are not yours to answer.
Install
pnpm dsh plugin --profile web add github:renjianbuchai/dsh-session-attention
dsh plugin is a thin wrapper around pnpm, so any pnpm spec works — <dir> for a
local checkout, github:<user>/<repo>, or the npm name once published. The command
writes the dependency and enables the plugin in the profile; there is nothing
else to toggle.
Once the plugin is listed in the awesome-dsh-plugin catalog, it can also be installed from the DSH plugin market with one click.
Two things that will otherwise waste your time:
- If pnpm refuses with
ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION, add--config.minimumReleaseAge=0. That is a pnpm release-age policy knob and has nothing to do with this plugin — a branch or publish can simply be too recent for the configured window. - Grant the notification permission when the browser asks. Without it the tab dot and the badge still work, but the desktop notification stays silent by design (nothing is prompted from a background event, because a browser ignores a permission request that has no user gesture behind it).
Requirements
- DSH
>= 0.2.1-alpha.1 < 0.3.0-0(developed and verified on0.2.1-alpha.1) - Node
>= 22.19.0— DSH itself requires^22.19.0 || >=24.0.0 - A web profile in a browser. The desktop notification needs
Notification; the taskbar badge needsnavigator.setAppBadge(installed PWA, Chromium-based). Both are presence-guarded: where the API is missing, that one signal no-ops and the others keep working.
How it works
- The client half subscribes to two public host observables —
uiSession.sessionStatusandsessions.list— and derives the two edges above. Nothing else is read, and no DSH source file is modified. - The title dot is maintained by a
MutationObserveron<title>. The shell ownsdocument.titleand rewrites it on every navigation, so the plugin never takes ownership — it only re-adds the mark while events are unseen, and removes it when the count returns to zero. - Notifications are de-duplicated by a stable per-session tag (
done:<id>/pending:<id>). The previous notification carrying that tag is explicitly closed andrenotify: trueis set, so a repeat event re-alerts instead of being silently replaced; liveNotificationobjects are retained until they close. - Every notification title carries a
DSH ·brand prefix and the icon the page itself declares. The app name shown above a toast is chosen by the browser (Chrome uses its own App User Model Id), so a page cannot set it — the prefix is the plugin's only way to make the toast unmistakably DSH. - The host half (
lib/index.js) declares the plugin and does nothing else: it writes no files and opens no sockets.
Debug surface
With the plugin loaded, the browser console exposes:
__DSH_ATTENTION__.permission // current Notification permission
__DSH_ATTENTION__.log // recent decisions: notified / skipped / failed + reason
__DSH_ATTENTION__.test() // fire one notification through the plugin's own path
test() is the quickest way to separate "the browser or the OS is blocking toasts"
from "the plugin never fired" — it uses the same code path as a real event.
Verification
| Check | Result |
|---|---|
pnpm typecheck |
host + client halves, both --noEmit — 0 errors |
pnpm selftest |
45 / 45 assertions against the built lib/client.js, run both on the local artifact and on the exact bytes the DSH web server serves |
pnpm e2e |
17 / 17 against a real Chromium, a real running DSH instance and a real model turn: notification title/body/tag, ● in the title, setAppBadge(1), dot removal and clearAppBadge() on return, and that the document was not reloaded mid-wait (a reload would silently drop the test's background simulation and look like a plugin bug) |
Limits, stated plainly:
- The desktop toast is painted by the browser and the OS. A headless browser cannot
assert that, so
e2everifies that the notification is constructed with the right arguments — not that Windows drew it. - Background state in
e2eis a controlleddocument.hidden/hasFocus()simulation, because headless Chromium has no real tab focus. - Windows Focus Assist (Do Not Disturb) and the per-app notification switch can
suppress toasts independently of any plugin. If
__DSH_ATTENTION__.test()produces no toast while__DSH_ATTENTION__.logsaysnotified, the block is on that side.
Provenance
The behavior is ported from DSH 0.1.x's built-in completion attention
(packages/client/ui-renderer/src/client/completion-attention.ts and
use-completion-attention.ts, commits 29d556d7b2, 3cb88b4aa5, 3e0553ed90) and
rebuilt as an installable plugin that uses only public platform APIs — the same
behavior on 0.2.x, without patching the renderer.
Development
DSH_HOME=<dsh home> DSH_CHECKOUT=<deepseek-harness checkout> pnpm prepare:links
pnpm typecheck && pnpm build && pnpm selftest
prepare:links mirrors the @deepseek-ai/* packages out of the profile's
node_modules (and the checkout's vendor/, where cordis and friends live) into
this project so tsc can see the host types. Those packages are pre-release and are
not reliably available from the public registry; at runtime the profile resolves them,
so the links only serve type-checking. Both paths come from environment variables —
the script intentionally hard-codes no machine-specific path.
pnpm e2e needs a running DSH web instance and takes its target from environment
variables (DSH_URL, DSH_WORKSPACE, DSH_TASK, PLAYWRIGHT_BROWSERS_PATH).
License
MIT — see LICENSE.
中文说明
给 DeepSeek Harness 的后台提醒 插件,纯客户端,不修改 DSH 核心任何文件。
会话需要你时,三处同时亮起:
| 信号 | 具体是什么 |
|---|---|
| 系统通知 | 真正的桌面通知 —— 标题带 DSH · 品牌前缀(如 DSH · 回复已完成;英文环境为 DSH · Reply completed),正文写出会话名,图标取页面自己声明的那个(气泡上方那行应用名由浏览器决定,网页改不了,所以用标题前缀来表明身份) |
| 标题页红点 | 浏览器标签标题前加 ● |
| PWA 任务栏徽标 | navigator.setAppBadge(n) —— 已安装 PWA 的应用图标角标 |
只在两个时刻触发,且只在你不在窗口时(document.hidden || !document.hasFocus())——
你正看着它的时候不会打扰你:
- 回复完成 —— 某个会话的
running由true变false。 - 会话在等你 —— 某个会话出现
pendingInteraction:审批、计划审阅或提问。
回到窗口时三处信号立刻清除。子代理会话按设计跳过 —— 它们不该由你回答。
安装
pnpm dsh plugin --profile web add github:renjianbuchai/dsh-session-attention
dsh plugin 是 pnpm 的薄封装,因此任何 pnpm 写法都可用 —— 本地目录 <dir>、
github:<用户>/<仓库>、或将来发布后的 npm 包名。这条命令会同时写入依赖并在
profile 里启用插件,没有别的开关要动。
等本插件进入 awesome-dsh-plugin 精选目录后, 也可以直接在 DSH 插件市场里一键安装。
两个会浪费时间的地方,先说清:
- 若 pnpm 报
ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION,加上--config.minimumReleaseAge=0。那是 pnpm 的「发布冷却期」策略,与本插件无关 —— 新分支或新发布可能刚好落在冷却窗口内。 - 浏览器询问通知权限时请允许。 没授权时标题红点与徽标照常工作,但桌面通知 会按设计保持静默(后台事件里不会去申请权限:浏览器会忽略没有用户手势的权限请求)。
环境要求
- DSH
>= 0.2.1-alpha.1 < 0.3.0-0(在0.2.1-alpha.1上开发与验证) - Node
>= 22.19.0—— DSH 自身要求^22.19.0 || >=24.0.0 - 浏览器里的 web profile。系统通知需要
Notification;任务栏徽标需要navigator.setAppBadge(已安装 PWA、Chromium 系)。两者都做了存在性守卫: 缺哪个 API,就那一项静默不生效,其余照常。
实现要点
- 客户端半体只订阅两个公开的宿主可观察量 ——
uiSession.sessionStatus与sessions.list—— 由它们推出上面两个边沿;不读别的东西,也不改 DSH 源码。 - 标题红点由
<title>上的MutationObserver维持。document.title归 shell 所有 且每次导航都会被重写,所以插件从不抢占所有权:只在仍有未读事件时把标记补回去, 未读数归零时摘掉。 - 通知按会话固定标签去重(
done:<id>/pending:<id>);同标签的旧通知会被显式 关闭并设置renotify: true,使重复事件重新提醒而不是被浏览器静默替换; 活动的Notification对象会保留引用直到关闭。 - 宿主半体(
lib/index.js)只声明插件、不做别的:不写文件、不开端口。
排查入口
插件加载后,浏览器控制台可用:
__DSH_ATTENTION__.permission // 当前通知权限
__DSH_ATTENTION__.log // 最近的决策:notified / skipped / failed 及原因
__DSH_ATTENTION__.test() // 走插件自己的代码路径发一条通知
test() 是区分「浏览器或系统这一侧被挡」与「插件根本没触发」最快的办法 ——
它与真实事件走同一条路径。
验证结果
| 项目 | 结果 |
|---|---|
pnpm typecheck |
宿主 + 客户端两侧,均 --noEmit —— 0 错误 |
pnpm selftest |
对构建产物 lib/client.js 的 45 / 45 项断言,既跑本地产物,也跑 DSH web 服务端实际下发的那份字节 |
pnpm e2e |
真实 Chromium + 真实运行的 DSH + 真实模型回合:17 / 17 —— 通知的标题/正文/标签、标题红点、setAppBadge(1)、回窗后的摘红点与 clearAppBadge(),以及"等待期间文档没有被重新加载"(文档若被重载,测试的后台模拟会静默失效,看起来就像插件坏了) |
如实说明局限:
- 桌面气泡由浏览器与操作系统绘制,无头浏览器无法断言 —— 所以 e2e 验证的是 通知以正确参数被构造,而不是 Windows 真的画出了气泡。
- e2e 里的「后台」是受控的
document.hidden/hasFocus()模拟,因为无头 Chromium 没有真实的标签焦点。 - Windows 的专注助手(免打扰)与应用级通知开关都可能在这插件之外压制气泡。
如果
__DSH_ATTENTION__.test()没冒出气泡、而__DSH_ATTENTION__.log显示notified,那么被挡的是那一侧。
来历
行为移植自 DSH 0.1.x 内置的完成提醒
(packages/client/ui-renderer/src/client/completion-attention.ts 与
use-completion-attention.ts,提交 29d556d7b2、3cb88b4aa5、3e0553ed90),
重做成可安装插件:只用公开平台 API,不补丁渲染器,在 0.2.x 上给出同一套行为。
开发
DSH_HOME=<dsh home> DSH_CHECKOUT=<deepseek-harness checkout> pnpm prepare:links
pnpm typecheck && pnpm build && pnpm selftest
prepare:links 把 profile node_modules 里的 @deepseek-ai/*(以及 checkout
vendor/ 下的 cordis 等未发布包)镜像进本项目,供 tsc 读取宿主类型;运行期由
profile 解析这些包,链接只服务类型检查。两个路径都来自环境变量 —— 脚本刻意不写死
任何本机路径。
pnpm e2e 需要一个正在运行的 DSH web 实例,目标由环境变量给出(DSH_URL、
DSH_WORKSPACE、DSH_TASK、PLAYWRIGHT_BROWSERS_PATH)。
许可
MIT —— 见 LICENSE。
No comments yet. Be the first to write one.