@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 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.resolve({
110
- command: `${JSON.stringify(cfg.whisperBin)} -m ${JSON.stringify(cfg.whisperModel)}`
111
- + ` --host 127.0.0.1 --port 8001 -t 8 -p 1 -l ${isAutoLang(cfg.dictation.language) ? 'auto' : cfg.dictation.language}`,
112
- timeoutMs: 0,
113
- stdoutMaxBytes: 4 * 1024 * 1024,
114
- })
115
- child = ctx.shell.start(spec)
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.resolve({
184
- command: `${JSON.stringify(cfg.sensevoiceBin)}`
185
- + ` --sense-voice-model=${JSON.stringify(cfg.sensevoiceModel)}`
186
- + tokensArg
187
- + ` --port=${port} --num-threads=4`,
188
- timeoutMs: 0,
189
- stdoutMaxBytes: 4 * 1024 * 1024,
190
- })
191
- sensevoiceChild = ctx.shell.start(spec)
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
- if (scope && typeof scope.patch === 'function') {
490
- scope.patch(patch)
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.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",