@goodandready/dsh-voice 0.9.9 → 0.9.10
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/lib/client.js +2 -0
- package/lib/index.js +117 -34
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -1982,6 +1982,8 @@ window.__ModuleLoader__.load({
|
|
|
1982
1982
|
micDeviceId: String((data && data.micDeviceId) || ''),
|
|
1983
1983
|
historyLimit: Number(data && data.historyLimit),
|
|
1984
1984
|
voiceCommands: !!(data && data.voiceCommands),
|
|
1985
|
+
vadSilenceMs: Number(data && data.modes && data.modes.dictation && data.modes.dictation.vadSilenceMs) || 700,
|
|
1986
|
+
autoSendMs: Number(data && data.modes && data.modes.message && data.modes.message.autoSendMs) || 4000,
|
|
1985
1987
|
sendDelayMs: Number(data && data.modes && data.modes.dictation && data.modes.dictation.sendDelayMs) || 0,
|
|
1986
1988
|
stream: !!(data && data.modes && data.modes.dictation && data.modes.dictation.stream),
|
|
1987
1989
|
streamChunkMs: Number(data && data.modes && data.modes.dictation && data.modes.dictation.streamChunkMs) || 1200,
|
package/lib/index.js
CHANGED
|
@@ -92,27 +92,38 @@ export function apply(ctx, baseConfig) {
|
|
|
92
92
|
async function startWhisper() {
|
|
93
93
|
if (startingWhisper) return false
|
|
94
94
|
startingWhisper = true
|
|
95
|
-
const cfg = live()
|
|
96
|
-
if (!cfg.autoStart) return false
|
|
97
|
-
// Without a model path there is nothing to start: the package cannot know
|
|
98
|
-
// where a user's model lives and must not launch a random binary.
|
|
99
|
-
if (!cfg.whisperModel) return false
|
|
100
|
-
if (await whisperAlive()) {
|
|
101
|
-
whisperError = null
|
|
102
|
-
return true
|
|
103
|
-
}
|
|
104
95
|
try {
|
|
96
|
+
const cfg = live()
|
|
97
|
+
if (!cfg.autoStart) return false
|
|
98
|
+
// Without a model path there is nothing to start: the package cannot know
|
|
99
|
+
// where a user's model lives and must not launch a random binary.
|
|
100
|
+
if (!cfg.whisperModel) return false
|
|
101
|
+
if (await whisperAlive()) {
|
|
102
|
+
whisperError = null
|
|
103
|
+
return true
|
|
104
|
+
}
|
|
105
105
|
if (child && typeof child.kill === 'function') {
|
|
106
106
|
try { child.kill() } catch { /* ignore */ }
|
|
107
107
|
child = null
|
|
108
108
|
}
|
|
109
|
-
const spec = ctx.shell
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
109
|
+
const spec = typeof ctx.shell?.resolve === 'function'
|
|
110
|
+
? ctx.shell.resolve({
|
|
111
|
+
command: `${JSON.stringify(cfg.whisperBin)} -m ${JSON.stringify(cfg.whisperModel)}`
|
|
112
|
+
+ ` --host 127.0.0.1 --port 8001 -t 8 -p 1 -l ${isAutoLang(cfg.dictation.language) ? 'auto' : cfg.dictation.language}`,
|
|
113
|
+
timeoutMs: 0,
|
|
114
|
+
stdoutMaxBytes: 4 * 1024 * 1024,
|
|
115
|
+
})
|
|
116
|
+
: {
|
|
117
|
+
command: `${JSON.stringify(cfg.whisperBin)} -m ${JSON.stringify(cfg.whisperModel)}`
|
|
118
|
+
+ ` --host 127.0.0.1 --port 8001 -t 8 -p 1 -l ${isAutoLang(cfg.dictation.language) ? 'auto' : cfg.dictation.language}`,
|
|
119
|
+
timeoutMs: 0,
|
|
120
|
+
stdoutMaxBytes: 4 * 1024 * 1024,
|
|
121
|
+
}
|
|
122
|
+
child = typeof ctx.shell?.execute === 'function'
|
|
123
|
+
? ctx.shell.execute(spec)
|
|
124
|
+
: typeof ctx.shell?.start === 'function'
|
|
125
|
+
? ctx.shell.start(spec)
|
|
126
|
+
: null
|
|
116
127
|
if (child && typeof child.on === 'function') {
|
|
117
128
|
child.on('error', (err) => {
|
|
118
129
|
whisperError = err?.message || String(err)
|
|
@@ -123,6 +134,15 @@ export function apply(ctx, baseConfig) {
|
|
|
123
134
|
}
|
|
124
135
|
})
|
|
125
136
|
}
|
|
137
|
+
if (child && child.done && typeof child.done.then === 'function') {
|
|
138
|
+
child.done.then((res) => {
|
|
139
|
+
if (res && res.code !== 0 && res.code !== null && res.code !== undefined) {
|
|
140
|
+
whisperError = `whisper server process exited with code ${res.code}`
|
|
141
|
+
}
|
|
142
|
+
}).catch((err) => {
|
|
143
|
+
whisperError = err?.message || String(err)
|
|
144
|
+
})
|
|
145
|
+
}
|
|
126
146
|
// Wait for it to become healthy (up to 30s for cold model loading on Metal/CPU).
|
|
127
147
|
for (let i = 0; i < 60; i++) {
|
|
128
148
|
await new Promise((r) => setTimeout(r, 500))
|
|
@@ -134,6 +154,10 @@ export function apply(ctx, baseConfig) {
|
|
|
134
154
|
whisperError = `whisper server process exited prematurely with code ${child.exitCode}`
|
|
135
155
|
return false
|
|
136
156
|
}
|
|
157
|
+
if (child && (child.status === 'exited' || child.status === 'failed')) {
|
|
158
|
+
whisperError = `whisper server process exited prematurely with status ${child.status}`
|
|
159
|
+
return false
|
|
160
|
+
}
|
|
137
161
|
}
|
|
138
162
|
whisperError = 'whisper server process started but failed health check on port 8001'
|
|
139
163
|
return false
|
|
@@ -164,14 +188,14 @@ export function apply(ctx, baseConfig) {
|
|
|
164
188
|
async function startSensevoice() {
|
|
165
189
|
if (startingSensevoice) return false
|
|
166
190
|
startingSensevoice = true
|
|
167
|
-
const cfg = live()
|
|
168
|
-
if (!cfg.sensevoiceAutostart) return false
|
|
169
|
-
if (!cfg.sensevoiceModel) return false
|
|
170
|
-
if (await sensevoiceAlive()) {
|
|
171
|
-
sensevoiceError = null
|
|
172
|
-
return true
|
|
173
|
-
}
|
|
174
191
|
try {
|
|
192
|
+
const cfg = live()
|
|
193
|
+
if (!cfg.sensevoiceAutostart) return false
|
|
194
|
+
if (!cfg.sensevoiceModel) return false
|
|
195
|
+
if (await sensevoiceAlive()) {
|
|
196
|
+
sensevoiceError = null
|
|
197
|
+
return true
|
|
198
|
+
}
|
|
175
199
|
if (sensevoiceChild && typeof sensevoiceChild.kill === 'function') {
|
|
176
200
|
try { sensevoiceChild.kill() } catch { /* ignore */ }
|
|
177
201
|
sensevoiceChild = null
|
|
@@ -180,15 +204,28 @@ export function apply(ctx, baseConfig) {
|
|
|
180
204
|
try { port = new URL(cfg.sensevoiceUrl).port || '6006' } catch { /* invalid URL */ }
|
|
181
205
|
const tokensPath = findTokensFile(path.dirname(cfg.sensevoiceModel))
|
|
182
206
|
const tokensArg = tokensPath ? ` --tokens=${JSON.stringify(tokensPath)}` : ''
|
|
183
|
-
const spec = ctx.shell
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
207
|
+
const spec = typeof ctx.shell?.resolve === 'function'
|
|
208
|
+
? ctx.shell.resolve({
|
|
209
|
+
command: `${JSON.stringify(cfg.sensevoiceBin)}`
|
|
210
|
+
+ ` --sense-voice-model=${JSON.stringify(cfg.sensevoiceModel)}`
|
|
211
|
+
+ tokensArg
|
|
212
|
+
+ ` --port=${port} --num-threads=4`,
|
|
213
|
+
timeoutMs: 0,
|
|
214
|
+
stdoutMaxBytes: 4 * 1024 * 1024,
|
|
215
|
+
})
|
|
216
|
+
: {
|
|
217
|
+
command: `${JSON.stringify(cfg.sensevoiceBin)}`
|
|
218
|
+
+ ` --sense-voice-model=${JSON.stringify(cfg.sensevoiceModel)}`
|
|
219
|
+
+ tokensArg
|
|
220
|
+
+ ` --port=${port} --num-threads=4`,
|
|
221
|
+
timeoutMs: 0,
|
|
222
|
+
stdoutMaxBytes: 4 * 1024 * 1024,
|
|
223
|
+
}
|
|
224
|
+
sensevoiceChild = typeof ctx.shell?.execute === 'function'
|
|
225
|
+
? ctx.shell.execute(spec)
|
|
226
|
+
: typeof ctx.shell?.start === 'function'
|
|
227
|
+
? ctx.shell.start(spec)
|
|
228
|
+
: null
|
|
192
229
|
if (sensevoiceChild && typeof sensevoiceChild.on === 'function') {
|
|
193
230
|
sensevoiceChild.on('error', (err) => {
|
|
194
231
|
sensevoiceError = err?.message || String(err)
|
|
@@ -199,6 +236,15 @@ export function apply(ctx, baseConfig) {
|
|
|
199
236
|
}
|
|
200
237
|
})
|
|
201
238
|
}
|
|
239
|
+
if (sensevoiceChild && sensevoiceChild.done && typeof sensevoiceChild.done.then === 'function') {
|
|
240
|
+
sensevoiceChild.done.then((res) => {
|
|
241
|
+
if (res && res.code !== 0 && res.code !== null && res.code !== undefined) {
|
|
242
|
+
sensevoiceError = `sensevoice process exited with code ${res.code}`
|
|
243
|
+
}
|
|
244
|
+
}).catch((err) => {
|
|
245
|
+
sensevoiceError = err?.message || String(err)
|
|
246
|
+
})
|
|
247
|
+
}
|
|
202
248
|
// Wait for it to become healthy (up to 30s).
|
|
203
249
|
for (let i = 0; i < 60; i++) {
|
|
204
250
|
await new Promise((r) => setTimeout(r, 500))
|
|
@@ -210,6 +256,10 @@ export function apply(ctx, baseConfig) {
|
|
|
210
256
|
sensevoiceError = `sensevoice process exited prematurely with code ${sensevoiceChild.exitCode}`
|
|
211
257
|
return false
|
|
212
258
|
}
|
|
259
|
+
if (sensevoiceChild && (sensevoiceChild.status === 'exited' || sensevoiceChild.status === 'failed')) {
|
|
260
|
+
sensevoiceError = `sensevoice process exited prematurely with status ${sensevoiceChild.status}`
|
|
261
|
+
return false
|
|
262
|
+
}
|
|
213
263
|
}
|
|
214
264
|
sensevoiceError = 'sensevoice process started but failed health check'
|
|
215
265
|
return false
|
|
@@ -486,8 +536,41 @@ export function apply(ctx, baseConfig) {
|
|
|
486
536
|
liveConfig: live,
|
|
487
537
|
isAlive: sensevoiceAlive,
|
|
488
538
|
updateConfig: async (patch) => {
|
|
489
|
-
|
|
490
|
-
|
|
539
|
+
let saved = false
|
|
540
|
+
const current = live()
|
|
541
|
+
const merged = { ...current, ...patch }
|
|
542
|
+
if (settingsService) {
|
|
543
|
+
let rev
|
|
544
|
+
try {
|
|
545
|
+
rev = settingsService.describe?.()?.find?.((r) => r.ns === NS)?.revision
|
|
546
|
+
} catch { /* ignore revision lookup errors */ }
|
|
547
|
+
|
|
548
|
+
if (typeof settingsService.replace === 'function') {
|
|
549
|
+
await settingsService.replace(NS, merged, rev)
|
|
550
|
+
saved = true
|
|
551
|
+
} else if (typeof settingsService.update === 'function') {
|
|
552
|
+
await settingsService.update(NS, merged, rev)
|
|
553
|
+
saved = true
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
if (scope) {
|
|
558
|
+
if (typeof scope.replace === 'function') {
|
|
559
|
+
await scope.replace(merged)
|
|
560
|
+
saved = true
|
|
561
|
+
} else if (typeof scope.patch === 'function') {
|
|
562
|
+
await scope.patch(merged)
|
|
563
|
+
saved = true
|
|
564
|
+
} else if (typeof scope.set === 'function') {
|
|
565
|
+
for (const [k, v] of Object.entries(merged)) {
|
|
566
|
+
await scope.set(k, v).catch(() => {})
|
|
567
|
+
}
|
|
568
|
+
saved = true
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
if (!saved) {
|
|
573
|
+
throw new Error('failed to persist settings: neither settingsService nor scope available')
|
|
491
574
|
}
|
|
492
575
|
},
|
|
493
576
|
startSensevoice,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-voice",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.10",
|
|
4
4
|
"description": "Voice input for DeepSeek Harness: dictation chunked by pauses and voice messages, each with its own provider fallback chain (Deepgram, Groq, HuggingFace, local whisper.cpp, plus any OpenAI-compatible API of your own).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|