@goodandready/dsh-voice 0.8.37 → 0.8.38

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.
Files changed (2) hide show
  1. package/lib/index.js +64 -15
  2. package/package.json +1 -1
package/lib/index.js CHANGED
@@ -217,16 +217,6 @@ export function apply(ctx, baseConfig) {
217
217
  let getConfig = readEntryConfig
218
218
  const live = () => BaseConfig(structuredClone(getConfig() ?? {})) ?? readEntryConfig()
219
219
 
220
- ctx.inject(['settings'], (sctx) => {
221
- try {
222
- scope = sctx.settings.register(NS, Config, { base: baseConfig })
223
- if (scope && typeof scope.get === 'function') {
224
- getConfig = () => scope.get() ?? readEntryConfig()
225
- }
226
- } catch { /* modern DSH handles settings via SettingsForms */ }
227
- sctx.effect(() => () => { getConfig = readEntryConfig; scope = null })
228
- })
229
-
230
220
  async function resolveKey(ref) {
231
221
  try {
232
222
  const resolved = await ctx.credentials.resolve(credentialRef(ref))
@@ -269,12 +259,27 @@ export function apply(ctx, baseConfig) {
269
259
  stdoutMaxBytes: 4 * 1024 * 1024,
270
260
  })
271
261
  child = ctx.shell.start(spec)
272
- for (let i = 0; i < 20; i++) {
262
+ if (child && typeof child.on === 'function') {
263
+ child.on('error', (err) => {
264
+ whisperError = err?.message || String(err)
265
+ })
266
+ child.on('exit', (code) => {
267
+ if (code !== 0 && code !== null) {
268
+ whisperError = `whisper server process exited with code ${code}`
269
+ }
270
+ })
271
+ }
272
+ // Wait for it to become healthy (up to 30s for cold model loading on Metal/CPU).
273
+ for (let i = 0; i < 60; i++) {
273
274
  await new Promise((r) => setTimeout(r, 500))
274
275
  if (await whisperAlive()) {
275
276
  whisperError = null
276
277
  return true
277
278
  }
279
+ if (child && child.exitCode !== null && child.exitCode !== undefined) {
280
+ whisperError = `whisper server process exited prematurely with code ${child.exitCode}`
281
+ return false
282
+ }
278
283
  }
279
284
  whisperError = 'whisper server process started but failed health check on port 8001'
280
285
  return false
@@ -285,8 +290,6 @@ export function apply(ctx, baseConfig) {
285
290
  finally { startingWhisper = false }
286
291
  }
287
292
 
288
- startWhisper().catch(() => {})
289
-
290
293
  // SenseVoice-ONNX / Sherpa-ONNX: autostart and health check.
291
294
  let sensevoiceChild = null
292
295
  let sensevoiceError = null
@@ -325,12 +328,27 @@ export function apply(ctx, baseConfig) {
325
328
  stdoutMaxBytes: 4 * 1024 * 1024,
326
329
  })
327
330
  sensevoiceChild = ctx.shell.start(spec)
328
- for (let i = 0; i < 20; i++) {
331
+ if (sensevoiceChild && typeof sensevoiceChild.on === 'function') {
332
+ sensevoiceChild.on('error', (err) => {
333
+ sensevoiceError = err?.message || String(err)
334
+ })
335
+ sensevoiceChild.on('exit', (code) => {
336
+ if (code !== 0 && code !== null) {
337
+ sensevoiceError = `sensevoice process exited with code ${code}`
338
+ }
339
+ })
340
+ }
341
+ // Wait for it to become healthy (up to 30s).
342
+ for (let i = 0; i < 60; i++) {
329
343
  await new Promise((r) => setTimeout(r, 500))
330
344
  if (await sensevoiceAlive()) {
331
345
  sensevoiceError = null
332
346
  return true
333
347
  }
348
+ if (sensevoiceChild && sensevoiceChild.exitCode !== null && sensevoiceChild.exitCode !== undefined) {
349
+ sensevoiceError = `sensevoice process exited prematurely with code ${sensevoiceChild.exitCode}`
350
+ return false
351
+ }
334
352
  }
335
353
  sensevoiceError = 'sensevoice process started but failed health check'
336
354
  return false
@@ -341,7 +359,38 @@ export function apply(ctx, baseConfig) {
341
359
  finally { startingSensevoice = false }
342
360
  }
343
361
 
344
- startSensevoice().catch(() => {})
362
+ function triggerAutostart() {
363
+ startWhisper().catch((err) => {
364
+ ctx.logger?.debug?.('whisper autostart failed: %s', err?.message || err)
365
+ })
366
+ startSensevoice().catch((err) => {
367
+ ctx.logger?.debug?.('sensevoice autostart failed: %s', err?.message || err)
368
+ })
369
+ }
370
+
371
+ ctx.inject(['settings'], (sctx) => {
372
+ try {
373
+ scope = sctx.settings.register(NS, Config, { base: baseConfig })
374
+ if (scope && typeof scope.get === 'function') {
375
+ getConfig = () => scope.get() ?? readEntryConfig()
376
+ }
377
+ if (scope && typeof scope.watch === 'function') {
378
+ scope.watch(() => {
379
+ triggerAutostart()
380
+ })
381
+ }
382
+ } catch { /* modern DSH handles settings via SettingsForms */ }
383
+ sctx.effect(() => () => { getConfig = readEntryConfig; scope = null })
384
+ triggerAutostart()
385
+ })
386
+
387
+ if (typeof ctx.on === 'function') {
388
+ ctx.on('loader/volatile-update', () => triggerAutostart())
389
+ ctx.on('settings/document-updated', () => triggerAutostart())
390
+ ctx.on('config', () => triggerAutostart())
391
+ }
392
+
393
+ triggerAutostart()
345
394
 
346
395
  // Provider health and latency stats (Latency & Health Dashboard).
347
396
  const statsTracker = createStatsTracker()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-voice",
3
- "version": "0.8.37",
3
+ "version": "0.8.38",
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",