DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

linanbuan /

linanbuan/dsh-system-notify

Verified

用 Windows App SDK 给 DeepSeek Harness 弹系统通知:授权请求 / 电脑操控 / 任务完成 / 会话终止。检测点在 agent 运行时(cordis 事件),不进 function calling、不改 system prompt、不依赖模型输出。

★ 1 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHub
READMESource: main@6a82b06c

系统弹窗 · dsh-system-notify

DSH 现在不会提醒你任何事:会话要授权、要操控你的电脑、任务跑完了、任务挂掉了、会话被终止了——统统没有声音、没有弹窗,全靠你盯着那个窗口。

这个插件补上这一环:在 agent 运行时盯着这些状态变化,用 Windows App SDK 弹出系统通知。它不是工具、不进 function calling、不改 system prompt——模型什么都不输出,通知照样会来。

  • 包名:@local/dsh-system-notify
  • 类型:宿主插件(没有客户端半边)
  • 运行环境:Windows 10 1809+ / Windows 11,DSH 桌面端
  • 第三方依赖:无(宿主侧零依赖,原生侧只用 Windows App SDK 与 .NET)

目录

  • 它解决什么问题
  • 检测点
  • 为什么不是 tool
  • 通知长什么样
  • 安装
  • 配置
  • 规则与开关
  • 节流
  • 诊断接口
  • 排障
  • 隐私
  • 已知限制
  • 测试
  • 结构
  • License

它解决什么问题

你在等的事 之前 装上之后
agent 要求授权(提权、写文件、跑命令) 界面里静静躺着,你不看就永远卡着 「会话请求授权」弹窗,带工具名与原因
agent 要操控电脑(OpenCU 之类) 同上 「电脑操控请求」弹窗,单独一条规则
一轮任务跑完 只能盯着看它停没停 「任务完成」,带会话名与轮次
一轮任务失败/被阻塞/爆上下文 同上 「任务未完成」,带错误码与消息
会话被终止 同上 「会话已终止」(只在终止时还有一轮在跑时才弹)

检测点

全部挂在 DSH 已经存在的运行时事件上,不新增任何"让模型做决定"的环节:

检测点 来源 覆盖场景 默认
approval/asked / approval/decided session/event(审批服务写的会话事件) 要求授权、提权、电脑操控请求 开
turn/end session/event(agent-loop 在每轮收尾时追加) 任务结束 / 失败 / 被阻塞 / 被中断 开
session/disposed cordis 事件 会话终止 开
agent/error cordis 事件 运行错误(turn/end 的 error 已覆盖大多数情况) 关
tool/call session/event(每调用一个工具前追加) 电脑操控:操控类工具一被调用就提醒 开

电脑操控为什么挂在 tool/call 上而不是审批事件上:本地运行时调研(docs/research/runtime-hooks.md)确认,OpenCU 那套电脑操控不经过 DSH 的审批服务——它自带一套 app-approvals,源码对 ctx.approval 零引用。所以"只看审批"就等于永远抓不到电脑操控。computer_use / opencu 这类工具被调用时,tool/call 是唯一纯事件级的挂点(tools/pre-execute 载荷更好,但要 return next() 才不打断调用,一个只读观察者没有理由插进那条链)。

同一会话里连续几十次 computer_use 不会刷屏:这条规则带 5 分钟冷却(cooldownMs),节流桶按会话合并。

两条与"贴心"有关的细节:

  1. 授权先等一小会儿再弹(scope.approvalGraceMs,默认 1500 ms)。approval/asked 之后往往几十毫秒就 approval/decided——你本来就在界面前点了,或者会话策略是 never 直接自动拒绝。真正值得打扰的是"弹出来之后一秒半还没人决定",那时候 agent 是真卡住了。
  2. 子会话默认静默(scope.includeChildSessions,默认 false)。一个 agent 拉起一堆 subagent 时,你不该被十条通知淹没。

为什么不是 tool

需求里写得很清楚:通知不能由 AI 发起。这个插件在结构上就做不到"被模型调用":

  • 它没有注册任何工具,ctx.tool、function calling 的 tools 列表里都没有它;
  • 它不读也不写 system prompt,模型不知道它存在;
  • 它的触发源是 cordis 事件(session/event、session/disposed、agent/error)——这些事件由 DSH 自己的审批服务、agent 循环、会话存储产生,模型输出什么、输不输出,都影响不了它们;
  • 监听器只读:不 append 事件、不改审批结果、不碰 agent 循环。cordis 的 emit 是"遇抛即停",所以每个监听器都被 contain() 包住——这里出错只会变成一条警告,绝不饿死其他订阅者。

测试里有一条 test/plugin-contract.test.mjs 用 Proxy 卡死了这个边界:插件只要访问 ctx.tool 或 ctx.systemPrompt,测试就直接失败。

通知长什么样

标题由规则决定,正文由模板渲染({session} 是会话标题,没有标题时用 cwd 最后一段):

┌─────────────────────────────────────────┐
│ DeepSeek Harness                        │
│ 会话请求授权                             │
│ pwsh 需要授权:danger-full-access · 迁移脚本 │
└─────────────────────────────────────────┘

紧急程度映射到 Windows 的通知场景与提示音:授权/失败类用 urgent(Scenario.Urgent),完成类用 default。

安装

插件是一个标准的 DSH bundle 包:package.json 里的 dsh.bundle.patch 指向 cordis.patch.yml,patch 里一条 insert 就把宿主半边挂进 profile。

方式一 · 在 DSH 桌面端里装(推荐)

用桌面端的插件管理器安装本目录。它会把这个包写进当前 profile 的 dsh.profile.bundles,不需要命令行。

方式二 · 手工写进 profile

在 profile 的 package.json 里加一条依赖和一个 bundle 条目(路径换成你本机的插件目录):

{
  "dependencies": {
    "@local/dsh-system-notify": "link:<本插件目录的绝对路径>"
  },
  "dsh": {
    "profile": {
      "bundles": ["...", "@local/dsh-system-notify"]
    }
  }
}

然后在 profile 目录里装一次依赖并重启 DSH。

首次使用:编译原生助手

通知真正的发送端是 native/ 下的 Windows App SDK 小助手(Node 自己弹不出系统通知)。有两种情况:

  • 自动:sink.autoBuild 默认开启。第一次要发通知时,插件会自己跑 native/build.ps1(没有这个脚本就退到 dotnet build native/*.csproj -c Release),构建期间的通知会排队等它;

  • 手动:自己先编一次,之后启动零延迟:

    需要 .NET SDK 10(原生侧目标 net10.0-windows10.0.19041.0)

    pwsh -NoProfile -File native/build.ps1

构建产物落在 native/bin/Release/(已被 .gitignore 排除)。想确认发送端是否通,用诊断接口的 /test(见下),或直接跑助手自检:

nativeinReleaseDshNotificationSink.exe --selftest

配置

写在 profile 的 cordis.patch.yml 里,按 id 覆盖:

- id: system-notify
  config:
    enabled: true

    sink:
      provider: windows          # windows | log | none
      helperPath: ''             # 留空=自动在 native/bin 下找
      appName: DeepSeek Harness
      appIconPath: ''            # 留空=用 native/assets 里的图标
      timeoutMs: 5000
      autoBuild: true
      fallbackProvider: log      # windows 发送失败后降级到哪

    throttle:
      dedupeWindowMs: 8000       # 同一件事的重复通知窗口
      minIntervalMs: 1000        # 同一类事件的最小间隔
      maxPerMinute: 15           # 全局每分钟上限
      quietHours:
        start: ''                # 'HH:mm',留空=不启用
        end: ''
        allowHighSeverity: false # 静音时段内高优先级是否仍放行

    scope:
      includeChildSessions: false      # 子 agent 会话要不要弹
      approvalGraceMs: 1500            # 授权通知的等待时间,0 = 立刻弹

    limits:
      maxTitleChars: 72
      maxBodyChars: 220
      queueLimit: 32

所有字段都有默认值,写错的值会被夹回合法范围并在日志里留一条 系统弹窗配置:… 说明——配置写错不会让插件挂不上,但也不会悄悄生效。

规则与开关

规则是一张有序分派表,第一条 match 命中的规则决定这条通知的归属与文案:

  • 命中但 enabled: false=吞掉这条通知,不再往下找更宽的规则。于是"关掉电脑操控弹窗"就真的不会再收到电脑操控弹窗,而不是悄悄降级成一条泛泛的"会话请求授权";
  • match 里没写的约束等于通配;
  • toolName 是不区分大小写的正则。

内置七条:

id 检测点 默认 说明
computer-control approval 开 走审批链路的操控类工具(工具名匹配 `computer
approval approval 开 兜住其余所有授权请求
computer-control-call tool-call 开 操控类工具被调用就提醒,5 分钟冷却
turn-completed turn-end 开 reason.kind = completed
turn-failed turn-end 开 error / blocked / max-tokens
turn-aborted turn-end 关 你亲手按的停止,通常不需要再弹一次
session-end session-end 开 会话销毁时那一轮还在跑
session-end-idle session-end 关 会话销毁时本来就空闲(几乎每次退出程序都会命中,默认不打扰)
agent-error agent-error 关 与 turn-failed 重复,按需打开

内置一共九条。写自定义 rules 时 signal 可取值:approval、tool-call、turn-end、session-end、agent-error;cooldownMs(0–3600000)是该规则的冷却时间,0 表示沿用全局 throttle.minIntervalMs。

自定义规则(rules 一旦写了就整体替换内置表,想保留内置的请一并抄过去):

- id: system-notify
  config:
    rules:
      - id: approval
        enabled: true
        signal: approval            # approval | tool-call | turn-end | session-end | agent-error
        match:
          toolName: 'bash|pwsh'     # 可选,正则
        title: '要你点头'
        body: '{tool} 在等授权{reasonText} · {session}'
        tag: approval               # tag 最长 16 字符(Windows 限制)
        urgency: high               # low | normal | high
        scenario: urgent            # default | urgent | reminder | alarm

可用占位符:{session} {project} {sessionId} {cwd} {tool} {reason} {reasonText} {turn} {reasonKind} {outcomeText} {error} {time}。写错的占位符会原样留在通知里,方便你一眼看出配置错了。

节流

规则通过之后还有四道闸门,命中即拒绝,拒绝原因都进统计(能在诊断接口里看到):

闸门 作用 默认
duplicate 同一个 dedupeKey(同一次审批、同一轮结束)在窗口内重复 8000 ms
interval 同一个 throttleKey 距上次发送太近 1000 ms
rate 全局每分钟上限(滑动窗口) 15 条
quiet 落在静音时段内 不启用

被拒绝的信号不占名额:只有真正发出去的那一次才记账。想彻底静音一段时间,比 maxPerMinute: 1 更直接的办法是 quietHours:

throttle:
  quietHours:
    start: '23:00'
    end: '07:00'
    allowHighSeverity: true   # 半夜的授权请求还是弹,完成通知不弹

发送是串行的:通知按事件顺序一条条发,队列满了(默认 32 条)就丢最旧的并警告一次。

诊断接口

挂了 connection 服务时(正常桌面端都有),插件注册三个 POST 路由:

# 配置摘要、规则表、节流统计、发送端状态、最近一次发送结果
curl -X POST http://127.0.0.1:<端口>/api/system-notify/status

# 立刻发一条测试通知(绕过节流,用来验证 Windows App SDK 链路)
curl -X POST http://127.0.0.1:<端口>/api/system-notify/test -d "{}"

# 清空节流状态
curl -X POST http://127.0.0.1:<端口>/api/system-notify/throttle/reset

/status 里最该先看的两处:sink.detail.helper.located(有没有找到原生助手)与 counters.skipped(到底是被关掉了、被节流压掉了,还是没有规则命中)。

排障

一条通知都没有

  1. 看 /api/system-notify/status 的 sink.detail.helper:located: null 说明原生助手没构建成功,lastError 里会写原因;
  2. 看 counters.skipped:child-session 表示全是子会话(打开 scope.includeChildSessions),rule-disabled 表示规则被你关掉了;
  3. 看 Windows 设置 → 通知:DeepSeek Harness 这一项是不是被关掉了。

弹是弹了,但显示的是 PowerShell 的图标/名字

助手在启动早期还没有设置 AppUserModelID。--selftest 里的 aumidRegistration 与 processAumid 能确认注册情况;注册在 HKCU 下,普通用户权限即可,不需要管理员。

构建失败

dotnet --list-sdks 要有 .NET SDK 10(原生侧目标是 net10.0-windows10.0.19041.0)或更新版本,nuget.org 要可达。失败信息会以一条 系统弹窗:构建失败(退出码 …) 出现在 DSH 日志里,修好之后重启 DSH,或先手动跑一次 native/build.ps1。

插件调用 native/build.ps1 时优先用 pwsh(PowerShell 7)。原因:Windows PowerShell 5.1 会按系统 ANSI 代码页解码无 BOM 的 UTF-8 脚本,脚本里的中文注释会让它误报"缺少结束符 }"——这条错看起来像语法错,其实是编码错。没有 pwsh 时插件会退回 5.1 并在日志里提示。

我不想要通知了

  • 暂时:enabled: false(检测照跑,一条都不发);
  • 只关某类:把对应规则的 enabled 设为 false;
  • 彻底:从 profile 的 bundles 里删掉这个包。

隐私

通知内容只在本机生成、只交给本机的原生助手,任何字段都不出网。默认只带:会话标题(或 cwd 最后一段)、工具名、授权原因、轮次与结束原因。不读取对话正文、不读取工具输出、不写数据库。

已知限制

  • 只有 Windows 有发送端。其他平台 sink.provider 会退到 log(只写日志),检测与节流照常工作;
  • 通知里的按钮点了不会有任何回调:原生助手是"一次调用一个进程",Show() 之后进程即退出,没人监听 NotificationInvoked。插件因此不发按钮(发了会让人误以为点了有用);
  • 原生助手是 framework-dependent 的,运行需要 .NET 10 运行时(装了 SDK 10 的构建机自带);另一台只部署不构建的机器上,要么装运行时,要么改成 self-contained 发布;
  • 同 tag 的通知不会互相替换,每条都是通知中心里的独立条目(Windows App SDK 不自动替换);
  • 单次发送约 0.5 秒(每次都要重新 Register()),所以通知是串行队列而不是并发;队列上限 32 条,超了丢最旧的;
  • Windows App SDK 路径失败时,原生助手会按设计降级到旧版 ToastNotificationManager(按钮、urgency 等特性可能不完全生效),降级信息写在 stderr 与 /status 里;
  • session/disposed 的真实语义:关会话标签、归档会话、子会话结束都不触发它;它只在关主窗口(≈退出程序)与插件重载时成批到达。所以 session-end 的文案是"第 N 轮没有跑完(会话已销毁)",而不是"你关掉了会话";空闲销毁由默认关闭的 session-end-idle 负责,否则每次退出都会抖一串。
  • 硬杀/崩溃没有 turn/end:进程被 SIGKILL 或崩溃时,那一轮不会写 turn/end,也就不会通知。要兜住只能在下次启动做补偿自检(遍历会话找末尾未闭合的 turn/start),当前版本没做。
  • 退出路径上的通知是尽力而为:宿主关停预算约 5 秒,桌面主进程最多再等 10 秒就 SIGTERM→SIGKILL,插件在插件卸载时会放弃还没发出的队列(并留一条警告)。

测试

node --test "test/*.test.mjs"

72 条用例,覆盖配置归一化、信号翻译、规则优先级、节流(含跨零点静音)、文案渲染、判定顺序、发送端降级、插件契约(含"不许碰 ctx.tool / ctx.systemPrompt"的 Proxy 断言),以及一组真 cordis 上下文的冒烟测试(装得上、收得到事件、卸载后不再发)。

那组真 cordis 测试在裸仓库里会跳过(解析不到 @deepseek-ai/cordis)。想强制运行,把 DSH_CORDIS_PATH 指到一个能解析依赖的 cordis 目录即可。

除了单测,这几条是真的跑过的:

  • 真 cordis 上下文 + windows 发送端:触发一次授权事件,Windows 通知中心里出现 tag=approval、scenario=urgent、正文为渲染结果的条目(--history 可查);
  • 冷构建:删掉 native/bin 后由插件自己 autoBuild(走 native/build.ps1)→ 找到 exe → 真实发出通知,全程约 4 秒(NuGet 缓存已热);
  • 突发压力:一轮里 20 次工具调用(含 6 次 computer_use)+ 5 次并发授权请求,最终只弹 2 条(冷却与节流按预期生效)。

结构

index.js                 插件入口:订阅事件、串行发送、诊断路由
lib/config.js            配置归一化与默认值(手写校验,零依赖)
lib/signals.js           运行时事件 → 信号(会话标题/轮次的记忆)
lib/rules.js             有序规则表与匹配
lib/throttle.js          去重 / 间隔 / 每分钟上限 / 静音时段
lib/engine.js            判定顺序:信号 + 规则 + 节流 → 通知或拒绝理由
lib/format.js            占位符渲染、单行化、截断
lib/sinks.js             windows / log / none 发送端与降级
lib/native.js            原生助手定位、构建、调用
native/                  Windows App SDK 通知助手(C#)
test/                    72 条用例
docs/native-sink.md      原生端协议:stdin/stdout JSON、错误码、AUMID、降级路径
docs/research/           运行时检测点调研(每条结论带 文件:行号)
docs/log/project-log.md  项目日志(按轮次追加)

License

MIT

—/ 5

No ratings yet

Verified DSH bundle

Commit 6a82b06ce438

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout