@goodandready/dsh-voice 0.9.19 → 0.9.21

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,18 +121,21 @@ 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
  }
115
- if (child.status === 'killed') {
116
- whisperError = detail ? `whisper server process was killed: ${detail}` : 'whisper server process was killed'
117
- return false
118
- }
119
- if (child.status === 'completed') {
120
- let code = child.exitCode
136
+ let code = child.exitCode
137
+ const isTerminal = (child.status && child.status !== 'running') || (code !== null && code !== undefined)
138
+ if (isTerminal) {
121
139
  if (typeof child.result === 'function') {
122
140
  try {
123
141
  const r = await child.result()
@@ -125,22 +143,31 @@ export function createLocalDaemons({ live, ctx }) {
125
143
  if (r.exitCode !== undefined && r.exitCode !== null) code = r.exitCode
126
144
  if (r.stderr && !detail) detail = String(r.stderr).trim()
127
145
  }
128
- } catch (_) {}
146
+ } catch (err) {
147
+ const msg = err && err.message ? err.message : String(err)
148
+ if (msg && !detail) detail = msg.trim()
149
+ }
129
150
  } else if (child.result && typeof child.result === 'object') {
130
151
  code = child.result.exitCode ?? code
131
152
  if (child.result.stderr && !detail) detail = String(child.result.stderr).trim()
132
153
  }
133
- code = code ?? 0
134
- whisperError = `whisper server process exited prematurely with code ${code}${detail ? `: ${detail}` : ''}`
135
- return false
136
- }
137
- if (child.status === 'exited' || child.status === 'failed') {
138
- whisperError = `whisper server process exited prematurely with status ${child.status}${detail ? `: ${detail}` : ''}`
139
- return false
140
- }
141
- if (child.exitCode !== null && child.exitCode !== undefined) {
142
- whisperError = `whisper server process exited prematurely with code ${child.exitCode}${detail ? `: ${detail}` : ''}`
143
- return false
154
+ if (child.status === 'killed') {
155
+ whisperError = detail ? `whisper server process was killed: ${detail}` : 'whisper server process was killed'
156
+ return false
157
+ }
158
+ if (child.status === 'completed') {
159
+ code = code ?? 0
160
+ whisperError = `whisper server process exited prematurely with code ${code}${detail ? `: ${detail}` : ''}`
161
+ return false
162
+ }
163
+ if (child.status === 'exited' || child.status === 'failed') {
164
+ whisperError = `whisper server process exited prematurely with status ${child.status}${detail ? `: ${detail}` : ''}`
165
+ return false
166
+ }
167
+ if (child.exitCode !== null && child.exitCode !== undefined) {
168
+ whisperError = `whisper server process exited prematurely with code ${child.exitCode}${detail ? `: ${detail}` : ''}`
169
+ return false
170
+ }
144
171
  }
145
172
  }
146
173
  }
@@ -257,22 +284,54 @@ export function createLocalDaemons({ live, ctx }) {
257
284
  return true
258
285
  }
259
286
  if (sensevoiceChild) {
260
- if (sensevoiceChild.status === 'killed') {
261
- sensevoiceError = 'sensevoice process was killed'
262
- return false
263
- }
264
- if (sensevoiceChild.status === 'completed') {
265
- const code = sensevoiceChild.result?.exitCode ?? sensevoiceChild.exitCode ?? 0
266
- sensevoiceError = `sensevoice process exited prematurely with code ${code}`
267
- return false
268
- }
269
- if (sensevoiceChild.status === 'exited' || sensevoiceChild.status === 'failed') {
270
- sensevoiceError = `sensevoice process exited prematurely with status ${sensevoiceChild.status}`
271
- return false
287
+ let detail = ''
288
+ if (typeof sensevoiceChild.readOutput === 'function') {
289
+ try {
290
+ const out = sensevoiceChild.readOutput()
291
+ if (out && out.stderr) detail = String(out.stderr).trim()
292
+ else if (out && out.stdout) detail = String(out.stdout).trim()
293
+ } catch (_) {}
294
+ } else if (typeof sensevoiceChild.read === 'function') {
295
+ try {
296
+ const out = sensevoiceChild.read()
297
+ if (out && out.stderr) detail = String(out.stderr).trim()
298
+ } catch (_) {}
272
299
  }
273
- if (sensevoiceChild.exitCode !== null && sensevoiceChild.exitCode !== undefined) {
274
- sensevoiceError = `sensevoice process exited prematurely with code ${sensevoiceChild.exitCode}`
275
- return false
300
+ let code = sensevoiceChild.exitCode
301
+ const isTerminal = (sensevoiceChild.status && sensevoiceChild.status !== 'running') || (code !== null && code !== undefined)
302
+ if (isTerminal) {
303
+ if (typeof sensevoiceChild.result === 'function') {
304
+ try {
305
+ const r = await sensevoiceChild.result()
306
+ if (r) {
307
+ if (r.exitCode !== undefined && r.exitCode !== null) code = r.exitCode
308
+ if (r.stderr && !detail) detail = String(r.stderr).trim()
309
+ }
310
+ } catch (err) {
311
+ const msg = err && err.message ? err.message : String(err)
312
+ if (msg && !detail) detail = msg.trim()
313
+ }
314
+ } else if (sensevoiceChild.result && typeof sensevoiceChild.result === 'object') {
315
+ code = sensevoiceChild.result.exitCode ?? code
316
+ if (sensevoiceChild.result.stderr && !detail) detail = String(sensevoiceChild.result.stderr).trim()
317
+ }
318
+ if (sensevoiceChild.status === 'killed') {
319
+ sensevoiceError = detail ? `sensevoice process was killed: ${detail}` : 'sensevoice process was killed'
320
+ return false
321
+ }
322
+ if (sensevoiceChild.status === 'completed') {
323
+ code = code ?? 0
324
+ sensevoiceError = `sensevoice process exited prematurely with code ${code}${detail ? `: ${detail}` : ''}`
325
+ return false
326
+ }
327
+ if (sensevoiceChild.status === 'exited' || sensevoiceChild.status === 'failed') {
328
+ sensevoiceError = `sensevoice process exited prematurely with status ${sensevoiceChild.status}${detail ? `: ${detail}` : ''}`
329
+ return false
330
+ }
331
+ if (sensevoiceChild.exitCode !== null && sensevoiceChild.exitCode !== undefined) {
332
+ sensevoiceError = `sensevoice process exited prematurely with code ${sensevoiceChild.exitCode}${detail ? `: ${detail}` : ''}`
333
+ return false
334
+ }
276
335
  }
277
336
  }
278
337
  }
package/lib/updater.js CHANGED
@@ -203,17 +203,21 @@ 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
+ const p3 = readLockPid(lockPath);
214
+ if (p3 !== null && isProcessAlive(p3)) return { locked: true, pid: p3 };
215
+ if (p1 === pid && p2 === pid && p3 === pid) {
211
216
  unlinkSync(lockPath);
212
217
  return { locked: false, cleanedStale: true };
213
218
  }
214
- if (currentPid !== null && isProcessAlive(currentPid)) {
215
- return { locked: true, pid: currentPid };
216
- }
219
+ const pFinal = readLockPid(lockPath);
220
+ if (pFinal !== null && isProcessAlive(pFinal)) return { locked: true, pid: pFinal };
217
221
  return { locked: false };
218
222
  } catch {
219
223
  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.21",
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",