@goodandready/dsh-voice 0.9.19 → 0.9.20

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
@@ -912,7 +912,12 @@ window.__ModuleLoader__.load({
912
912
  if (typeof actions.insertText === 'function') {
913
913
  try {
914
914
  const res = actions.insertText(text, span)
915
- if (res !== false) return true
915
+ if (res !== false) {
916
+ if (target && typeof actions.captureInsertion === 'function') {
917
+ try { target.span = actions.captureInsertion() } catch (_) {}
918
+ }
919
+ return true
920
+ }
916
921
  return false
917
922
  } catch (_) {
918
923
  return false
@@ -936,10 +941,10 @@ window.__ModuleLoader__.load({
936
941
  }
937
942
 
938
943
  function appendDraft(text, target) {
939
- if (voice.disposed || !text) return
944
+ if (voice.disposed || !text) return false
940
945
  let clean = voice.settings.voiceCommands ? applyVoiceCommands(text) : tidyPhrase(text)
941
- if (!clean) return
942
- insertDraftText(clean, target)
946
+ if (!clean) return false
947
+ return insertDraftText(clean, target)
943
948
  }
944
949
 
945
950
  function announceVoice(phase) {
@@ -1322,8 +1327,12 @@ voice._chainsPromise = null
1322
1327
  }
1323
1328
  const delay = Number(voice.settings.sendDelayMs) || 0
1324
1329
  if (text && delay > 0 && !voice.holding) {
1325
- appendDraft(text, target)
1326
- voice.set({ phase: 'pending', pending: { text, target, undoOnly: true, leftMs: delay } })
1330
+ const ok = appendDraft(text, target)
1331
+ if (ok !== false) {
1332
+ voice.set({ phase: 'pending', pending: { text, target, undoOnly: true, leftMs: delay } })
1333
+ } else {
1334
+ voice.set({ phase: 'idle' })
1335
+ }
1327
1336
  return
1328
1337
  }
1329
1338
  if (text) appendDraft(text, target)
@@ -1486,8 +1495,12 @@ voice._chainsPromise = null
1486
1495
  voice.browserFinals = null
1487
1496
  if (!said) { voice.set({ phase: 'idle' }); return }
1488
1497
  if (mode === 'message') {
1489
- appendDraft(said, target)
1490
- voice.set({ phase: 'pending', pending: { text: said, target, totalMs: voice.settings.autoSendMs, leftMs: voice.settings.autoSendMs } })
1498
+ const ok = appendDraft(said, target)
1499
+ if (ok !== false) {
1500
+ voice.set({ phase: 'pending', pending: { text: said, target, totalMs: voice.settings.autoSendMs, leftMs: voice.settings.autoSendMs } })
1501
+ } else {
1502
+ voice.set({ phase: 'idle' })
1503
+ }
1491
1504
  } else {
1492
1505
  voice.set({ phase: 'idle' })
1493
1506
  }
@@ -1551,10 +1564,14 @@ voice._chainsPromise = null
1551
1564
  return
1552
1565
  }
1553
1566
  if (action === 'cancel') { cancelCurrent(); return }
1554
- appendDraft(text, target)
1567
+ const ok = appendDraft(text, target)
1555
1568
  if (voice.lastNote) voice.lastNote.text = text
1556
1569
  if (mode === 'message') {
1557
- voice.set({ phase: 'pending', pending: { text, target, totalMs: voice.settings.autoSendMs, leftMs: voice.settings.autoSendMs, audioUrl: noteUrl } })
1570
+ if (ok !== false) {
1571
+ voice.set({ phase: 'pending', pending: { text, target, totalMs: voice.settings.autoSendMs, leftMs: voice.settings.autoSendMs, audioUrl: noteUrl } })
1572
+ } else {
1573
+ voice.set({ phase: 'idle' })
1574
+ }
1558
1575
  } else {
1559
1576
  voice.set({ phase: 'idle' })
1560
1577
  }
@@ -68,17 +68,32 @@ export function createLocalDaemons({ live, ctx }) {
68
68
  child.done.then(async (res) => {
69
69
  let code = res && res.code
70
70
  let detail = ''
71
+ if (typeof child.readOutput === 'function') {
72
+ try {
73
+ const out = child.readOutput()
74
+ if (out && out.stderr) detail = String(out.stderr).trim()
75
+ else if (out && out.stdout) detail = String(out.stdout).trim()
76
+ } catch (_) {}
77
+ } else if (typeof child.read === 'function') {
78
+ try {
79
+ const out = child.read()
80
+ if (out && out.stderr) detail = String(out.stderr).trim()
81
+ } catch (_) {}
82
+ }
71
83
  if (typeof child.result === 'function') {
72
84
  try {
73
85
  const r = await child.result()
74
86
  if (r) {
75
87
  if (r.exitCode !== undefined && r.exitCode !== null) code = r.exitCode
76
- if (r.stderr) detail = String(r.stderr).trim()
88
+ if (r.stderr && !detail) detail = String(r.stderr).trim()
77
89
  }
78
- } catch (_) {}
90
+ } catch (err) {
91
+ const msg = err && err.message ? err.message : String(err)
92
+ if (msg && !detail) detail = msg.trim()
93
+ }
79
94
  } else if (child.result && typeof child.result === 'object') {
80
95
  code = child.result.exitCode ?? code
81
- if (child.result.stderr) detail = String(child.result.stderr).trim()
96
+ if (child.result.stderr && !detail) detail = String(child.result.stderr).trim()
82
97
  }
83
98
  if (child.status === 'killed') {
84
99
  whisperError = detail ? `whisper server process was killed: ${detail}` : 'whisper server process was killed'
@@ -106,30 +121,39 @@ export function createLocalDaemons({ live, ctx }) {
106
121
  }
107
122
  if (child) {
108
123
  let detail = ''
109
- if (typeof child.read === 'function') {
124
+ if (typeof child.readOutput === 'function') {
125
+ try {
126
+ const out = child.readOutput()
127
+ if (out && out.stderr) detail = String(out.stderr).trim()
128
+ else if (out && out.stdout) detail = String(out.stdout).trim()
129
+ } catch (_) {}
130
+ } else if (typeof child.read === 'function') {
110
131
  try {
111
132
  const out = child.read()
112
133
  if (out && out.stderr) detail = String(out.stderr).trim()
113
134
  } catch (_) {}
114
135
  }
136
+ let code = child.exitCode
137
+ if (typeof child.result === 'function') {
138
+ try {
139
+ const r = await child.result()
140
+ if (r) {
141
+ if (r.exitCode !== undefined && r.exitCode !== null) code = r.exitCode
142
+ if (r.stderr && !detail) detail = String(r.stderr).trim()
143
+ }
144
+ } catch (err) {
145
+ const msg = err && err.message ? err.message : String(err)
146
+ if (msg && !detail) detail = msg.trim()
147
+ }
148
+ } else if (child.result && typeof child.result === 'object') {
149
+ code = child.result.exitCode ?? code
150
+ if (child.result.stderr && !detail) detail = String(child.result.stderr).trim()
151
+ }
115
152
  if (child.status === 'killed') {
116
153
  whisperError = detail ? `whisper server process was killed: ${detail}` : 'whisper server process was killed'
117
154
  return false
118
155
  }
119
156
  if (child.status === 'completed') {
120
- let code = child.exitCode
121
- if (typeof child.result === 'function') {
122
- try {
123
- const r = await child.result()
124
- if (r) {
125
- if (r.exitCode !== undefined && r.exitCode !== null) code = r.exitCode
126
- if (r.stderr && !detail) detail = String(r.stderr).trim()
127
- }
128
- } catch (_) {}
129
- } else if (child.result && typeof child.result === 'object') {
130
- code = child.result.exitCode ?? code
131
- if (child.result.stderr && !detail) detail = String(child.result.stderr).trim()
132
- }
133
157
  code = code ?? 0
134
158
  whisperError = `whisper server process exited prematurely with code ${code}${detail ? `: ${detail}` : ''}`
135
159
  return false
@@ -257,21 +281,50 @@ export function createLocalDaemons({ live, ctx }) {
257
281
  return true
258
282
  }
259
283
  if (sensevoiceChild) {
284
+ let detail = ''
285
+ if (typeof sensevoiceChild.readOutput === 'function') {
286
+ try {
287
+ const out = sensevoiceChild.readOutput()
288
+ if (out && out.stderr) detail = String(out.stderr).trim()
289
+ else if (out && out.stdout) detail = String(out.stdout).trim()
290
+ } catch (_) {}
291
+ } else if (typeof sensevoiceChild.read === 'function') {
292
+ try {
293
+ const out = sensevoiceChild.read()
294
+ if (out && out.stderr) detail = String(out.stderr).trim()
295
+ } catch (_) {}
296
+ }
297
+ let code = sensevoiceChild.exitCode
298
+ if (typeof sensevoiceChild.result === 'function') {
299
+ try {
300
+ const r = await sensevoiceChild.result()
301
+ if (r) {
302
+ if (r.exitCode !== undefined && r.exitCode !== null) code = r.exitCode
303
+ if (r.stderr && !detail) detail = String(r.stderr).trim()
304
+ }
305
+ } catch (err) {
306
+ const msg = err && err.message ? err.message : String(err)
307
+ if (msg && !detail) detail = msg.trim()
308
+ }
309
+ } else if (sensevoiceChild.result && typeof sensevoiceChild.result === 'object') {
310
+ code = sensevoiceChild.result.exitCode ?? code
311
+ if (sensevoiceChild.result.stderr && !detail) detail = String(sensevoiceChild.result.stderr).trim()
312
+ }
260
313
  if (sensevoiceChild.status === 'killed') {
261
- sensevoiceError = 'sensevoice process was killed'
314
+ sensevoiceError = detail ? `sensevoice process was killed: ${detail}` : 'sensevoice process was killed'
262
315
  return false
263
316
  }
264
317
  if (sensevoiceChild.status === 'completed') {
265
- const code = sensevoiceChild.result?.exitCode ?? sensevoiceChild.exitCode ?? 0
266
- sensevoiceError = `sensevoice process exited prematurely with code ${code}`
318
+ code = code ?? 0
319
+ sensevoiceError = `sensevoice process exited prematurely with code ${code}${detail ? `: ${detail}` : ''}`
267
320
  return false
268
321
  }
269
322
  if (sensevoiceChild.status === 'exited' || sensevoiceChild.status === 'failed') {
270
- sensevoiceError = `sensevoice process exited prematurely with status ${sensevoiceChild.status}`
323
+ sensevoiceError = `sensevoice process exited prematurely with status ${sensevoiceChild.status}${detail ? `: ${detail}` : ''}`
271
324
  return false
272
325
  }
273
326
  if (sensevoiceChild.exitCode !== null && sensevoiceChild.exitCode !== undefined) {
274
- sensevoiceError = `sensevoice process exited prematurely with code ${sensevoiceChild.exitCode}`
327
+ sensevoiceError = `sensevoice process exited prematurely with code ${sensevoiceChild.exitCode}${detail ? `: ${detail}` : ''}`
275
328
  return false
276
329
  }
277
330
  }
package/lib/updater.js CHANGED
@@ -203,17 +203,19 @@ export function checkProfileLock(profileDir) {
203
203
  return { locked: true, pid };
204
204
  }
205
205
  // Stale lock: PID is not running.
206
- // Verify before unlinking: ensure the file content still holds the dead pid we read,
207
- // without renaming the canonical file away so live successors are never hidden (#205).
206
+ // Verify before unlinking: re-check PID to ensure a concurrent live successor
207
+ // was not written during the check (#205).
208
208
  try {
209
- const currentPid = readLockPid(lockPath);
210
- if (currentPid === pid) {
209
+ const p1 = readLockPid(lockPath);
210
+ if (p1 !== null && isProcessAlive(p1)) return { locked: true, pid: p1 };
211
+ const p2 = readLockPid(lockPath);
212
+ if (p2 !== null && isProcessAlive(p2)) return { locked: true, pid: p2 };
213
+ if (p1 === pid && p2 === pid) {
211
214
  unlinkSync(lockPath);
212
215
  return { locked: false, cleanedStale: true };
213
216
  }
214
- if (currentPid !== null && isProcessAlive(currentPid)) {
215
- return { locked: true, pid: currentPid };
216
- }
217
+ if (p2 !== null && isProcessAlive(p2)) return { locked: true, pid: p2 };
218
+ if (p1 !== null && isProcessAlive(p1)) return { locked: true, pid: p1 };
217
219
  return { locked: false };
218
220
  } catch {
219
221
  if (!existsSync(lockPath)) return { locked: false };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-voice",
3
- "version": "0.9.19",
3
+ "version": "0.9.20",
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",