dsh-done-sound 0.1.2 → 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/assets/turn-done.wav +0 -0
- package/lib/client.js +106 -18
- package/lib/index.js +25 -2
- package/package.json +7 -2
|
Binary file
|
package/lib/client.js
CHANGED
|
@@ -60,6 +60,8 @@ window.__ModuleLoader__.load({
|
|
|
60
60
|
interruptHint: '手动停止生成时也播放(默认关闭)',
|
|
61
61
|
error: '出错时也响',
|
|
62
62
|
errorHint: '对话以错误结束时也播放(默认开启)',
|
|
63
|
+
pending: '等待确认时提醒',
|
|
64
|
+
pendingHint: 'Agent 需要你确认操作(如审批)而停住等待时播放提醒(默认开启)',
|
|
63
65
|
saved: '已保存:',
|
|
64
66
|
readFailed: '读取文件失败',
|
|
65
67
|
apiFailed: '无法连接插件服务(Host 未加载?),请重启 dsh web',
|
|
@@ -69,6 +71,7 @@ window.__ModuleLoader__.load({
|
|
|
69
71
|
evNormal: '正常完成',
|
|
70
72
|
evInterrupt: '中断',
|
|
71
73
|
evError: '出错',
|
|
74
|
+
evPending: '等待确认',
|
|
72
75
|
evPlayed: '已播放',
|
|
73
76
|
evNotPlayed: '未播放',
|
|
74
77
|
},
|
|
@@ -86,6 +89,8 @@ window.__ModuleLoader__.load({
|
|
|
86
89
|
interruptHint: 'Also play when generation is stopped (default off)',
|
|
87
90
|
error: 'Play on error',
|
|
88
91
|
errorHint: 'Also play when a turn ends in error (default on)',
|
|
92
|
+
pending: 'Alert on approval wait',
|
|
93
|
+
pendingHint: 'Play a reminder when the agent is waiting for your confirmation (default on)',
|
|
89
94
|
saved: 'Saved:',
|
|
90
95
|
readFailed: 'Failed to read file',
|
|
91
96
|
apiFailed: 'Cannot reach the plugin host service (host not loaded?), restart dsh web',
|
|
@@ -95,6 +100,7 @@ window.__ModuleLoader__.load({
|
|
|
95
100
|
evNormal: 'completed',
|
|
96
101
|
evInterrupt: 'interrupted',
|
|
97
102
|
evError: 'error',
|
|
103
|
+
evPending: 'approval wait',
|
|
98
104
|
evPlayed: 'played',
|
|
99
105
|
evNotPlayed: 'not played',
|
|
100
106
|
},
|
|
@@ -130,7 +136,9 @@ window.__ModuleLoader__.load({
|
|
|
130
136
|
volume: 0.8,
|
|
131
137
|
playOnInterrupt: false,
|
|
132
138
|
playOnError: true,
|
|
139
|
+
playOnPending: true,
|
|
133
140
|
url: null,
|
|
141
|
+
defaultUrl: null,
|
|
134
142
|
fileName: '',
|
|
135
143
|
mime: '',
|
|
136
144
|
size: 0,
|
|
@@ -159,9 +167,10 @@ window.__ModuleLoader__.load({
|
|
|
159
167
|
return cfg;
|
|
160
168
|
}
|
|
161
169
|
function playChime(cfg) {
|
|
162
|
-
|
|
170
|
+
const url = cfg.url || cfg.defaultUrl;
|
|
171
|
+
if (!cfg.enabled || !url) return;
|
|
163
172
|
try {
|
|
164
|
-
const audio = new Audio(
|
|
173
|
+
const audio = new Audio(url);
|
|
165
174
|
audio.volume = cfg.volume;
|
|
166
175
|
audio.play().catch(() => {});
|
|
167
176
|
} catch {
|
|
@@ -203,7 +212,9 @@ window.__ModuleLoader__.load({
|
|
|
203
212
|
volume: typeof payload.volume === 'number' ? payload.volume : 0.8,
|
|
204
213
|
playOnInterrupt: payload.playOnInterrupt === true,
|
|
205
214
|
playOnError: payload.playOnError !== false,
|
|
215
|
+
playOnPending: payload.playOnPending !== false,
|
|
206
216
|
url: typeof payload.url === 'string' && payload.url ? payload.url : null,
|
|
217
|
+
defaultUrl: typeof payload.defaultUrl === 'string' && payload.defaultUrl ? payload.defaultUrl : null,
|
|
207
218
|
fileName: payload.audio && typeof payload.audio.fileName === 'string' ? payload.audio.fileName : '',
|
|
208
219
|
mime: payload.audio && typeof payload.audio.mime === 'string' ? payload.audio.mime : '',
|
|
209
220
|
size: payload.audio && typeof payload.audio.size === 'number' ? payload.audio.size : 0,
|
|
@@ -395,6 +406,24 @@ window.__ModuleLoader__.load({
|
|
|
395
406
|
),
|
|
396
407
|
react.createElement('span', { className: 'dtc-muted' }, L('errorHint')),
|
|
397
408
|
),
|
|
409
|
+
react.createElement(
|
|
410
|
+
'div',
|
|
411
|
+
{ className: 'dtc-row' },
|
|
412
|
+
react.createElement(
|
|
413
|
+
'label',
|
|
414
|
+
{ className: 'dtc-toggle' },
|
|
415
|
+
react.createElement('input', {
|
|
416
|
+
type: 'checkbox',
|
|
417
|
+
checked: cfg.playOnPending,
|
|
418
|
+
disabled: busy,
|
|
419
|
+
onChange: (e) => {
|
|
420
|
+
run('/dsh-done-sound/api/config', { playOnPending: e.target.checked });
|
|
421
|
+
},
|
|
422
|
+
}),
|
|
423
|
+
react.createElement('span', null, L('pending')),
|
|
424
|
+
),
|
|
425
|
+
react.createElement('span', { className: 'dtc-muted' }, L('pendingHint')),
|
|
426
|
+
),
|
|
398
427
|
|
|
399
428
|
error !== null && react.createElement('div', { className: 'dtc-err' }, error),
|
|
400
429
|
!cfg.loaded && react.createElement('div', { className: 'dtc-muted' }, L('loading')),
|
|
@@ -406,7 +435,13 @@ window.__ModuleLoader__.load({
|
|
|
406
435
|
':' +
|
|
407
436
|
formatEventTime(cfg.lastEvent.at) +
|
|
408
437
|
' ' +
|
|
409
|
-
(cfg.lastEvent.kind === 'interrupt'
|
|
438
|
+
(cfg.lastEvent.kind === 'interrupt'
|
|
439
|
+
? L('evInterrupt')
|
|
440
|
+
: cfg.lastEvent.kind === 'error'
|
|
441
|
+
? L('evError')
|
|
442
|
+
: cfg.lastEvent.kind === 'pending'
|
|
443
|
+
? L('evPending')
|
|
444
|
+
: L('evNormal')) +
|
|
410
445
|
' → ' +
|
|
411
446
|
(cfg.lastEvent.play ? L('evPlayed') : L('evNotPlayed')),
|
|
412
447
|
),
|
|
@@ -426,6 +461,7 @@ window.__ModuleLoader__.load({
|
|
|
426
461
|
const pendingReasonRef = react.useRef(null);
|
|
427
462
|
const lastEndTurnRef = react.useRef(0);
|
|
428
463
|
const lastSessionRef = react.useRef(null);
|
|
464
|
+
const prevPendingRef = react.useRef(false);
|
|
429
465
|
|
|
430
466
|
const snap =
|
|
431
467
|
typeof useSession === 'function'
|
|
@@ -447,6 +483,8 @@ window.__ModuleLoader__.load({
|
|
|
447
483
|
streaming: s.partial !== null || s.running === true,
|
|
448
484
|
endReason,
|
|
449
485
|
endTurn,
|
|
486
|
+
// agent is waiting for human intervention (approval etc.)
|
|
487
|
+
pendingCount: s && Array.isArray(s.pending) ? s.pending.length : 0,
|
|
450
488
|
};
|
|
451
489
|
})
|
|
452
490
|
: null;
|
|
@@ -460,8 +498,22 @@ window.__ModuleLoader__.load({
|
|
|
460
498
|
lastSessionRef.current = snap.sessionId;
|
|
461
499
|
pendingReasonRef.current = null;
|
|
462
500
|
lastEndTurnRef.current = snap.endTurn;
|
|
501
|
+
prevPendingRef.current = snap.pendingCount > 0;
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// Agent entered a waiting-for-human state (rising edge only).
|
|
506
|
+
if (snap.pendingCount > 0 && !prevPendingRef.current) {
|
|
507
|
+
prevPendingRef.current = true;
|
|
508
|
+
let played = false;
|
|
509
|
+
if (cfg.enabled && cfg.playOnPending && (cfg.url || cfg.defaultUrl)) {
|
|
510
|
+
playChime(cfg);
|
|
511
|
+
played = true;
|
|
512
|
+
}
|
|
513
|
+
setConfig({ lastEvent: { at: Date.now(), kind: 'pending', reason: 'pending', play: played } });
|
|
463
514
|
return;
|
|
464
515
|
}
|
|
516
|
+
prevPendingRef.current = snap.pendingCount > 0;
|
|
465
517
|
|
|
466
518
|
// A newly-closed turn is the authoritative "a turn ended" signal.
|
|
467
519
|
if (snap.endTurn > lastEndTurnRef.current) {
|
|
@@ -480,24 +532,60 @@ window.__ModuleLoader__.load({
|
|
|
480
532
|
else if (reason === 'error' || reason === 'max-tokens') kind = 'error';
|
|
481
533
|
else kind = 'normal'; // 'completed' (and unknown future reasons default to playing)
|
|
482
534
|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
535
|
+
// Fire the chime for a given config view; returns whether it played.
|
|
536
|
+
const effectiveUrlOf = (view) => view.url || view.defaultUrl;
|
|
537
|
+
const firePlay = (view) => {
|
|
538
|
+
if (kind === 'interrupt') {
|
|
539
|
+
if (view.playOnInterrupt && effectiveUrlOf(view)) {
|
|
540
|
+
playChime(view);
|
|
541
|
+
return true;
|
|
542
|
+
}
|
|
543
|
+
return false;
|
|
488
544
|
}
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
545
|
+
if (kind === 'error') {
|
|
546
|
+
if (view.playOnError && effectiveUrlOf(view)) {
|
|
547
|
+
playChime(view);
|
|
548
|
+
return true;
|
|
549
|
+
}
|
|
550
|
+
return false;
|
|
493
551
|
}
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
552
|
+
if (effectiveUrlOf(view)) {
|
|
553
|
+
playChime(view);
|
|
554
|
+
return true;
|
|
555
|
+
}
|
|
556
|
+
return false;
|
|
557
|
+
};
|
|
498
558
|
|
|
499
|
-
|
|
500
|
-
|
|
559
|
+
let played = firePlay(cfg);
|
|
560
|
+
const record = (value) =>
|
|
561
|
+
setConfig({ lastEvent: { at: Date.now(), kind, reason, play: value } });
|
|
562
|
+
if (!played && !cfg.url && !cfg.defaultUrl) {
|
|
563
|
+
// Self-heal: the in-memory config may be stale (e.g. the audio was
|
|
564
|
+
// uploaded after this page mounted, or in another tab). Re-fetch
|
|
565
|
+
// status once and retry before giving up.
|
|
566
|
+
api('/dsh-done-sound/api/status')
|
|
567
|
+
.then((payload) => {
|
|
568
|
+
if (payload && typeof payload === 'object') {
|
|
569
|
+
syncConfig(payload);
|
|
570
|
+
const fresh = {
|
|
571
|
+
...cfg,
|
|
572
|
+
enabled: payload.enabled === true,
|
|
573
|
+
volume: typeof payload.volume === 'number' ? payload.volume : 0.8,
|
|
574
|
+
playOnInterrupt: payload.playOnInterrupt === true,
|
|
575
|
+
playOnError: payload.playOnError !== false,
|
|
576
|
+
playOnPending: payload.playOnPending !== false,
|
|
577
|
+
url: typeof payload.url === 'string' && payload.url ? payload.url : null,
|
|
578
|
+
defaultUrl: typeof payload.defaultUrl === 'string' && payload.defaultUrl ? payload.defaultUrl : null,
|
|
579
|
+
};
|
|
580
|
+
record(firePlay(fresh));
|
|
581
|
+
} else {
|
|
582
|
+
record(false);
|
|
583
|
+
}
|
|
584
|
+
})
|
|
585
|
+
.catch(() => record(false));
|
|
586
|
+
} else {
|
|
587
|
+
record(played);
|
|
588
|
+
}
|
|
501
589
|
}, [snap, cfg]);
|
|
502
590
|
|
|
503
591
|
return null;
|
package/lib/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// dsh-done-sound/src/index.js
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
3
|
import { mkdir, readFile, writeFile, unlink } from "node:fs/promises";
|
|
4
|
-
import { join } from "node:path";
|
|
4
|
+
import { join, dirname } from "node:path";
|
|
5
5
|
import { randomUUID } from "node:crypto";
|
|
6
6
|
|
|
7
7
|
// C:/Users/Administrator/.dsh/profiles/web/node_modules/@deepseek-ai/cosmokit/lib/index.js
|
|
@@ -796,6 +796,9 @@ defineMethod("transform", [
|
|
|
796
796
|
var name = "dsh-done-sound";
|
|
797
797
|
var inject = ["settings", "commands", "webServer"];
|
|
798
798
|
var MAX_BYTES = 2 * 1024 * 1024;
|
|
799
|
+
var DEFAULT_AUDIO_PATH = join(dirname(fileURLToPath(import.meta.url)), "..", "assets", "turn-done.wav");
|
|
800
|
+
var DEFAULT_AUDIO_URL = "/dsh-done-sound/audio/default";
|
|
801
|
+
var DEFAULT_AUDIO_MIME = "audio/wav";
|
|
799
802
|
var AUDIO_MIMES = {
|
|
800
803
|
"audio/mpeg": "mp3",
|
|
801
804
|
"audio/mp3": "mp3",
|
|
@@ -816,6 +819,7 @@ var TurnChimeSchema = Schema.object({
|
|
|
816
819
|
volume: Schema.number().min(0).max(1).step(0.01).default(0.8),
|
|
817
820
|
playOnInterrupt: Schema.boolean().default(false),
|
|
818
821
|
playOnError: Schema.boolean().default(true),
|
|
822
|
+
playOnPending: Schema.boolean().default(true),
|
|
819
823
|
audio: Schema.object({
|
|
820
824
|
fileId: Schema.string().default(""),
|
|
821
825
|
fileName: Schema.string().default(""),
|
|
@@ -840,13 +844,15 @@ function apply(ctx) {
|
|
|
840
844
|
volume: value.volume,
|
|
841
845
|
playOnInterrupt: value.playOnInterrupt,
|
|
842
846
|
playOnError: value.playOnError,
|
|
847
|
+
playOnPending: value.playOnPending,
|
|
843
848
|
audio: {
|
|
844
849
|
fileId,
|
|
845
850
|
fileName: value.audio?.fileName ?? "",
|
|
846
851
|
mime: value.audio?.mime ?? "",
|
|
847
852
|
size: value.audio?.size ?? 0
|
|
848
853
|
},
|
|
849
|
-
url: fileId ? `${ROUTE_PREFIX}/audio/${fileId}` : null
|
|
854
|
+
url: fileId ? `${ROUTE_PREFIX}/audio/${fileId}` : null,
|
|
855
|
+
defaultUrl: DEFAULT_AUDIO_URL
|
|
850
856
|
};
|
|
851
857
|
};
|
|
852
858
|
const clearAudio = async () => {
|
|
@@ -972,6 +978,22 @@ function apply(ctx) {
|
|
|
972
978
|
pathname = "/";
|
|
973
979
|
}
|
|
974
980
|
const method = (req.method ?? "GET").toUpperCase();
|
|
981
|
+
if (pathname === DEFAULT_AUDIO_URL) {
|
|
982
|
+
try {
|
|
983
|
+
const buffer = await readFile(DEFAULT_AUDIO_PATH);
|
|
984
|
+
res.writeHead(200, {
|
|
985
|
+
"Content-Type": DEFAULT_AUDIO_MIME,
|
|
986
|
+
"Content-Length": buffer.length,
|
|
987
|
+
"Cache-Control": "no-cache",
|
|
988
|
+
"X-Content-Type-Options": "nosniff"
|
|
989
|
+
});
|
|
990
|
+
res.end(buffer);
|
|
991
|
+
} catch {
|
|
992
|
+
res.writeHead(404);
|
|
993
|
+
res.end("not found");
|
|
994
|
+
}
|
|
995
|
+
return;
|
|
996
|
+
}
|
|
975
997
|
const audioMatch = /^\/dsh-done-sound\/audio\/([A-Za-z0-9-]+)$/.exec(pathname);
|
|
976
998
|
if (audioMatch) {
|
|
977
999
|
const fileId = audioMatch[1];
|
|
@@ -1008,6 +1030,7 @@ function apply(ctx) {
|
|
|
1008
1030
|
if (typeof body.volume === "number" && body.volume >= 0 && body.volume <= 1) patch.volume = body.volume;
|
|
1009
1031
|
if (typeof body.playOnInterrupt === "boolean") patch.playOnInterrupt = body.playOnInterrupt;
|
|
1010
1032
|
if (typeof body.playOnError === "boolean") patch.playOnError = body.playOnError;
|
|
1033
|
+
if (typeof body.playOnPending === "boolean") patch.playOnPending = body.playOnPending;
|
|
1011
1034
|
if (Object.keys(patch).length > 0) await scope.update(patch);
|
|
1012
1035
|
sendJson(res, 200, statusPayload());
|
|
1013
1036
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-done-sound",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Play a user-chosen sound whenever a conversation finishes in the DeepSeek Harness web GUI",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
},
|
|
31
31
|
"files": [
|
|
32
32
|
"lib",
|
|
33
|
+
"assets",
|
|
33
34
|
"cordis.patch.yml",
|
|
34
35
|
"README.md",
|
|
35
36
|
"LICENSE"
|
|
@@ -81,8 +82,12 @@
|
|
|
81
82
|
"devDependencies": {
|
|
82
83
|
"esbuild": "^0.25.0"
|
|
83
84
|
},
|
|
85
|
+
"engines": {
|
|
86
|
+
"node": ">=18"
|
|
87
|
+
},
|
|
84
88
|
"scripts": {
|
|
85
89
|
"build": "esbuild src/index.js --bundle --format=esm --platform=node --target=node22 --outfile=lib/index.js",
|
|
86
|
-
"check": "node --check lib/index.js && node --check lib/client.js"
|
|
90
|
+
"check": "node --check lib/index.js && node --check lib/client.js",
|
|
91
|
+
"test": "node test/host.test.mjs && node test/classify.test.mjs"
|
|
87
92
|
}
|
|
88
93
|
}
|