dsh-plugin-desktop-notify 0.5.6 → 0.5.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +43 -180
- package/README.md +39 -206
- package/lib/client.js +28 -12
- package/locale/en.json +2 -2
- package/locale/zh.json +1 -1
- package/package.json +2 -2
package/README.en.md
CHANGED
|
@@ -1,214 +1,77 @@
|
|
|
1
1
|
# dsh-plugin-desktop-notify
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
[DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) desktop app.
|
|
5
|
-
|
|
6
|
-
- A native banner, attributed to **DeepSeek Harness itself**, when a turn finishes
|
|
7
|
-
**even while the window is in the background**, when a tool approval / question /
|
|
8
|
-
plan review is waiting, or when a turn fails.
|
|
9
|
-
- **Click the banner to jump back to that session** and raise the window.
|
|
10
|
-
- The Dock icon keeps a number for every session that needs you until you actually
|
|
11
|
-
look at it.
|
|
12
|
-
|
|
13
|
-
[中文](README.md) · MIT
|
|
3
|
+
Desktop notifications and an app-icon badge for DSH: a finished turn, a pending approval or question, and a failure, with click-through back to that session.
|
|
14
4
|
|
|
15
5
|
## What it does
|
|
16
6
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
| You open (and look at) that session | — | cleared |
|
|
23
|
-
|
|
24
|
-
Rules:
|
|
25
|
-
|
|
26
|
-
- **A notification always implies a badge**: both read the same attention facts.
|
|
27
|
-
- **“Watching” means** the session is in the main view **and** the window is focused
|
|
28
|
-
**and** visible. The harness’s own `completionUnread` only covers the first case;
|
|
29
|
-
a backgrounded window is invisible to the host, so the plugin adds the
|
|
30
|
-
focus/visibility half itself with `document.hasFocus()` / `visibilityState`.
|
|
31
|
-
- **The badge is sticky** until you actually look at the session; the harness
|
|
32
|
-
clearing `completionUnread` does not silently drop it.
|
|
33
|
-
- A failed turn posts the error banner only, never an extra “completed” one.
|
|
34
|
-
- Sessions of subagents / Agent Teams members are ignored.
|
|
35
|
-
|
|
36
|
-
## Requirements
|
|
37
|
-
|
|
38
|
-
- Tested on **DSH Desktop 0.2.1-rc.3** (Electron **44.7.0**) on **macOS**.
|
|
39
|
-
- The desktop shell must register `dsh-app://` as a secure context (current builds do),
|
|
40
|
-
otherwise `navigator.setAppBadge` and `Notification` are unavailable
|
|
41
|
-
(`capabilities()` in DevTools reports `badge: false`).
|
|
42
|
-
- System Settings → Notifications → **DeepSeek Harness** must be allowed, otherwise
|
|
43
|
-
you get the badge without banners.
|
|
44
|
-
- **Windows / Linux: untested, but expected to work.** Both surfaces are plain web
|
|
45
|
-
APIs: Electron has supported the Badging API on Windows since v29 (taskbar overlay,
|
|
46
|
-
see [the v29.1.5 Windows badge fix](https://releases.electronjs.org/release/v29.1.5)),
|
|
47
|
-
and `Notification` maps to Windows toasts. The real caveat is that Windows toasts
|
|
48
|
-
need an AppUserModelID plus a Start Menu shortcut, and the DSH main process never
|
|
49
|
-
calls `app.setAppUserModelId`, so whether banners appear depends on the shortcut
|
|
50
|
-
the installer writes.
|
|
51
|
-
- The only genuinely macOS-only code is the **optional host fallback**
|
|
52
|
-
(`osascript` / `afplay` / `lsappinfo`), disabled by default.
|
|
53
|
-
- To check a platform: run `capabilities()` (it reports `platform`) and then
|
|
54
|
-
`simulateComplete()`, and open an issue with the result.
|
|
55
|
-
|
|
56
|
-
### Banner click does nothing?
|
|
57
|
-
|
|
58
|
-
Read `__DSH_DESKTOP_NOTIFY__.state().lastClick`:
|
|
59
|
-
|
|
60
|
-
- `null` → the click never reached the renderer. Rare on macOS, **common on Windows**:
|
|
61
|
-
WinRT toast activation needs a registered COM activator
|
|
62
|
-
([electron/electron#24524](https://github.com/electron/electron/issues/24524)), and without
|
|
63
|
-
one the click may simply dismiss the toast. A plugin cannot fix that layer.
|
|
64
|
-
- `opened.ok: false` → the click arrived but the session switch failed; `opened.attempts`
|
|
65
|
-
says why (`uiWorkspace` unreachable, `retain` failed, …). Since v0.5.2 the plugin declares
|
|
66
|
-
`uiWorkspace` as a dependency (the same way the official `ui-chat` plugin does), which is
|
|
67
|
-
what made the macOS click fail to switch sessions before.
|
|
68
|
-
- `opened.ok: true` but the view did not change → the window was hidden; the click only
|
|
69
|
-
activates the app, so bring it forward from the Dock/taskbar.
|
|
70
|
-
|
|
71
|
-
The click path can be exercised directly with `__DSH_DESKTOP_NOTIFY__.openSession("<session id>")`.
|
|
72
|
-
|
|
73
|
-
### Windows: the click switches the session, but the window may stay behind
|
|
74
|
-
|
|
75
|
-
Not something a plugin can fix. The click reaches the renderer (since v0.5.2
|
|
76
|
-
`lastClick.opened.ok === true` and the session really switches), but raising the main
|
|
77
|
-
window needs the main process (`BrowserWindow.show()` / `focus()`) or a registered
|
|
78
|
-
AppUserModelID. The renderer only has `webContents.focus()`, and Windows' foreground
|
|
79
|
-
lock ignores requests from non-foreground processes.
|
|
80
|
-
|
|
81
|
-
Two upstream fixes, either works:
|
|
82
|
-
|
|
83
|
-
1. Call `app.setAppUserModelId("com.deepseek.dsh")` in the desktop main process and ship a
|
|
84
|
-
Start Menu shortcut with the same AUMID — Windows then activates the app on toast
|
|
85
|
-
clicks, so the window comes forward.
|
|
86
|
-
2. Expose a "focus window" IPC channel the renderer can call.
|
|
87
|
-
|
|
88
|
-
Until then: if the window does not surface, click the taskbar/tray icon — **the session
|
|
89
|
-
is already selected**.
|
|
90
|
-
|
|
91
|
-
### Windows: badge appears, no banner
|
|
92
|
-
|
|
93
|
-
**Fixed in v0.4.4**: older builds passed a long `tag`
|
|
94
|
-
(`dsh-plugin-desktop-notify:complete:<sessionId>`), and Windows 11 silently drops web
|
|
95
|
-
notifications whose tag is longer than ~34 characters
|
|
96
|
-
([electron/electron#40433](https://github.com/electron/electron/issues/40433)). The
|
|
97
|
-
plugin no longer sets `tag` / `silent`; dedupe relies on status edges, which is why a
|
|
98
|
-
bare `new Notification("t",{body:"b"})` showed up while the plugin's banner did not.
|
|
99
|
-
|
|
100
|
-
If it still fails after upgrading, use the checks below:
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
The badge is a taskbar overlay and needs no AppUserModelID. **Windows toasts do need an
|
|
104
|
-
AppUserModelID plus a Start Menu shortcut**, and the DSH main process never calls
|
|
105
|
-
`app.setAppUserModelId` (checked `lib/main.js`), so Windows can drop the toast silently —
|
|
106
|
-
it is not the plugin's logic. Confirm with:
|
|
107
|
-
|
|
108
|
-
```js
|
|
109
|
-
new Notification("test", { body: "windows toast check" }) // nothing here means app-level
|
|
110
|
-
__DSH_DESKTOP_NOTIFY__.state().lastNotify // the last delivery attempt
|
|
111
|
-
```
|
|
7
|
+
- **Turn finished** — including while the window is backgrounded or another app is in front
|
|
8
|
+
- **You are needed** — a tool approval, a question, or a plan waiting for confirmation
|
|
9
|
+
- **Turn failed** — the error is summarised in the body
|
|
10
|
+
- **Badge** — the app icon carries the number of sessions that need you, cleared once you have looked
|
|
11
|
+
- **Click a notification** — it switches to that session instead of only raising the app
|
|
112
12
|
|
|
113
|
-
|
|
114
|
-
with click-through), or the host half falls back to a PowerShell toast (visible, but
|
|
115
|
-
attributed to Windows PowerShell and without click-through).
|
|
13
|
+
Subagent sessions are skipped by default.
|
|
116
14
|
|
|
117
15
|
## Install
|
|
118
16
|
|
|
119
|
-
|
|
17
|
+
DSH desktop → **Plugins** → Install, then paste the package name:
|
|
120
18
|
|
|
121
19
|
```
|
|
122
20
|
dsh-plugin-desktop-notify
|
|
123
21
|
```
|
|
124
22
|
|
|
125
|
-
Press **⌘R** afterwards
|
|
126
|
-
|
|
23
|
+
Press **⌘R** afterwards. The GitHub address
|
|
24
|
+
(`https://github.com/chendaleiQ/dsh-plugin-desktop-notify`) and an absolute local
|
|
25
|
+
directory work the same way.
|
|
127
26
|
|
|
128
|
-
|
|
129
|
-
(`https://github.com/chendaleiQ/dsh-plugin-desktop-notify`) and a local checkout
|
|
130
|
-
directory both work the same way.
|
|
131
|
-
For development, symlink it into the profile:
|
|
27
|
+
## Preferences
|
|
132
28
|
|
|
133
|
-
|
|
134
|
-
git clone https://github.com/chendaleiQ/dsh-plugin-desktop-notify.git
|
|
135
|
-
cd dsh-plugin-desktop-notify
|
|
136
|
-
ln -s "$PWD" ~/.dsh/profiles/desktop/node_modules/dsh-plugin-desktop-notify
|
|
137
|
-
# then add "dsh-plugin-desktop-notify" to dsh.profile.bundles in
|
|
138
|
-
# ~/.dsh/profiles/desktop/package.json
|
|
139
|
-
```
|
|
29
|
+
Stored in localStorage under `dsh-plugin-desktop-notify.preferences`:
|
|
140
30
|
|
|
141
|
-
|
|
31
|
+
| Field | Default | Meaning |
|
|
32
|
+
| --- | --- | --- |
|
|
33
|
+
| `notifications` | `true` | Master switch |
|
|
34
|
+
| `notifyComplete` | `true` | Notify when a turn finishes |
|
|
35
|
+
| `notifyPending` | `true` | Notify for an approval / question / plan |
|
|
36
|
+
| `notifyError` | `true` | Notify when a turn fails |
|
|
37
|
+
| `notifyViewed` | `false` | Also notify for the session you are looking at |
|
|
38
|
+
| `badge` | `true` | Show the badge |
|
|
39
|
+
| `excludeSubagents` | `true` | Ignore subagent sessions |
|
|
40
|
+
| `titleComplete` / `titlePending` / `titleError` | empty | Custom titles; empty keeps the built-in copy |
|
|
41
|
+
| `maxBodyChars` | `140` | Body length limit |
|
|
142
42
|
|
|
143
|
-
|
|
43
|
+
## Language
|
|
144
44
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
__DSH_DESKTOP_NOTIFY__.state() // live attention + badge number
|
|
148
|
-
__DSH_DESKTOP_NOTIFY__.simulateComplete() // fire a banner and set the badge to 1
|
|
149
|
-
__DSH_DESKTOP_NOTIFY__.reset() // undo the simulation
|
|
150
|
-
__DSH_DESKTOP_NOTIFY__.state().lastClick // what the last banner click did
|
|
151
|
-
__DSH_DESKTOP_NOTIFY__.openSession("<session id>") // run the click path manually
|
|
152
|
-
```
|
|
45
|
+
The copy follows DSH's language setting (Settings → General → Language): Chinese
|
|
46
|
+
by default, English once the app is switched.
|
|
153
47
|
|
|
154
|
-
##
|
|
48
|
+
## Self-check
|
|
155
49
|
|
|
156
|
-
|
|
157
|
-
(desktop and web profiles are independent), applied after ⌘R:
|
|
50
|
+
From the renderer console:
|
|
158
51
|
|
|
159
52
|
```js
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
notifyPending: true, // approvals / questions / plan reviews
|
|
164
|
-
notifyError: true,
|
|
165
|
-
notifyViewed: false, // true = also banner the session you are watching
|
|
166
|
-
badge: true, // Dock badge master switch
|
|
167
|
-
excludeSubagents: true,
|
|
168
|
-
// empty = built-in copy, following the UI language (zh/en)
|
|
169
|
-
titleComplete: "",
|
|
170
|
-
titlePending: "",
|
|
171
|
-
titleError: "",
|
|
172
|
-
maxBodyChars: 140
|
|
173
|
-
}))
|
|
53
|
+
__DSH_DESKTOP_NOTIFY__.capabilities()
|
|
54
|
+
__DSH_DESKTOP_NOTIFY__.state()
|
|
55
|
+
__DSH_DESKTOP_NOTIFY__.report()
|
|
174
56
|
```
|
|
175
57
|
|
|
176
|
-
##
|
|
177
|
-
|
|
178
|
-
Both surfaces only exist in the desktop **renderer**, not in the Host process:
|
|
179
|
-
|
|
180
|
-
| Capability | API | Lands at |
|
|
181
|
-
|---|---|---|
|
|
182
|
-
| Native banner + click callback | `new Notification(...)` / `onclick` | Electron → macOS Notification Center, attributed to DeepSeek Harness |
|
|
183
|
-
| Dock badge | `navigator.setAppBadge(n)` / `clearAppBadge()` | Chromium → Electron `BadgeManager` → `NSDockTile.badgeLabel` |
|
|
58
|
+
## Known limits
|
|
184
59
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
60
|
+
- **Windows**: clicking a notification switches the session, but the window does
|
|
61
|
+
not always come to the front — the AppUserModelID and a main-process focus
|
|
62
|
+
channel are the desktop app's to provide
|
|
63
|
+
- **Attribution**: the notification centre shows DeepSeek Harness, because the
|
|
64
|
+
banner is a renderer-side native notification
|
|
65
|
+
- **Host half** (`index.js`, an osascript fallback) is off by default; switch it on
|
|
66
|
+
only when a renderer cannot post notifications
|
|
192
67
|
|
|
193
68
|
## Development
|
|
194
69
|
|
|
195
|
-
Zero dependencies, hand-written ES modules, no build step.
|
|
196
|
-
|
|
197
70
|
```bash
|
|
198
|
-
node test/client.test.mjs
|
|
199
|
-
node test/host.test.mjs
|
|
200
|
-
npm test
|
|
71
|
+
node test/client.test.mjs
|
|
72
|
+
node test/host.test.mjs
|
|
201
73
|
```
|
|
202
74
|
|
|
203
|
-
## Limitations
|
|
204
|
-
|
|
205
|
-
- macOS is verified; Windows / Linux are untested (see Requirements).
|
|
206
|
-
- “Stuck” covers waiting-for-approval / question / plan review / failure; a
|
|
207
|
-
“still running but silent for N minutes” watchdog would need a host timer.
|
|
208
|
-
- Badge facts live in host memory and reset when the app restarts (harness semantics).
|
|
209
|
-
- Conflicts with `@caohaoyangcn/dsh-dock-badge` (badge-only, with a settings page):
|
|
210
|
-
install only one, they fight over the same Dock badge.
|
|
211
|
-
|
|
212
75
|
## License
|
|
213
76
|
|
|
214
|
-
MIT
|
|
77
|
+
MIT · https://github.com/chendaleiQ/dsh-plugin-desktop-notify
|
package/README.md
CHANGED
|
@@ -1,238 +1,71 @@
|
|
|
1
1
|
# dsh-plugin-desktop-notify
|
|
2
2
|
|
|
3
|
-
给
|
|
3
|
+
给 DSH 加**桌面通知 + 应用图标小红点**:任务完成、等待你操作、任务失败时提醒你,点通知直接回到那个会话。
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
- 有工具等你审批、有提问等你回答、有计划等你确认时弹「需要你确认」;
|
|
7
|
-
- 任务出错时弹「任务出错」,正文带错误摘要;
|
|
8
|
-
- **点横幅直接回到那个会话**并抬起窗口;
|
|
9
|
-
- 需要你处理的会话在 Dock 图标上留一个数字,直到你真的看过它才清零。
|
|
5
|
+
## 它做什么
|
|
10
6
|
|
|
11
|
-
|
|
7
|
+
- **任务完成**——包括窗口切到后台、你在别的应用里的时候
|
|
8
|
+
- **等待你操作**——工具审批、提问、执行计划确认
|
|
9
|
+
- **任务失败**——这一轮出错时提示
|
|
10
|
+
- **小红点**——应用图标上显示「需要你的会话数」,你看过之后才清零
|
|
11
|
+
- **点击通知**——切到对应会话,而不是只把应用叫到前面
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
| Codex 桌面版 | 这里 |
|
|
16
|
-
|---|---|
|
|
17
|
-
| Settings → Notifications → **Task completion** | 标题「任务已完成」,正文「对话名」已完成,用时 3 分 12 秒 |
|
|
18
|
-
| Settings → Notifications → **Approval required** | 标题「等待你的批准」,正文「对话名」请求使用 Bash,等待你的批准 |
|
|
19
|
-
| 等你回复(提问 / 计划确认) | 标题「等待你的回答」/「等待你的确认」,正文带会话名 |
|
|
20
|
-
| 任务失败 | 标题「任务执行失败」,正文带会话名与错误摘要 |
|
|
21
|
-
| Dock 徽标 | 数字 = **当前需要你的会话数**;打开该会话即清 |
|
|
22
|
-
|
|
23
|
-
规则:
|
|
24
|
-
|
|
25
|
-
- **有通知就有红点**:两者读同一份 attention 事实,弹了横幅就一定进徽标。
|
|
26
|
-
- **「在看」才算看过**:`主视图是它` **且** `窗口有焦点` **且** `窗口可见`,三者同时成立才不打扰。
|
|
27
|
-
宿主的 `completionUnread` 只覆盖第一种(切到别的会话);窗口被切到后台时它看不见,
|
|
28
|
-
这部分由插件用 `document.hasFocus()` / `visibilityState` 自己补——否则最常见的那种
|
|
29
|
-
「切到别的 App 等它跑完」永远不会提醒。
|
|
30
|
-
- **红点粘住**:弹过横幅的会话,红点保留到你真的切回来并看着它;宿主把 `completionUnread`
|
|
31
|
-
清掉并不会让红点消失。
|
|
32
|
-
- **失败不重复播**:一轮以错误结束时只发「任务出错」,不再补一条「任务完成」。
|
|
33
|
-
- 子会话(subagent / Agent Teams 成员)不弹不计;同一会话同类横幅用 `tag` 覆盖,不堆一屏。
|
|
34
|
-
|
|
35
|
-
## 兼容性
|
|
36
|
-
|
|
37
|
-
- **已在 macOS 实测**:DSH Desktop 0.2.1-rc.3(Electron 44.7.0)。
|
|
38
|
-
- 需要桌面端把 `dsh-app://` 注册为 secure context(现版本就是这么做的),否则渲染层拿不到
|
|
39
|
-
`navigator.setAppBadge` 与 `Notification`;`capabilities()` 自检里会显示 `badge: false`。
|
|
40
|
-
- 系统设置 → 通知 → **DeepSeek Harness** 必须是允许状态,否则只有红点没有横幅。
|
|
41
|
-
- **Windows / Linux:未实测,但理论上可用**。渲染层用的是标准 Web API:
|
|
42
|
-
Electron 从 29 起支持 Windows 上的 Badging API(任务栏 overlay,修过 [Windows 下徽标文字不显示](https://releases.electronjs.org/release/v29.1.5)),
|
|
43
|
-
`Notification` 走 Windows toast。真正的风险在 Windows 的 toast 需要 AppUserModelID +
|
|
44
|
-
开始菜单快捷方式,而 DSH 主进程没有显式 `setAppUserModelId`(我查过 `lib/main.js`),
|
|
45
|
-
所以能不能出横幅取决于安装包写的快捷方式。
|
|
46
|
-
- **唯一确定只在 macOS 的是宿主兜底那半边**(`osascript` / `afplay` / `lsappinfo`),
|
|
47
|
-
它默认关闭,只在你没有 `Notification` 时才需要。
|
|
48
|
-
- 想验证 Windows:跑一次 `capabilities()`(会带上 `platform`),再 `simulateComplete()`,
|
|
49
|
-
看 toast 与任务栏数字是否出现,把结果开个 issue 就行。
|
|
50
|
-
|
|
51
|
-
### 点击通知没反应?
|
|
52
|
-
|
|
53
|
-
先看 `__DSH_DESKTOP_NOTIFY__.state().lastClick`:
|
|
54
|
-
|
|
55
|
-
- `null` → 点击事件根本没送到渲染层。macOS 上基本不会;**Windows 上很常见**:Electron 的 WinRT toast 点击激活需要 COM activator 注册([electron/electron#24524](https://github.com/electron/electron/issues/24524)),没注册时点横幅可能只是把系统通知收起。这一层插件修不了。
|
|
56
|
-
- `opened.ok: false` → 点击到了,但切会话失败,`opened.attempts` 里写着原因(`uiWorkspace` 服务不可达 / `retain` 失败等)。
|
|
57
|
-
v0.5.2 起插件把 `uiWorkspace` 声明成了依赖(和官方 `ui-chat` 插件一致),此前它在这个插件里是取不到的——这就是 macOS 上「点击只激活应用、不切会话」的原因。
|
|
58
|
-
- `opened.ok: true` 但界面没变 → 窗口被藏起来了(点横幅只激活应用,不一定会显示窗口),手动点 Dock/任务栏图标把窗口调出来即可。
|
|
59
|
-
|
|
60
|
-
也可以直接验证切会话这条链路:`__DSH_DESKTOP_NOTIFY__.openSession("<会话 id>")`。
|
|
61
|
-
|
|
62
|
-
### Windows:点通知切会话成功,但窗口不一定到最前
|
|
63
|
-
|
|
64
|
-
这不是插件能修的:点击事件已经送到渲染层(v0.5.2 起 `lastClick.opened.ok === true`,会话也确实切了),
|
|
65
|
-
但把**主窗口提到前台**需要主进程的 `BrowserWindow.show()` / `focus()`,或者系统层面的
|
|
66
|
-
AppUserModelID 注册;渲染层只有 `webContents.focus()`,而 Windows 的前台锁定会忽略来自
|
|
67
|
-
非前台进程的请求。
|
|
68
|
-
|
|
69
|
-
两种上游修法(任一即可):
|
|
70
|
-
|
|
71
|
-
1. 桌面端主进程调用 `app.setAppUserModelId("com.deepseek.dsh")`,并让安装包写出带同一 AUMID
|
|
72
|
-
的开始菜单快捷方式——Windows 就会在点击 toast 时激活应用,窗口自然到前台。
|
|
73
|
-
2. 主进程暴露一个「focus window」的 IPC 通道,让插件请求抬窗。
|
|
74
|
-
|
|
75
|
-
在此之前:点通知后若窗口没上来,点任务栏/托盘图标即可,**会话已经切好了**。
|
|
76
|
-
|
|
77
|
-
### Windows 上「有红点、没横幅」
|
|
78
|
-
|
|
79
|
-
**v0.4.4 已修**:旧版本给每条通知带了一个很长的 `tag`(`dsh-plugin-desktop-notify:complete:<sessionId>`),
|
|
80
|
-
而 Windows 11 会**静默丢弃 tag 长度超过约 34 字符的 Web Notification**([electron/electron#40433](https://github.com/electron/electron/issues/40433))。
|
|
81
|
-
现在不再设置 `tag` / `silent`,去重改由状态边沿判断完成——所以裸 `new Notification("t",{body:"b"})` 能弹、
|
|
82
|
-
插件的不弹,正是这个原因。
|
|
83
|
-
|
|
84
|
-
如果升级后仍不弹,再按下面排查:
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
红点走的是任务栏 overlay,不需要 AppUserModelID;**Windows toast 需要 AppUserModelID +
|
|
88
|
-
开始菜单快捷方式**才会显示。DSH 主进程没有调用 `app.setAppUserModelId`(`lib/main.js` 里没有
|
|
89
|
-
这行),所以 toast 很可能被 Windows 静默丢弃——这不是插件逻辑问题。先确认:
|
|
90
|
-
|
|
91
|
-
```js
|
|
92
|
-
new Notification("test", { body: "windows toast check" }) // 连这条都不弹 = 应用层问题
|
|
93
|
-
__DSH_DESKTOP_NOTIFY__.state().lastNotify // 插件最后一次投递的结果
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
两条路:等 DSH 主进程补上 `app.setAppUserModelId`(那时现有代码就能正常出 toast 并支持点击);
|
|
97
|
-
或改用宿主侧 PowerShell toast 兜底(能提示,但归属会显示成 Windows PowerShell,点击无法跳回会话)。
|
|
13
|
+
子智能体的会话默认不提醒(可在偏好里打开)。
|
|
98
14
|
|
|
99
15
|
## 安装
|
|
100
16
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
DSH 桌面版左侧栏 **插件** → **安装**,填包名:
|
|
17
|
+
DSH 桌面版 → 左侧栏「插件」→ 安装,填包名:
|
|
104
18
|
|
|
105
19
|
```
|
|
106
20
|
dsh-plugin-desktop-notify
|
|
107
21
|
```
|
|
108
22
|
|
|
109
|
-
装完按 **⌘R**
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
> 国内网络可以给 npm 配镜像(`npm config set registry https://registry.npmmirror.com`),
|
|
113
|
-
> 或者直接用下面的 GitHub / 本地方式。
|
|
114
|
-
|
|
115
|
-
### 方式二:从 GitHub 安装
|
|
116
|
-
|
|
117
|
-
插件页 → 安装 → 填仓库地址:
|
|
118
|
-
|
|
119
|
-
```
|
|
120
|
-
https://github.com/chendaleiQ/dsh-plugin-desktop-notify
|
|
121
|
-
```
|
|
23
|
+
装完按 **⌘R** 刷新窗口。也可以填 GitHub 地址
|
|
24
|
+
(`https://github.com/chendaleiQ/dsh-plugin-desktop-notify`)或本地目录的绝对路径。
|
|
122
25
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
```bash
|
|
126
|
-
git clone https://github.com/chendaleiQ/dsh-plugin-desktop-notify.git
|
|
127
|
-
```
|
|
26
|
+
## 偏好
|
|
128
27
|
|
|
129
|
-
|
|
28
|
+
存在浏览器 localStorage 的 `dsh-plugin-desktop-notify.preferences`:
|
|
130
29
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
30
|
+
| 字段 | 默认 | 说明 |
|
|
31
|
+
| --- | --- | --- |
|
|
32
|
+
| `notifications` | `true` | 总开关 |
|
|
33
|
+
| `notifyComplete` | `true` | 任务完成时通知 |
|
|
34
|
+
| `notifyPending` | `true` | 等待审批 / 回答 / 计划确认时通知 |
|
|
35
|
+
| `notifyError` | `true` | 任务失败时通知 |
|
|
36
|
+
| `notifyViewed` | `false` | 正在看的会话也通知(默认跳过) |
|
|
37
|
+
| `badge` | `true` | 显示小红点 |
|
|
38
|
+
| `excludeSubagents` | `true` | 忽略子智能体会话 |
|
|
39
|
+
| `titleComplete` / `titlePending` / `titleError` | 空 | 自定义标题,空 = 用内置文案 |
|
|
40
|
+
| `maxBodyChars` | `140` | 正文长度上限 |
|
|
134
41
|
|
|
135
|
-
|
|
42
|
+
## 语言
|
|
136
43
|
|
|
137
|
-
|
|
138
|
-
git clone https://github.com/chendaleiQ/dsh-plugin-desktop-notify.git
|
|
139
|
-
cd dsh-plugin-desktop-notify
|
|
140
|
-
ln -s "$PWD" ~/.dsh/profiles/desktop/node_modules/dsh-plugin-desktop-notify
|
|
141
|
-
# 再把 dsh-plugin-desktop-notify 加进 ~/.dsh/profiles/desktop/package.json 的 dsh.profile.bundles
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
改完代码按 ⌘R 重载渲染层即可(Host 那半边改动需要重启应用)。
|
|
44
|
+
跟随 DSH 的语言设置(设置 → 通用 → 语言):默认中文,切成英文后通知文案同步变成英文。
|
|
145
45
|
|
|
146
46
|
## 自检
|
|
147
47
|
|
|
148
|
-
|
|
48
|
+
渲染层控制台:
|
|
149
49
|
|
|
150
50
|
```js
|
|
151
|
-
__DSH_DESKTOP_NOTIFY__.capabilities()
|
|
152
|
-
__DSH_DESKTOP_NOTIFY__.state()
|
|
153
|
-
__DSH_DESKTOP_NOTIFY__.
|
|
154
|
-
__DSH_DESKTOP_NOTIFY__.reset() // 清掉自检状态
|
|
155
|
-
__DSH_DESKTOP_NOTIFY__.state().lastClick // 上一次点横幅发生了什么
|
|
156
|
-
__DSH_DESKTOP_NOTIFY__.openSession("<会话 id>") // 直接按点击逻辑切会话,验证服务可达
|
|
51
|
+
__DSH_DESKTOP_NOTIFY__.capabilities() // 平台、通知权限、小红点、服务可达性
|
|
52
|
+
__DSH_DESKTOP_NOTIFY__.state() // 小红点、待处理会话、最近一次通知/点击
|
|
53
|
+
__DSH_DESKTOP_NOTIFY__.report() // 上面全部 + 窗口焦点,便于排查
|
|
157
54
|
```
|
|
158
55
|
|
|
159
|
-
|
|
160
|
-
点横幅回来,数字清零。
|
|
161
|
-
|
|
162
|
-
## 偏好
|
|
163
|
-
|
|
164
|
-
没有设置界面,偏好放在渲染进程本地(desktop / web profile 各自独立),控制台执行后 ⌘R:
|
|
165
|
-
|
|
166
|
-
```js
|
|
167
|
-
localStorage.setItem("dsh-plugin-desktop-notify:preferences", JSON.stringify({
|
|
168
|
-
notifications: true, // 总开关:横幅
|
|
169
|
-
notifyComplete: true, // 任务完成
|
|
170
|
-
notifyPending: true, // 等审批 / 提问 / 计划确认
|
|
171
|
-
notifyError: true, // 任务失败
|
|
172
|
-
notifyViewed: false, // true = 盯着看它也弹(但不进红点)
|
|
173
|
-
badge: true, // 总开关:Dock 红点
|
|
174
|
-
excludeSubagents: true, // 子会话不弹不计(建议保持)
|
|
175
|
-
// 留空 = 用内置文案(跟随界面语言 zh/en)
|
|
176
|
-
titleComplete: "",
|
|
177
|
-
titlePending: "",
|
|
178
|
-
titleError: "",
|
|
179
|
-
maxBodyChars: 140
|
|
180
|
-
}))
|
|
181
|
-
// 恢复默认
|
|
182
|
-
localStorage.removeItem("dsh-plugin-desktop-notify:preferences")
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
## 为什么是渲染层插件
|
|
186
|
-
|
|
187
|
-
这两件事只能在桌面**渲染进程**做,Host 子进程碰不到:
|
|
188
|
-
|
|
189
|
-
| 能力 | 接口 | 去处 |
|
|
190
|
-
|---|---|---|
|
|
191
|
-
| 原生横幅 + 点击回调 | `new Notification(...)` / `onclick` | Electron → macOS 通知中心,**归属 DeepSeek Harness** |
|
|
192
|
-
| Dock 小红点 | `navigator.setAppBadge(n)` / `clearAppBadge()` | Chromium → Electron `BadgeManager` → `NSDockTile.badgeLabel` |
|
|
193
|
-
|
|
194
|
-
宿主半边(`index.js`)保留成**默认关闭的 osascript 兜底**:万一某个渲染环境没有 `Notification`,
|
|
195
|
-
把 `cordis.patch.yml` 里的 `enabled` 改成 `true` 再重启——两者同时开会重复提醒。
|
|
196
|
-
|
|
197
|
-
数据源用的是 Harness 自己的 `uiSession.sessionStatus`(`completionUnread` / `pendingInteraction`,
|
|
198
|
-
和侧边栏状态点同源)+ `api-session/error`(失败),插件不自己记账,所以「读过」的语义和官方 UI 一致。
|
|
199
|
-
|
|
200
|
-
## 目录
|
|
201
|
-
|
|
202
|
-
```
|
|
203
|
-
dsh-plugin-desktop-notify/
|
|
204
|
-
├── package.json # dsh.bundle.patch + dsh.client(渲染层入口与 inject 顺序)
|
|
205
|
-
├── cordis.patch.yml # desktop-notify 一行(宿主兜底默认 enabled: false)
|
|
206
|
-
├── lib/client.js # 渲染层:横幅 + 点击跳转 + Dock 徽标
|
|
207
|
-
├── index.js # Host 半边:osascript 兜底,默认关闭
|
|
208
|
-
├── test/client.test.mjs # 渲染层测试(node:vm + 假浏览器全局)
|
|
209
|
-
├── test/host.test.mjs # 宿主兜底测试
|
|
210
|
-
├── icon.svg / locale/{en,zh}.json / README(.en).md / LICENSE
|
|
211
|
-
```
|
|
56
|
+
## 已知限制
|
|
212
57
|
|
|
213
|
-
|
|
58
|
+
- **Windows**:点通知会切到对应会话,但窗口不一定到前台——`AppUserModelID` 和主进程 focus 通道属于桌面端,渲染层插件做不到
|
|
59
|
+
- **通知归属**:系统通知中心里显示为 DeepSeek Harness(用的是渲染层原生通知)
|
|
60
|
+
- **宿主半边**(`index.js`,osascript 兜底)默认关闭;只有渲染层拿不到 `Notification` 时才需要打开
|
|
214
61
|
|
|
215
|
-
|
|
62
|
+
## 开发
|
|
216
63
|
|
|
217
64
|
```bash
|
|
218
|
-
node test/client.test.mjs
|
|
219
|
-
node test/host.test.mjs
|
|
220
|
-
npm test # 两个都跑
|
|
65
|
+
node test/client.test.mjs # 渲染层:文案、小红点、点击链路
|
|
66
|
+
node test/host.test.mjs # 宿主兜底:dryRun 下的事件处理
|
|
221
67
|
```
|
|
222
68
|
|
|
223
|
-
|
|
224
|
-
完成/审批/失败各自增减、失败不重复播、子会话排除、各偏好开关。
|
|
225
|
-
|
|
226
|
-
## 已知限制
|
|
227
|
-
|
|
228
|
-
- macOS 已实测;Windows / Linux 未实测(见上面「兼容性」)。
|
|
229
|
-
- 「卡住」目前覆盖**等待审批 / 提问 / 计划确认 / 失败**;没有做「还在 running 但长时间无输出」
|
|
230
|
-
的超时判定(需要宿主侧定时器)。
|
|
231
|
-
- `completionUnread` 与失败事实在宿主内存里,重启应用从零开始(官方语义)。
|
|
232
|
-
- 点击横幅若对应会话已归档,只会抬窗。
|
|
233
|
-
- 与 [`@caohaoyangcn/dsh-dock-badge`](https://www.npmjs.com/package/@caohaoyangcn/dsh-dock-badge)
|
|
234
|
-
功能重叠(那个只做徽标、带设置页),**两个都装会抢写同一个 Dock 徽标,只留一个**。
|
|
235
|
-
|
|
236
|
-
## License
|
|
69
|
+
## 协议
|
|
237
70
|
|
|
238
|
-
MIT
|
|
71
|
+
MIT · 仓库 https://github.com/chendaleiQ/dsh-plugin-desktop-notify
|
package/lib/client.js
CHANGED
|
@@ -91,7 +91,7 @@ window.__ModuleLoader__.load({
|
|
|
91
91
|
return name === "" ? text : `「${name}」${text}`
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
/** The UI language
|
|
94
|
+
/** The UI language before the app's locale service answers: `<html lang>`, then the browser. */
|
|
95
95
|
function activeLanguage() {
|
|
96
96
|
try {
|
|
97
97
|
const html = typeof document === "undefined" ? "" : document.documentElement?.lang ?? ""
|
|
@@ -104,14 +104,14 @@ window.__ModuleLoader__.load({
|
|
|
104
104
|
return "zh"
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
/** The copy table for
|
|
108
|
-
function copy() {
|
|
109
|
-
return COPY[
|
|
107
|
+
/** The copy table for one UI language. */
|
|
108
|
+
function copy(language) {
|
|
109
|
+
return COPY[language] ?? COPY.zh
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
/** Compact elapsed time: "42 秒" / "3 分 12 秒" / "1 小时 5 分". */
|
|
113
|
-
function durationText(milliseconds) {
|
|
114
|
-
const table = copy()
|
|
113
|
+
function durationText(milliseconds, language) {
|
|
114
|
+
const table = copy(language)
|
|
115
115
|
const total = Math.max(1, Math.round(milliseconds / 1000))
|
|
116
116
|
if (total < 60) return table.seconds(total)
|
|
117
117
|
const minutes = Math.floor(total / 60)
|
|
@@ -243,12 +243,28 @@ window.__ModuleLoader__.load({
|
|
|
243
243
|
}
|
|
244
244
|
}
|
|
245
245
|
|
|
246
|
-
const inject = ["sessions", "uiSession", "uiWorkspace"]
|
|
246
|
+
const inject = ["sessions", "uiSession", "uiWorkspace", "locale"]
|
|
247
247
|
|
|
248
248
|
/**
|
|
249
249
|
* @param ctx - Client root context.
|
|
250
250
|
*/
|
|
251
251
|
function apply(ctx) {
|
|
252
|
+
/**
|
|
253
|
+
* The app's own language, in the order DSH resolves it: the locale service
|
|
254
|
+
* (Settings → General), then `<html lang>`, then the browser. Chinese is
|
|
255
|
+
* the default; English is the only other language this plugin ships.
|
|
256
|
+
*/
|
|
257
|
+
const languageOf = () => {
|
|
258
|
+
try {
|
|
259
|
+
const active = String(ctx.locale?.getSnapshot?.().active ?? "").toLowerCase()
|
|
260
|
+
if (active.startsWith("zh")) return "zh"
|
|
261
|
+
if (active.startsWith("en")) return "en"
|
|
262
|
+
} catch {
|
|
263
|
+
/* an older host without the locale service falls back below */
|
|
264
|
+
}
|
|
265
|
+
return activeLanguage()
|
|
266
|
+
}
|
|
267
|
+
|
|
252
268
|
const preferences = readPreferences()
|
|
253
269
|
askPermission()
|
|
254
270
|
|
|
@@ -439,13 +455,13 @@ window.__ModuleLoader__.load({
|
|
|
439
455
|
if (!turnStartedAt.has(sessionId)) turnStartedAt.set(sessionId, Date.now())
|
|
440
456
|
}
|
|
441
457
|
|
|
442
|
-
const table = copy()
|
|
458
|
+
const table = copy(languageOf())
|
|
443
459
|
const name = sessionName(summary, preferences.maxBodyChars)
|
|
444
460
|
|
|
445
461
|
if (completionEdge) {
|
|
446
462
|
const started = turnStartedAt.get(sessionId)
|
|
447
463
|
turnStartedAt.delete(sessionId)
|
|
448
|
-
const duration = started === undefined ? "" : durationText(Date.now() - started)
|
|
464
|
+
const duration = started === undefined ? "" : durationText(Date.now() - started, languageOf())
|
|
449
465
|
const title = preferences.titleComplete || table.completeTitle
|
|
450
466
|
const body = bodyText(table.complete(name, duration), preferences.maxBodyChars)
|
|
451
467
|
if (looking) {
|
|
@@ -498,7 +514,7 @@ window.__ModuleLoader__.load({
|
|
|
498
514
|
const summary = summaries[sessionId]
|
|
499
515
|
if (preferences.excludeSubagents && isSubagent(summary)) return
|
|
500
516
|
if (isLooking(summary)) return
|
|
501
|
-
const table = copy()
|
|
517
|
+
const table = copy(languageOf())
|
|
502
518
|
const raw = typeof message === "string" ? message.trim() : ""
|
|
503
519
|
const text = bodyText(raw, preferences.maxBodyChars)
|
|
504
520
|
const before = attention.get(sessionId) ?? {}
|
|
@@ -622,9 +638,9 @@ window.__ModuleLoader__.load({
|
|
|
622
638
|
}
|
|
623
639
|
},
|
|
624
640
|
simulateComplete(sessionId = "desktop-notify-simulation") {
|
|
625
|
-
const table = copy()
|
|
641
|
+
const table = copy(languageOf())
|
|
626
642
|
setAttention(sessionId, { completed: true })
|
|
627
|
-
notify("complete", sessionId, preferences.titleComplete || table.completeTitle, table.complete("", durationText(12000)))
|
|
643
|
+
notify("complete", sessionId, preferences.titleComplete || table.completeTitle, table.complete("", durationText(12000, languageOf())))
|
|
628
644
|
applyBadge()
|
|
629
645
|
return this.state()
|
|
630
646
|
},
|
package/locale/en.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"meta": {
|
|
3
|
-
"title": "Desktop Notifications &
|
|
4
|
-
"description": "
|
|
3
|
+
"title": "Desktop Notifications & Badge",
|
|
4
|
+
"description": "A system notification when a turn finishes, an approval or question is waiting, or a task fails, plus a badge on the app icon counting the sessions that need you. Click the notification to open that session."
|
|
5
5
|
}
|
|
6
6
|
}
|
package/locale/zh.json
CHANGED
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-desktop-notify",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.7",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "Desktop notifications and a Dock/taskbar badge for DeepSeek Harness: a finished turn, a pending approval or question, and a failure, with click-through back to the session",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"main": "index.js",
|
|
8
8
|
"exports": {
|