Windows native toasts for DeepSeek Harness: pops a toast when a session needs you (approval, question, error, optional turn end), click to return to DSH, answer approvals with Allow/Deny buttons on the toast, plus a notify_user tool and an HTTP API.
Install
# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)
dsh plugin --profile web add github:airenburen/dsh-windows-notify
Any plugin you install runs third-party code with your own permissions — it can read your files, use your credentials, and reach the network, and tool approvals don’t sandbox it. GitHub-sourced plugins also run build scripts at install time — pnpm blocks those until you allow them, so an install can stop with ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED or ERR_PNPM_IGNORED_BUILDS; dsh prints the exact key to add under allowBuilds in your profile’s pnpm-workspace.yaml, and the install works on the next run. Allowing a build is a trust decision: only install sources you trust, and pin a commit (github:owner/repo#sha).
README
This plugin publishes its README in Chinese only.
给 DeepSeek Harness 加「需要你操作」的 Windows 原生通知,并且点通知就回到 DSH。
- 对话卡在要权限(命令/文件操作需要批准)时弹通知:
pwsh 想执行一条命令,需要你确认权限—— 而且是可直接回答的通知:上面两个按钮「允许 / 拒绝」,点一下这次审批就结算了, 不必再切回 DSH 界面 - 对话卡在要你回答(AI 提问 / 计划确认)时弹通知,带选项预览
- 可选:一轮回复结束、会话出错时弹通知(默认只开错误)
- 通知走 Windows 10/11 的 Toast(操作中心可见、可设显示时间限制自动消失)
- 点击通知(或点通知上的「打开 DSH」按钮)→ 唤起 DSH 主窗口:最小化会还原、后台会置前
- 给 AI 一个写通知的 API:模型工具
notify_user、HTTP 接口、ctx.windowsNotify服务
触发时机
| 状态 | DSH 事件 | 默认 |
|---|---|---|
| 需要授权(沙箱升级 / 工具需要批准) | approval/request |
开(带允许 / 拒绝按钮) |
| AI 向你提问(含计划确认) | user-questions/request |
开 |
| 一轮回复结束 | agent/turn-stopping |
关(notifyTurnEnd 打开) |
| 会话出错 | agent/error |
开 |
同类通知会按 tag 互相替换,并有去重(同一 tag 4 秒内不重复)与限流(默认每分钟 20 条),
避免刷屏。tag 的分配规则:
- 带按钮的权限通知:每条一个独立 tag(
ap-<token 前 8 位>)——两个待你回答的审批 必须是两条通知,否则会互相覆盖、没法分别回答; - 不带按钮的审批(高危走界面那条):带
callId时按它派生(并发审批互不吞), 没有callId时用固定 tag(短窗内的同类审批合并,不刷屏); - 回合结束:tag 带会话标识——两个会话同时结束是两条通知,不会互相吞;
- 出错:tag 带会话标识 + 错误指纹(FNV-1a)——不同错误各占一条,同一会话同一错误 仍被去重。
安装
从 GitHub(推荐)
# 浏览器 / 带 profile CLI 的部署(web profile 等)
dsh plugin --profile <profile> add https://github.com/airenburen/dsh-windows-notify
# 桌面端(Electron)跑保留 profile desktop,CLI 被硬拒,只能用 GUI:
# 侧栏「插件」→ 安装 → 填同一个 git 地址
装完重启宿主(客户端半边是已构建的 bundle,宿主会缓存模块),然后: 设置 → 左侧导航「通知」(或按下面的其它入口)。
从本地目录(开发用)
tools/install-desktop.mjs 会把本目录以 link: 方式装进 desktop profile:
cd <本目录>
node tools/install-desktop.mjs
它做的事:备份 profile 配置 → pnpm add link:<本目录>(失败则退回目录联接)→ 写
dsh.profile.bundles → 从 profile 里真实 import 一次插件 → 跑 test/smoke.mjs。
装好之后,~/.dsh/profiles/desktop/package.json 里会有:
"dependencies": { "dsh-windows-notify": "link:<本目录>" },
"dsh": { "profile": { "bundles": [ "...", "dsh-windows-notify" ] } }
开发时的一个坑:以
link:装进来时,Node 会按真实路径解析裸导入,所以要给本目录做一个node_modules目录联接(指向 profile 的node_modules)才能找到@deepseek-ai/schemastery等依赖;install-desktop.mjs会自动处理。pnpm 11 默认的
minimumReleaseAge供应链校验会拿既有 lockfile 里已装好的条目报错并中断收尾, 脚本因此带--config.minimumReleaseAge=0(只影响这次安装,不会下载任何新包)。
配置
主入口:设置 → 左侧导航「通知」(DSH 原生设置页,order 55,排在「插件市场」后面)。
这是 DSH 自己的槽位机制:客户端半边注册一格
settings.section({ id: 'dsh-windows-notify', order: 55, label })就多一页 ——dsh-market的「插件市场」页是同一套做法。用自己的 id:复用别人的 id 会把对方那一页顶掉。 这一格不依赖任何第三方插件,better-sidebar 卸了也照样在。
另一处入口:设置 → 侧边栏卡片 → 找到「通知」卡片 → 卡片上的齿轮(DSH-better-sidebar 的设置页)。
better-sidebar 给每个注册的 tab 类型在它的设置页放一张卡片,卡片的齿轮渲染
settings.render;所以插件用ctx.betterSidebar.registerTab({ ..., hidden: true, settings: { render } })把同一份面板 也挂在那里。hidden: true是编辑器 / diff 内置类型的同一套路:有设置卡片,但不往 DSH 原生 「+」菜单里添一个用不上的侧栏页。没装 better-sidebar 的部署会自动退回插件行配置页(侧栏「插件」→ 已安装 →
dsh-windows-notify→ 行上的「配置」)。三处入口共用同一个面板与同一份配置。
面板里的字段(全部 .volatile(),改完立即生效):
| 字段 | 默认 | 说明 |
|---|---|---|
enabled |
true |
状态通知总开关(不影响 AI/脚本的显式通知) |
notifyApproval |
true |
要权限时通知 |
notifyQuestion |
true |
AI 提问时通知 |
notifyTurnEnd |
false |
一轮回复结束时通知 |
notifyError |
true |
会话出错时通知 |
agentTool |
true |
注册 notify_user 工具(改后需重载插件) |
httpApi |
true |
开启 /plugins/dsh-windows-notify/* 路由 |
focusOnClick |
true |
点击通知回到 DSH |
sound |
true |
提示音 |
displaySeconds |
10 |
显示时间限制:到点自动从屏幕与操作中心移除;0 = 不限制 |
sticky |
false |
状态通知用系统「提醒」样式常驻(仅当 displaySeconds = 0 时生效) |
decisionButtons |
true |
权限通知带「允许 / 拒绝」按钮 |
decisionSeconds |
300 |
按钮可点窗口(也会写进通知的显示时限);0 = 跟随 displaySeconds |
decisionButtonsFor |
safe-only |
哪些审批带按钮:safe-only = 高危的 danger-full-access 不发按钮(只提示去界面确认),all = 全都发 |
privilegedModes |
['danger-full-access'] |
哪些权限模式算「高危」(不给通知按钮)。DSH 未来引入新特权档位时把名字加进来。判定只认权限模式本身,理由文本不参与 |
sessionFilter |
[](不过滤) |
只对匹配的会话弹状态通知(审批/提问/回合结束/出错)。匹配会话标题或 cwd 末段(大小写不敏感的包含匹配),如 ['dsh-windows-notify', '猫窝']。显式通知(notify_user/HTTP)不受过滤 |
whenFocused |
silent |
DSH 在前台时怎么办:silent = 只进操作中心(SuppressPopup:不弹横幅、不响铃)、skip = 完全不发(历史仍记录)、notify = 照常弹。只作用于自动状态通知,notify_user/HTTP 的显式通知照常弹 |
language |
zh |
zh / en |
aumid |
com.deepseek.dsh |
通知来源 AppUserModelID(桌面版开始菜单里的 AppID) |
dedupeMs |
4000 |
同 tag 去重窗口 |
maxPerMinute |
20 |
每分钟上限 |
debugLog |
false |
打印每条通知的调试日志 |
前后台:DSH 在前台时不打扰(whenFocused)
宿主是独立 Node 进程(ELECTRON_RUN_AS_NODE),拿不到 Electron 的窗口状态,webServer
也不暴露窗口信息——所以判定来自渲染进程:客户端半边监听 focus/blur/
visibilitychange,每次焦点变化上报一次,平时每 15 秒心跳一次
(POST /plugins/dsh-windows-notify/focus,只接受本机回环)。
「前台」= visibilityState === 'visible' && document.hasFocus();宿主侧 45 秒
(FOCUS_TTL_MS)收不到消息就当作后台——页面关掉、崩溃、换机器都不会把通知永久压住。
多窗口各报各的,取「有一个在前台」。
离开类事件强制报后台:pagehide(页面关闭/跳转)与 visibilitychange → hidden
(最小化、锁屏)触发时跳过计算直接上报 focused: false(带 keepalive,页面卸载后
请求仍能发出)——这两类事件触发瞬间 hasFocus() 可能仍返回 true,按计算结果报会把
「已离开」误报成「在前台」。锁屏时页面转入 hidden → 通知恢复弹横幅,符合「人不在电脑前」
的直觉(真机锁屏行为建议升级后手动确认一次)。
命中前台时按 whenFocused 处理:
silent(默认):给 toast 设ToastNotification.SuppressPopup = $true——不弹横幅、 不响铃,但通知照旧进操作中心(它上面的按钮也照旧可用)。既不看横幅、又不会漏,还顺手 消掉了「横幅弹在光标下被误点」的隐患。skip:完全不发,历史里记一条skipped: "foreground"(侧栏「通知」页能看到)。notify:回到老行为,一律弹。
只有 4 类自动状态通知受这个策略约束;notify_user、HTTP /send、
ctx.windowsNotify.send 这类显式通知永远照常弹(那是「我就是要提醒你」)。
/status 里的 focus 字段(foreground / policy / windows / ttlMs)可以在侧栏
「通知」页直接看到当前判定。
显示时间限制是怎么做到的
Windows 里两件事是分开的:duration 只决定横幅在屏幕上停多久(short ≈ 7 秒 / long ≈ 25 秒),
而 scenario="reminder"(「提醒」样式)根本不自动下屏——这正是之前通知「一直显示」的原因。
真正能把通知撤掉的是 ToastNotification.ExpirationTime:到点后系统把它从屏幕和操作中心一并移除。
所以:
displaySeconds > 0(默认 10)→ 给每条 toast 设ExpirationTime = now + N;同时强制关掉 「提醒」样式(否则它会顶掉时限),N > 25时用long横幅,否则short。displaySeconds = 0→ 不设ExpirationTime,此时sticky才起作用(状态通知用「提醒」 样式,一直挂在屏幕上等你处理)。- 这个上限对所有通知生效,包括 AI 主动调
notify_user(..., sticky=true)的那条。
node test/windows.mjs --expire 会发两条真通知(一条设 6 秒时限、一条不设),12 秒后回读
操作中心,验证设了时限的那条已被移除、对照那条还在。
「允许 / 拒绝」按钮是怎么把答案传回来的
通知上的按钮只能激活一个 URI,所以链路是这样接的:
- 审批通知渲染成
<actions>里两个按钮:dsh-notify:allow/<16位随机token>/dsh-notify:reject/<token>,按钮配色用hint-button-style(允许=Success / 拒绝=Critical), 点通知正文仍然是回到 DSH(dsh://open)。 - 插件把宿主半边注册的
dsh-notify:协议指向一个隐藏 PowerShell 启动器 (%LOCALAPPDATA%\dsh-windows-notify\decision.ps1)——它把 URI 解析成POST <宿主地址>/plugins/dsh-windows-notify/decision?token=…&choice=allow|reject。 这条通道独立于回 DSH 用的dsh:协议,所以两边互不影响。 启动器脚本里要写死宿主地址,而这个地址只能从请求上取:webServer服务不暴露端口, 宿主进程里也没有DSH_WEB_URL(那是 DSH 注入给 shell 子进程的)。所以插件在每条路由里 记下req.socket.localPort(服务端监听端口,比 Host 头可靠——桌面端页面的请求是 Electron 主进程转发过来的),客户端半边在页面加载时还会 ping 一次/hello, 把地址尽早送过来。 - 宿主维护「待回传」表:token 一次性、默认 300 秒(
decisionSeconds)后作废,回传口只接受 本机回环请求。审批监听器把「GUI 里的回答」和「通知按钮的回答」赛跑: 谁先到用谁,另一边随即作废,并把那条通知从操作中心撤掉。 - 只在通道注册成功时才给按钮——不知道自身地址、注册失败、或
decisionButtons=false时都退化成普通通知(否则点下去系统会弹「选择打开方式」)。 - 顺手把 GUI 里那张「等待审批」卡也收掉。那张卡是客户端自己在
approval/request瀑布里创建的 pending interaction,只有它自己结束(answer/delegate/abort)时 才会消失——宿主返回的答案它看不到,所以之前点完通知按钮,卡片还杵在那儿等人点。 现在宿主每完成一次「从通知按钮结算」就记一条事实 (GET /plugins/dsh-windows-notify/decisions?since=<seq>),客户端半边拿它做两件事:- A(首选):从
ctx.uiSession.sessionStatus的快照里找到那个 pending 实例, 直接调它的公开answer(outcome)→ 卡片随它的finally一起消失。这次迟到的回答会被 瀑布丢弃(宿主早已落盘),实例自带的 already-settled 守卫也拦住重复结算。 - B(兜底):拿不到可结算的实例时,按文案在 DOM 里找卡上的「允许一次 / 拒绝」
(Allow once / Reject)按钮点它——不依赖任何内部 API,代价是换文案/换皮会失效。
客户端只在本地真的有待审批时才拉这条增量,空闲时零请求;
since基线在每次进入 「有待审批」时重新对齐,避免旧事实误伤新卡片。
- A(首选):从
安全性:token 16 位十六进制随机、只能用一次;回传口只认回环地址;即使 token 泄漏,最坏结果 也只是替你答了一次本来就在等你的审批,而它同时仍受 DSH 自己的授权策略约束。
HTTP 接口的统一安全门
所有 /plugins/dsh-windows-notify/* 路由(不只是写入口)都先过两道检查:
- 回环限定——非
127.0.0.1/::1的请求一律 403; - Origin 校验——浏览器发起的跨源请求必带
Origin头;不是本机来源(http://127.0.0.1:*"、http://localhost:*)也不是null(file:// 基)的一律 403。没有 Origin 的调用 (curl、PowerShell、宿主进程内的 Electron 转发)不受影响。
背景:浏览器里任何网页都可以向 http://127.0.0.1:<port>/send 发跨源 POST(简单请求不受
CORS 预检拦截),伪造一条「DSH 需要授权」样式的通知做社工钓鱼。统一安全门把它挡掉。
威胁模型备案:%LOCALAPPDATA%dsh-windows-notify 下的回传脚本(decision.ps1、ps-worker.ps1)
对本机其他进程可写——但本机恶意进程本来就能直接发 toast / 读注册表,本插件不假设
「本机已失陷」场景。
高危审批默认不发按钮(decisionButtonsFor = safe-only):通知上的「允许」太容易误点,
而 danger-full-access 一旦放行就不再受工作区限制。这类审批只弹一条正文带
「高危权限,请到 DSH 界面确认」的普通通知,必须回界面点。低危的(如升级到
workspace-write)照旧带按钮。想恢复「全都给按钮」就把它设成 all。
每次按钮激活都有审计:%LOCALAPPDATA%\dsh-windows-notify\decision.log,一行一次
时间 | 允许/拒绝 | token | 拉起它的父进程 | 父进程命令行。父进程能区分来源——
真人点通知走的是 Windows 通知平台(svchost.exe -s WpnUserService),
而程序里 Start-Process "dsh-notify:…" 的父进程是 powershell.exe。
(现场验证过:一条没人点的通知挂 10 分钟不会有任何回传,不会自己触发。)
node tools/peek-toast.mjs 则用来查看操作中心里那条通知实际带了哪些按钮参数。
node tools/test-decision.mjs 会起一个替身宿主、用真实注册表协议和
Start-Process "dsh-notify:allow/<token>"(就是系统点按钮时做的事)验完整条链路,
跑完恢复原来的协议注册。
node tools/demo-buttons.mjs 是「人点一下」的现场演示:起替身宿主 + 注册真实协议 +
发一条真的带按钮通知,点哪个都会把 token/choice 打在终端里,用来确认 Windows
确实渲染了这两个按钮、点下去也确实回传(演示结束会把协议恢复原样)。
面板顶部还有发送测试通知:点一次就能验证「弹得出来 + 点击能回到 DSH」。
另外注册了一个 hidden 的 tab 类型(正常看不到,只有被显式打开时才出现):它显示通知通道
状态、最近发出的通知(宿主保留最近 50 条,GET /plugins/dsh-windows-notify/recent)和测试按钮。
想让它出现在 DSH 侧栏的「+」菜单里,把 lib/client.js 里的 hidden: true 改成 false 即可。
不想用界面也行:同一份配置等价地存在 profile 的 cordis.patch.yml(entry id windows-notify)
里,直接改文件效果一样——界面写的就是它(经 DSH 的 settings 服务)。
界面靠宿主半边这两个路由读写配置(写入只允许本机回环):
GET /plugins/dsh-windows-notify/config、POST /plugins/dsh-windows-notify/config。
给 AI / 脚本用的 API
1. 模型工具 notify_user(AI 直接调用)
notify_user(title="构建完成", message="产物在 dist/", tag="build", sticky=false)
→ { delivered: true, title: "构建完成", body: "产物在 dist/" }
2. HTTP 接口(任何脚本 / 外部 AI)
Invoke-RestMethod -Method Post -Uri http://127.0.0.1:19387/plugins/dsh-windows-notify/send `
-ContentType 'application/json' `
-Body '{"title":"部署完成","message":"v0.1.0 已上线","tag":"deploy"}'
# 自检 / 状态 / 最近通知
Invoke-RestMethod http://127.0.0.1:19387/plugins/dsh-windows-notify/status
Invoke-RestMethod 'http://127.0.0.1:19387/plugins/dsh-windows-notify/recent?limit=10'
# 「已从通知按钮结算」的增量事实(客户端半边用它收掉 GUI 的审批卡)
Invoke-RestMethod 'http://127.0.0.1:19387/plugins/dsh-windows-notify/decisions?since=0'
# 清空通知历史(侧栏「通知」页的清除按钮调它)
Invoke-RestMethod -Method Post http://127.0.0.1:19387/plugins/dsh-windows-notify/clear
# 端口说明:上面示例里的 19387 是旧版桌面端端口,**以你当前 DSH Web GUI 的实际端口为准**
# (GUI 地址栏里就是;所有接口只接受本机回环 + 本机 Origin)。
3. 插件服务 ctx.windowsNotify(其它 DSH 插件)
const notify = ctx.get('windowsNotify');
await notify.send({ title: '标题', message: '正文', tag: 'x', sticky: false });
await notify.status();
notify.recent(10); // 最近发出的通知
点击通知是怎么回到 DSH 的
- 通知 XML 用
activationType="protocol",点击/按钮都激活一个 URI。 - 桌面端优先用 DSH 自己注册的
dsh:协议(Electron 的setAsDefaultProtocolClient("dsh")), URI 是dsh://open。系统执行"D:\DSH\DeepSeek Harness.exe" "dsh://open", 应用因单实例锁把这次启动转成second-instance→restore() + show() + focus()主窗口。 —— 这条路不改注册表。 - 若
dsh:没注册(或宿主是dsh web浏览器模式),插件才退回到自己注册的HKCU\Software\Classes\dsh-notify:桌面端指向应用本体,浏览器模式指向一个%LOCALAPPDATA%\dsh-windows-notify\open-dsh.cmd(打开本会话地址)。 - 注册表读取失败、
focusOnClick关闭等情况统统只降级:通知照发,只是不带激活。
自检与排错
cd <本目录>
node test/windows.mjs # XML/协议/文案的单测(不需要 DSH)
node test/windows.mjs --toast # 额外发一条真通知,并回读操作中心历史
node test/smoke.mjs # 假 ctx 驱动宿主半边:4 类状态、去重限流、安全门、tag 唯一化、
# sessionFilter、privilegedModes、退避重试、decisions 落盘、card-cleanup
node test/client.mjs # 假 React + 假 __ModuleLoader__ 驱动客户端半边(设置页、前后台、卡片清理)
node tools/test-decision.mjs # 「点通知按钮 → 隐藏脚本 → POST 回宿主」端到端(真注册表协议)
node tools/compare-define-tool.mjs # 本地 defineTool 与 DSH 自带实现逐字段对照
node tools/register-protocol.mjs --check # 看 dsh-notify: 注册成了什么
node tools/test-activate.ps1 # 最小化 DSH → 触发 dsh://open → 确认窗口被还原
| 现象 | 排查 |
|---|---|
| 完全没有通知 | 系统「设置 → 系统 → 通知」里允许通知、关掉专注助手;/status 看 available 是否为 true |
| 通知来源显示不对 | aumid 保持 com.deepseek.dsh(桌面版开始菜单里的 AppID,Get-StartApps 可查) |
| 点了没反应 | node tools/register-protocol.mjs --check;桌面端应存在 HKCU\Software\Classes\dsh\...\command |
| 有通知但没声音 | 配置里 sound,以及系统通知的提示音设置 |
| 权限通知上没有按钮 | 说明按钮通道没建好(日志里会有 decision channel unavailable):浏览器模式(没有 DSH_WEB_URL)或 decisionButtons 关掉了;此时会退化成普通通知。可跑 node tools/register-protocol.mjs --check 看 dsh-notify: 指向哪 |
| 点「允许 / 拒绝」没反应 | 跑 node tools/test-decision.mjs 验证链路;它跑完会把协议恢复成原来的命令,之后插件会在下次启动时重新注册 |
| 点了通知按钮、审批已结算,但界面上那张「等待审批」卡还在 | 卡片清理走客户端半边:确认 DSH 界面已刷新(客户端 bundle 换代后要重新加载页面);/decisions 里应能看到那条事实。两分钟内没消失说明该 DSH 版本的 uiSession 面变了,B 兜底的文案也可能变了——把现象发我,我更新匹配规则 |
| 不确定是不是「自己点到的」/ 审批怎么就被批准了 | 看 %LOCALAPPDATA%\dsh-windows-notify\decision.log:一行一次激活,含时间、允许/拒绝、以及拉起它的父进程(真人点击 = 通知平台 WpnUserService;脚本触发 = powershell.exe)。当前默认高危审批不发按钮,也能从源头少踩这个坑 |
| 设置页里找不到「通知」卡片 | 卡片在设置 → 侧边栏卡片 → 侧边栏内容一组的最后(hidden 类型排在末尾);没有的话先确认 dsh-better-sidebar 在运行,客户端半边改动需要重启宿主才生效 |
| 想看插件日志 | 配置 debugLog=true,再在会话日志里找 windows-notify: 前缀 |
| 从通知点「允许/拒绝」后界面上的审批卡不消失 | 侧栏「通知」页(或 /status 的 cardCleanup 字段):degraded = 客户端的文案匹配失效了(DSH 换皮/换文案),A/B 两条清理路线都没走到;settled = 清理动作已发出(卡还挂着就是渲染层的问题,刷新页面) |
| 通知发送明显变慢 | 看日志里有没有 powerShell worker:长驻通道没起来时会回退逐条 spawn(每条冷启动几百 ms);连续 3 次起不来本轮进程不再重试。通道失败(注册表/协议)有 60 秒退避,/status 的 decision.retryInMs 能看到下次重试还有多久 |
卸载
# 1) 从 profile 移除
cd ~/.dsh/profiles/desktop
<DSH 自带 node> <DSH 自带 pnpm> remove dsh-windows-notify
# 2) 从 package.json 的 dsh.profile.bundles 里删掉 "dsh-windows-notify"
# 3) 可选:删掉 HKCU\Software\Classes\dsh-notify(仅当用过兜底协议)
目录
lib/index.js 插件宿主半边的编排层:Config、notify 主路径(去重/限流/前后台/显示时限)、
PowerShell 通道(长驻 worker + 逐条 spawn 回退)、decisions 落盘、状态组装
lib/events.js 4 类「会话状态 → 通知」的事件监听(审批竞速、tag 唯一化、sessionFilter 接线)
lib/http-routes.js 全部 HTTP 路由 + 统一安全门(回环 + Origin)
lib/channels.js 两条惰性注册通道(点击回 DSH / 按钮回传)+ 失败退避 + 按钮 token 记账
lib/http.js 路由共用小工具(请求体解析 / JSON 回写 / 回环判定)
lib/client.js 客户端半边(手写的已构建 bundle):设置面板、better-sidebar 卡片、
前后台上报(pagehide/hidden 强制后台)、审批卡清理(结果上报宿主)
lib/win-toast.js WinRT Toast 发送器(Windows PowerShell 5.1 + -EncodedCommand)+ 撤下通知
lib/protocol.js 点击目标 / 按钮回传的协议推导与注册(dsh://open 优先,dsh-notify: 兜底)
lib/messages.js 4 类状态的文案与载荷防御式解析(zh/en)+ 高危审批判定
lib/define-tool.js defineTool 的等价实现(避免依赖 @deepseek-ai/dsh-tools 的裸导入解析)
cordis.patch.yml profile 挂载层(insert 一个 loader entry)
tools/ 安装、协议注册、按钮链路端到端、defineTool 对照、激活测试、演示通知、
ps-worker.ps1(长驻 toast worker)
test/ 自检
开发说明:本目录下的 node_modules 是一个指向
~/.dsh/profiles/desktop/node_modules 的目录联接。原因是插件以 link: 方式装进 profile 时,
Node 按真实路径解析插件内部的裸导入,@deepseek-ai/schemastery 会从本目录往上找;
没有这个联接,插件在加载期就会 ERR_MODULE_NOT_FOUND。package.json 的 files 白名单
不包含 node_modules,所以它不会被当成包内容发布。
License
MIT © 2026 Aina_PJAR (@airenburen)
Links
More in this category
xmanrui/dsh-im★ 3118
Connect IM bots to DeepSeek Harness via QR codes or bot credentials (9 channels: Feishu, WeChat, DingTalk, WeCom, QQ, Slack, Telegram, Discord, and WhatsApp).
shaobeichen/dsh-pocket★ 2685
Remote phone access to the DSH Web UI: scan a QR code for LAN or public (cloudflared tunnel) access with real-time sync, a mobile-adaptive layout, and a settings tab.
OpenPetsHQ/openpets#dsh★ 1373
Bridges DeepSeek Harness lifecycle status, errors, and approval requests to a locally running OpenPets desktop companion.
c1overzz/dsh-bili-scroll★ 1150
Let your DSH agent browse Bilibili on its own, read subtitles, write a daily digest, and @ you when it finds something worth your attention.
inclusionAI/Avernet#deepseek-harness-channel-bcn★ 599
Connects DeepSeek Harness to Avernet's Bot Collaboration Network over WebSocket V2, with automatic onboarding, isolated agent sessions, tool-call events, and multi-bot routing tools.
zaofan-make/dsh-qqbot★ 213
QQ official bot channel for DeepSeek Harness (feature-rich fork of tencent-connect/dsh-qqbot): group and private chat with the full agent, sticker library with auto-tagging and AI meme replies, group management (join approval and mute), botplay interactive card events with paginated catalogs, user extension registry (custom slash commands and QQ tools), scheduled wakeups, remote approvals over QQ, and a floating dock web UI.
Community comments
Comments are public GitHub Discussions. Loading them connects to GitHub and Giscus; a GitHub account is required to post.