dsh-plugin-session-notifier 0.1.1 → 0.1.3
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 +21 -9
- package/README.md +23 -10
- package/client.js +61 -20
- package/locale/en.json +2 -2
- package/locale/zh.json +2 -2
- package/package.json +3 -2
package/README.en.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](https://www.npmjs.com/package/dsh-plugin-session-notifier)
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
|
|
8
|
-
A DeepSeek Harness bundle. It raises a desktop notification and a short chime when a session finishes,
|
|
8
|
+
A DeepSeek Harness session notification bundle. It raises a desktop notification and a short chime when a session finishes,
|
|
9
9
|
fails, or stops to wait for your answer (a question or a tool approval). Nothing is missed while you
|
|
10
10
|
are in another window, watching a video, or playing a game.
|
|
11
11
|
|
|
@@ -21,7 +21,7 @@ The interface follows DSH's Chinese/English setting.
|
|
|
21
21
|
| The session fails | A low two-note chime and an error notification |
|
|
22
22
|
| The session you are looking at ends | Reminded after about 1 second, effectively immediate |
|
|
23
23
|
| Any other session ends | Waits out the 10/20/30 second merge window to avoid a burst |
|
|
24
|
-
|
|
|
24
|
+
| Any other session resumes inside the merge window | Merged into one reminder |
|
|
25
25
|
| You do not come back | Repeats every 30 seconds, at most 3 times, and stops as soon as the page is focused |
|
|
26
26
|
| Subagent sessions | Silent, so one task's internal fan-out never rings |
|
|
27
27
|
|
|
@@ -43,22 +43,33 @@ The panel says so as well.
|
|
|
43
43
|
|
|
44
44
|
## Install
|
|
45
45
|
|
|
46
|
-
**1.
|
|
46
|
+
**1. From the DSH interface**
|
|
47
|
+
|
|
48
|
+
1. Open the **Plugins** page in the sidebar
|
|
49
|
+
2. Choose **+ Add plugin** at the top right
|
|
50
|
+
3. Enter `dsh-plugin-session-notifier`, or name a version, for example `dsh-plugin-session-notifier@0.1.2`
|
|
51
|
+
4. Pick the China mainland registry mirror if you are there, it is much faster
|
|
52
|
+
5. Choose **Install**
|
|
53
|
+
|
|
54
|
+
**2. Ask the agent**
|
|
47
55
|
|
|
48
56
|
```
|
|
49
|
-
plugin_manager({ action: "install_bundle", target: "dsh-plugin-session-notifier
|
|
57
|
+
plugin_manager({ action: "install_bundle", target: "dsh-plugin-session-notifier" })
|
|
50
58
|
```
|
|
51
59
|
|
|
52
|
-
|
|
53
|
-
|
|
60
|
+
Omitting the version installs the newest release that has also cleared pnpm's release-age window.
|
|
61
|
+
|
|
62
|
+
**3. Local development**
|
|
54
63
|
|
|
55
|
-
|
|
64
|
+
```
|
|
65
|
+
plugin_manager({ action: "install_bundle", target: "/absolute/path/dsh-plugin-session-notifier" })
|
|
66
|
+
```
|
|
56
67
|
|
|
57
68
|
After installation the left sidebar shows the bell entry, and the `main` panel key is `session-notifier`.
|
|
58
69
|
Compatible DSH versions are declared by `engines.dsh` in `package.json`, currently `>=0.2.1-rc.3`.
|
|
59
70
|
|
|
60
|
-
Upgrading
|
|
61
|
-
|
|
71
|
+
Upgrading a plugin in DeepSeek Harness does not happen in place: uninstall the old version, then install the new one. If a new version is
|
|
72
|
+
reported as still inside pnpm's release-age window (24 hours by default), wait it out or name the version.
|
|
62
73
|
|
|
63
74
|
## Limits
|
|
64
75
|
|
|
@@ -93,6 +104,7 @@ upgrade in place.
|
|
|
93
104
|
| `client.js` | Browser half: the reminder engine and the settings panel |
|
|
94
105
|
| `cordis.patch.yml` | Inserts the plugin row |
|
|
95
106
|
| `locale/*.json` | Title and description the plugin manager displays |
|
|
107
|
+
| `scripts/sync-version.mjs` | Copies the release version into the panel footer |
|
|
96
108
|
| `test/*.mjs` | Engine scenarios plus static style and copy checks |
|
|
97
109
|
| `.github/workflows/` | `ci` runs the checks, `publish` uploads to npm |
|
|
98
110
|
|
package/README.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
#
|
|
1
|
+
# 会话提醒 · Session Notifier
|
|
2
2
|
|
|
3
3
|
[English](README.en.md) | 中文
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/dsh-plugin-session-notifier)
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
|
|
8
|
-
DeepSeek Harness
|
|
9
|
-
|
|
8
|
+
DeepSeek Harness 会话通知插件。会话跑完、出错,或者停下来等你回答(提问 / 工具授权)时,
|
|
9
|
+
用桌面通知和提示音提醒你。切到别的窗口、全屏看视频或打游戏时也不会错过。
|
|
10
10
|
|
|
11
11
|
左侧栏有铃铛入口,点开是设置页,所有开关即时生效。界面跟随 DSH 的中英文设置切换。
|
|
12
12
|
|
|
@@ -19,7 +19,7 @@ DeepSeek Harness 插件。会话跑完、出错,或者停下来等你回答(
|
|
|
19
19
|
| 会话出错 | 低沉的两声提示音 + 错误通知 |
|
|
20
20
|
| 结束的是你正看着的会话 | 约 1 秒后提醒,几乎即时 |
|
|
21
21
|
| 结束的是其他会话 | 等 10 / 20 / 30 秒的合并窗口再提醒,避免连环响 |
|
|
22
|
-
|
|
|
22
|
+
| 结束的是其他会话,在合并窗口内又继续跑 | 合并成一次提醒 |
|
|
23
23
|
| 你没回来看 | 每 30 秒再提醒一次,最多 3 次,页面获得焦点后立即停止 |
|
|
24
24
|
| 子代理 | 不提醒,避免一个任务内部并行时连环响 |
|
|
25
25
|
|
|
@@ -40,21 +40,33 @@ DeepSeek Harness 插件。会话跑完、出错,或者停下来等你回答(
|
|
|
40
40
|
|
|
41
41
|
## 安装
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
**一、在 DSH 界面里安装**
|
|
44
|
+
|
|
45
|
+
1. 侧边栏打开「插件」页
|
|
46
|
+
2. 点右上角「+ 添加插件」
|
|
47
|
+
3. 输入框填 `dsh-plugin-session-notifier`,想装指定版本就在包名后加版本,例如 `dsh-plugin-session-notifier@0.1.2`
|
|
48
|
+
4. 安装源可选「中国大陆镜像源」,国内会快很多
|
|
49
|
+
5. 点「安装」
|
|
50
|
+
|
|
51
|
+
**二、让智能体安装**
|
|
44
52
|
|
|
45
53
|
```
|
|
46
|
-
plugin_manager({ action: "install_bundle", target: "dsh-plugin-session-notifier
|
|
54
|
+
plugin_manager({ action: "install_bundle", target: "dsh-plugin-session-notifier" })
|
|
47
55
|
```
|
|
48
56
|
|
|
49
|
-
|
|
50
|
-
|
|
57
|
+
不写版本号时,装的是最新的、并且已经过 pnpm 发布观察期的版本。
|
|
58
|
+
|
|
59
|
+
**三、本地开发**
|
|
51
60
|
|
|
52
|
-
|
|
61
|
+
```
|
|
62
|
+
plugin_manager({ action: "install_bundle", target: "/绝对路径/dsh-plugin-session-notifier" })
|
|
63
|
+
```
|
|
53
64
|
|
|
54
65
|
安装后左侧栏出现铃铛入口,`main` 面板 key 为 `session-notifier`。
|
|
55
66
|
兼容的 DSH 版本由 `package.json` 的 `engines.dsh` 声明,当前为 `>=0.2.1-rc.3`。
|
|
56
67
|
|
|
57
|
-
|
|
68
|
+
目前 DeepSeek Harness 的插件升级不能原地进行,需要先卸载旧版本,再安装新版本。如果提示某个新版本还处在 pnpm 的发布观察期内
|
|
69
|
+
(默认 24 小时),要么等它过去,要么直接指定版本号安装。
|
|
58
70
|
|
|
59
71
|
## 能力边界
|
|
60
72
|
|
|
@@ -84,6 +96,7 @@ plugin_manager({ action: "install_bundle", target: "dsh-plugin-session-notifier@
|
|
|
84
96
|
| `client.js` | 浏览器半侧,提醒引擎与设置面板 |
|
|
85
97
|
| `cordis.patch.yml` | 插入插件行 |
|
|
86
98
|
| `locale/*.json` | 插件管理器展示用的标题与描述 |
|
|
99
|
+
| `scripts/sync-version.mjs` | 发布时把版本号同步到面板底部 |
|
|
87
100
|
| `test/*.mjs` | 引擎场景、样式、文案的静态检查 |
|
|
88
101
|
| `.github/workflows/` | `ci` 跑检查,`publish` 发 npm |
|
|
89
102
|
|
package/client.js
CHANGED
|
@@ -28,13 +28,17 @@ window.__ModuleLoader__.load({
|
|
|
28
28
|
/** Monotonic per-page counter: two reminders in one millisecond still need distinct tags. */
|
|
29
29
|
let notificationSeq = 0;
|
|
30
30
|
const RECENT_LIMIT = 12;
|
|
31
|
+
/** Shown at the foot of the panel; scripts/sync-version.mjs keeps it in step with package.json. */
|
|
32
|
+
const PLUGIN_VERSION = '0.1.3';
|
|
31
33
|
/** Wait before reminding about the session the user is looking at. */
|
|
32
34
|
const VIEWING_MERGE_MS = 1000;
|
|
35
|
+
/** Two reminders landing together share one tone instead of stacking two. */
|
|
36
|
+
const TONE_GAP_MS = 250;
|
|
33
37
|
|
|
34
38
|
//#region copy
|
|
35
39
|
const zh = {
|
|
36
40
|
'panel.label': '会话提醒',
|
|
37
|
-
'panel.title': '
|
|
41
|
+
'panel.title': '会话提醒',
|
|
38
42
|
'panel.desc':
|
|
39
43
|
'会话跑完或停下来等你回答时,用桌面通知和提示音提醒你。切到别的窗口、全屏看视频或打游戏时也不会错过。',
|
|
40
44
|
'group.master': '总开关',
|
|
@@ -101,11 +105,12 @@ window.__ModuleLoader__.load({
|
|
|
101
105
|
'notify.test.title': '这是一条测试提醒',
|
|
102
106
|
'notify.test.body': '看到这条通知、听到提示音,就说明提醒已经生效。',
|
|
103
107
|
'session.unknown': '新会话',
|
|
108
|
+
'footer.version': '版本 {version}',
|
|
104
109
|
};
|
|
105
110
|
|
|
106
111
|
const en = {
|
|
107
|
-
'panel.label': 'Session
|
|
108
|
-
'panel.title': 'Session
|
|
112
|
+
'panel.label': 'Session Notifier',
|
|
113
|
+
'panel.title': 'Session Notifier',
|
|
109
114
|
'panel.desc':
|
|
110
115
|
'A desktop notification and a short chime when a session finishes or stops to wait for you — so a task that ends while you are on another window is not missed.',
|
|
111
116
|
'group.master': 'Master switch',
|
|
@@ -172,16 +177,17 @@ window.__ModuleLoader__.load({
|
|
|
172
177
|
'notify.test.title': 'This is a test reminder',
|
|
173
178
|
'notify.test.body': 'If you can see this notification and hear the chime, reminders work.',
|
|
174
179
|
'session.unknown': 'New session',
|
|
180
|
+
'footer.version': 'Version {version}',
|
|
175
181
|
};
|
|
176
182
|
//#endregion
|
|
177
183
|
|
|
178
184
|
//#region settings
|
|
179
185
|
/**
|
|
180
|
-
* Stored-document version
|
|
181
|
-
*
|
|
182
|
-
*
|
|
186
|
+
* Stored-document version, written with every save. Nothing reads it yet: the key
|
|
187
|
+
* was introduced with the current defaults, so no older document can exist. It is
|
|
188
|
+
* here so a future default change can migrate instead of guessing.
|
|
183
189
|
*/
|
|
184
|
-
const SETTINGS_VERSION =
|
|
190
|
+
const SETTINGS_VERSION = 1;
|
|
185
191
|
|
|
186
192
|
/** Merge-window choices, in milliseconds. */
|
|
187
193
|
const COMBINE_CHOICES = [10000, 20000, 30000];
|
|
@@ -223,12 +229,6 @@ window.__ModuleLoader__.load({
|
|
|
223
229
|
for (const key of Object.keys(DEFAULTS)) {
|
|
224
230
|
if (typeof parsed[key] === typeof DEFAULTS[key]) next[key] = parsed[key];
|
|
225
231
|
}
|
|
226
|
-
const version = typeof parsed.v === 'number' ? parsed.v : 1;
|
|
227
|
-
if (version < SETTINGS_VERSION) {
|
|
228
|
-
// The two defaults that changed; other stored choices stay as the user left them.
|
|
229
|
-
next.whenFocused = DEFAULTS.whenFocused;
|
|
230
|
-
next.combineMs = DEFAULTS.combineMs;
|
|
231
|
-
}
|
|
232
232
|
} catch (_storageUnavailable) {
|
|
233
233
|
// A blocked or corrupt store simply falls back to the defaults.
|
|
234
234
|
}
|
|
@@ -287,8 +287,33 @@ window.__ModuleLoader__.load({
|
|
|
287
287
|
/** Newest-first reminder log for the panel. */
|
|
288
288
|
recent: [],
|
|
289
289
|
recentListeners: new Set(),
|
|
290
|
+
/** Timers owned by one notification or tone, cleared with the plugin. */
|
|
291
|
+
timers: new Set(),
|
|
292
|
+
/** Wall-clock time of the last tone, so two reminders do not overlap. */
|
|
293
|
+
lastToneAt: 0,
|
|
290
294
|
};
|
|
291
295
|
|
|
296
|
+
/**
|
|
297
|
+
* Run a callback later, keeping its timer id in the owned set.
|
|
298
|
+
* @param callback - work to run once.
|
|
299
|
+
* @param delay - delay in milliseconds.
|
|
300
|
+
* @returns the timer id.
|
|
301
|
+
*/
|
|
302
|
+
function startTimer(callback, delay) {
|
|
303
|
+
const id = window.setTimeout(() => {
|
|
304
|
+
state.timers.delete(id);
|
|
305
|
+
callback();
|
|
306
|
+
}, delay);
|
|
307
|
+
state.timers.add(id);
|
|
308
|
+
return id;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/** Drop every timer this plugin started. */
|
|
312
|
+
function clearTimers() {
|
|
313
|
+
for (const id of state.timers) window.clearTimeout(id);
|
|
314
|
+
state.timers.clear();
|
|
315
|
+
}
|
|
316
|
+
|
|
292
317
|
/** Faces assigned when they become available; every consumer tolerates absence. */
|
|
293
318
|
const services = {
|
|
294
319
|
sessions: null,
|
|
@@ -397,8 +422,13 @@ window.__ModuleLoader__.load({
|
|
|
397
422
|
* Play the reminder tone for one kind of event.
|
|
398
423
|
* @param kind - `done`, `awaiting`, `error`, or `test`.
|
|
399
424
|
*/
|
|
400
|
-
function chime(kind) {
|
|
425
|
+
function chime(kind, force) {
|
|
401
426
|
if (!settings.sound) return;
|
|
427
|
+
const at = Date.now();
|
|
428
|
+
if (force !== true) {
|
|
429
|
+
if (at - state.lastToneAt < TONE_GAP_MS) return;
|
|
430
|
+
state.lastToneAt = at;
|
|
431
|
+
}
|
|
402
432
|
const context = audioContext();
|
|
403
433
|
if (context === null) return;
|
|
404
434
|
if (context.state === 'suspended' && typeof context.resume === 'function') {
|
|
@@ -439,7 +469,7 @@ window.__ModuleLoader__.load({
|
|
|
439
469
|
oscillator.start(start + offset);
|
|
440
470
|
oscillator.stop(start + offset + duration + 0.03);
|
|
441
471
|
}
|
|
442
|
-
|
|
472
|
+
startTimer(() => {
|
|
443
473
|
try {
|
|
444
474
|
master.disconnect();
|
|
445
475
|
} catch (_disconnected) {
|
|
@@ -525,8 +555,8 @@ window.__ModuleLoader__.load({
|
|
|
525
555
|
// Already gone.
|
|
526
556
|
}
|
|
527
557
|
};
|
|
528
|
-
|
|
529
|
-
|
|
558
|
+
startTimer(() => settle('unconfirmed'), 2500);
|
|
559
|
+
startTimer(() => {
|
|
530
560
|
try {
|
|
531
561
|
notification.close();
|
|
532
562
|
} catch (_closed) {
|
|
@@ -657,7 +687,7 @@ window.__ModuleLoader__.load({
|
|
|
657
687
|
*/
|
|
658
688
|
function runTestReminder() {
|
|
659
689
|
primeAudio();
|
|
660
|
-
chime('test');
|
|
690
|
+
chime('test', true);
|
|
661
691
|
publishProbe({ status: 'pending' });
|
|
662
692
|
const delivery = notifyDesktop(
|
|
663
693
|
'test',
|
|
@@ -688,6 +718,9 @@ window.__ModuleLoader__.load({
|
|
|
688
718
|
});
|
|
689
719
|
if (!settings.repeat || attempt >= settings.repeatMax) return;
|
|
690
720
|
if (!pageIsAway()) return;
|
|
721
|
+
// A chain already armed for this session would keep its own timer, and its
|
|
722
|
+
// callback would delete this newer record.
|
|
723
|
+
cancelRepeat(sessionId);
|
|
691
724
|
const timer = window.setTimeout(() => {
|
|
692
725
|
state.repeats.delete(sessionId);
|
|
693
726
|
if (!pageIsAway()) return;
|
|
@@ -719,13 +752,17 @@ window.__ModuleLoader__.load({
|
|
|
719
752
|
*/
|
|
720
753
|
function scheduleEnd(sessionId, kind) {
|
|
721
754
|
const previous = state.pendingEnd.get(sessionId);
|
|
755
|
+
// A failed turn reports its error and then its idle edge, so an armed error
|
|
756
|
+
// outranks the finish that follows it. Otherwise every failure would be
|
|
757
|
+
// announced as an ordinary completion.
|
|
758
|
+
const effective = previous !== undefined && previous.kind === 'error' ? 'error' : kind;
|
|
722
759
|
if (previous !== undefined) window.clearTimeout(previous.timer);
|
|
723
760
|
const timer = window.setTimeout(() => {
|
|
724
761
|
state.pendingEnd.delete(sessionId);
|
|
725
762
|
if (!eligible(sessionId)) return;
|
|
726
|
-
deliver(
|
|
763
|
+
deliver(effective, sessionId, 1);
|
|
727
764
|
}, mergeWindowFor(sessionId));
|
|
728
|
-
state.pendingEnd.set(sessionId, { timer, kind });
|
|
765
|
+
state.pendingEnd.set(sessionId, { timer, kind: effective });
|
|
729
766
|
}
|
|
730
767
|
|
|
731
768
|
/**
|
|
@@ -1198,6 +1235,8 @@ window.__ModuleLoader__.load({
|
|
|
1198
1235
|
),
|
|
1199
1236
|
),
|
|
1200
1237
|
),
|
|
1238
|
+
|
|
1239
|
+
h('p', { className: 'sen-version' }, t('footer.version', { version: PLUGIN_VERSION })),
|
|
1201
1240
|
),
|
|
1202
1241
|
),
|
|
1203
1242
|
);
|
|
@@ -1288,6 +1327,7 @@ window.__ModuleLoader__.load({
|
|
|
1288
1327
|
'.sen-btn-primary:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover)}',
|
|
1289
1328
|
'.sen-actions{display:flex;align-items:center;gap:8px;padding:16px 0;border-bottom:.5px solid var(--dsw-alias-border-l2)}',
|
|
1290
1329
|
'.sen-empty{margin:16px 0;font-size:13px;line-height:20px;color:var(--dsw-alias-label-tertiary)}',
|
|
1330
|
+
'.sen-version{margin:8px 0 0;font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary)}',
|
|
1291
1331
|
'.sen-recent{list-style:none;margin:0;padding:0}',
|
|
1292
1332
|
'.sen-recent-row{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 0;border-bottom:.5px solid var(--dsw-alias-border-l2);font-size:13px;line-height:20px}',
|
|
1293
1333
|
'.sen-recent-name{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-primary)}',
|
|
@@ -1328,6 +1368,7 @@ window.__ModuleLoader__.load({
|
|
|
1328
1368
|
ctx.effect(
|
|
1329
1369
|
() => () => {
|
|
1330
1370
|
cancelAll();
|
|
1371
|
+
clearTimers();
|
|
1331
1372
|
},
|
|
1332
1373
|
'session-notifier: timers',
|
|
1333
1374
|
);
|
package/locale/en.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"meta": {
|
|
3
|
-
"title": "Session
|
|
4
|
-
"description": "
|
|
3
|
+
"title": "Session Notifier",
|
|
4
|
+
"description": "A desktop notification and a short chime when a session finishes or stops to wait for you — so a task that ends while you are on another window is not missed."
|
|
5
5
|
}
|
|
6
6
|
}
|
package/locale/zh.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-session-notifier",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Desktop notification and a short chime when a DeepSeek Harness session finishes or stops to wait for your answer.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -49,7 +49,8 @@
|
|
|
49
49
|
},
|
|
50
50
|
"scripts": {
|
|
51
51
|
"verify": "node test/verify-engine.mjs && node test/check-styles.mjs && node test/check-copy.mjs",
|
|
52
|
-
"prepublishOnly": "npm run verify"
|
|
52
|
+
"prepublishOnly": "npm run verify",
|
|
53
|
+
"version": "node scripts/sync-version.mjs && git add client.js"
|
|
53
54
|
},
|
|
54
55
|
"dsh": {
|
|
55
56
|
"bundle": {
|