dsh-voice-mode 0.7.9 → 0.7.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/README.en.md CHANGED
@@ -14,7 +14,7 @@ interrupts playback and the running turn. No API key.
14
14
 
15
15
  ![dsh-voice-mode full-duplex voice conversation](https://raw.githubusercontent.com/qishuilalala/dsh-voice-mode/HEAD/assets/hero-banner.png)
16
16
 
17
- > **Version note (v0.7.9, 2026-09-18)**: **Wake-word pipeline overhaul** (Issue #10 + real-machine retest) — a harness driving the real engine pinpointed and fixed five flow defects: ① TTS echo polluting the standby segment while the agent reads (could not wake); ② only above-threshold frames uploaded, so trailing characters never flushed (wake word truncated); ③ discarding the whole segment on a wake hit (saying "wake word + command" in one breath sent only the tail); ④ the command hanging when the hit arrived after you stopped speaking; ⑤ pausing after the wake word closed the command window (command lost). **The wake word may now be said together with your command — it is stripped and never sent**, standby live-shows what it heard, and "wake word … pause … command" works. Otherwise as v0.7.7: Edge cloud TTS by default, local TTS (VITS / Kokoro) optional; silence split defaults to 1500 ms.
17
+ > **Version note (v0.7.10, 2026-09-18)**: **Silent-audio causes in the output pipeline fixed** — ① a sentence whose cloud-TTS synthesis fails after 3 retries is no longer dropped silently (the status bar now says one sentence failed and was skipped; previously this showed up as "the reply is occasionally not read" with no way to tell why); ② audio playback no longer goes silent after the browser suspends the AudioContext (background tab / long idle) — the context is resumed on every enqueue and on any click/keypress; ③ dropped incomplete sentences (SSE frame loss) now leave a diagnostic trace. Otherwise as v0.7.9: **Wake-word pipeline overhaul** (Issue #10 + real-machine retest) — a harness driving the real engine pinpointed and fixed five flow defects: ① TTS echo polluting the standby segment while the agent reads (could not wake); ② only above-threshold frames uploaded, so trailing characters never flushed (wake word truncated); ③ discarding the whole segment on a wake hit (saying "wake word + command" in one breath sent only the tail); ④ the command hanging when the hit arrived after you stopped speaking; ⑤ pausing after the wake word closed the command window (command lost). **The wake word may now be said together with your command — it is stripped and never sent**, standby live-shows what it heard, and "wake word … pause … command" works. Otherwise as v0.7.7: Edge cloud TTS by default, local TTS (VITS / Kokoro) optional; silence split defaults to 1500 ms.
18
18
 
19
19
  ---
20
20
 
package/README.md CHANGED
@@ -13,7 +13,7 @@ DeepSeek Harness 语音双工对话模式:会话内一键进入 → 边说边
13
13
 
14
14
  ![语音模式:实时字幕与状态条](https://raw.githubusercontent.com/qishuilalala/dsh-voice-mode/HEAD/assets/screenshot-voice.png)
15
15
 
16
- > **版本说明(v0.7.9,2026-09-18)**:**唤醒词链路全面修复**(Issue #10 + 真机复测)——台架驱动真引擎逐项定位并修掉五处流程缺陷:① 朗读期 TTS 回声污染待机段(喊不醒);② 只上传超门限帧导致尾字不 flush(唤醒词只剩半截);③ 唤醒命中丢整段(连说「唤醒词+命令」只发出去尾部几个字);④ 命中晚于停口时命令悬挂不定稿;⑤ 只喊唤醒词后停顿会关掉命令窗口(命令丢失)。**现在唤醒词可与命令连说、词头自动剥离不进消息**,待机态实时显示「它听到了什么」,支持「唤醒词…停顿…命令」。其余同 v0.7.7:朗读默认 Edge 云端,本地 TTS(VITS / Kokoro)可选;静音断句默认 1500 毫秒。
16
+ > **版本说明(v0.7.10,2026-09-18)**:**输出链路静默丢音根治**——① 云端 TTS 单句重试耗尽后不再静默跳句(现在状态条提示「有一句朗读失败,已跳过」,此前表现为「AI 回复偶尔不朗读」且无从判断);② 浏览器挂起 AudioContext 后的「无声播放」(UI 显示朗读中、字幕照走)现在每次入队 + 任意点击/按键自动恢复;③ SSE 丢帧导致的坏句丢弃留诊断痕迹。其余同 v0.7.9:**唤醒词链路全面修复**(Issue #10 + 真机复测)——台架驱动真引擎逐项定位并修掉五处流程缺陷:① 朗读期 TTS 回声污染待机段(喊不醒);② 只上传超门限帧导致尾字不 flush(唤醒词只剩半截);③ 唤醒命中丢整段(连说「唤醒词+命令」只发出去尾部几个字);④ 命中晚于停口时命令悬挂不定稿;⑤ 只喊唤醒词后停顿会关掉命令窗口(命令丢失)。**现在唤醒词可与命令连说、词头自动剥离不进消息**,待机态实时显示「它听到了什么」,支持「唤醒词…停顿…命令」。其余同 v0.7.7:朗读默认 Edge 云端,本地 TTS(VITS / Kokoro)可选;静音断句默认 1500 毫秒。
17
17
 
18
18
  ---
19
19
 
@@ -221,6 +221,7 @@ flowchart LR
221
221
  | 点麦克风无反应,状态条红字 | 浏览器拒绝麦克风:地址栏(iOS 为 设置 → Safari → 麦克风)开启后重试 |
222
222
  | 状态条「正在加载模型… x%」卡住 | 检查网络;模型较大可先 `npm run prefetch`;国内网络 `modelHost` 配 `https://hf-mirror.com` |
223
223
  | 朗读无声音/无字幕 | 本地引擎首次合成需加载模型;若持续失败查看状态条提示(自动退避重试);确认页面前台且未静音 |
224
+ | AI 回复**偶尔不朗读**(某句没声/整条没声) | 已定位三类原因并修(v0.7.10):① 云端 TTS 偶发超时——单句自动重试 3 次,仍失败会**跳过该句并在状态条提示**「有一句朗读失败,已跳过」(此前是静默丢句);② 浏览器挂起 AudioContext(切到后台标签页/长时间静音后回来)——播放被调度但无声、UI 却显示朗读中;现在每次入队与**任意点击/按键**都会自动恢复;③ SSE 断线丢帧导致整句不完整——按设计丢弃该句(避免播坏音频),可开 `localStorage['dsh-voice-mode.telemetry']='1'` 看 `tts-drop-sentence` 诊断 |
224
225
  | 语音模式进不去 | 检查插件 `enabled`;多标签页时确认当前会话为活动会话 |
225
226
  | 识别到但不是我要说的 | 环境噪声或唤醒词误判:降低音量、提高 `interruptLevel`(高门槛)或启用 `wakeWord` |
226
227
  | 唤醒词唤不醒 | 先看待机态状态条的实时转写(它听到了什么):同音字 1 字内已自动容错;停顿约 1.5 秒(= 静音断句时长)再说,清掉待机段残留语音、从段首重新匹配;换一个转写稳定的词(建议 3-4 字) |
package/lib/client.js CHANGED
@@ -1373,6 +1373,7 @@ function estimateBulkDelay(mic, ref, opts = {}) {
1373
1373
  var zh = {
1374
1374
  stateVoiceMode: "\u8BED\u97F3\u6A21\u5F0F",
1375
1375
  ttsNoticeFail: "\u6717\u8BFB\u8FDE\u63A5\u5931\u8D25\uFF1A\u6B63\u5728\u91CD\u8BD5\u2026",
1376
+ ttsSkipNotice: "\u6709\u4E00\u53E5\u6717\u8BFB\u5931\u8D25\uFF0C\u5DF2\u8DF3\u8FC7\uFF08\u4E91\u7AEF\u6717\u8BFB\u7F51\u7EDC\u6296\u52A8\uFF0C\u53EF\u91CD\u53D1\u8FD9\u6761\u6D88\u606F\uFF09",
1376
1377
  enterFail: "\u8FDB\u5165\u8BED\u97F3\u6A21\u5F0F\u5931\u8D25",
1377
1378
  disabled: "\u8BED\u97F3\u6A21\u5F0F\u5DF2\u7981\u7528\uFF08\u63D2\u4EF6 enabled=false\uFF09",
1378
1379
  sendFailKept: "\u53D1\u9001\u5931\u8D25\uFF0C\u5DF2\u4FDD\u7559\u5728\u8349\u7A3F",
@@ -1552,6 +1553,7 @@ var zh = {
1552
1553
  var en = {
1553
1554
  stateVoiceMode: "Voice Mode",
1554
1555
  ttsNoticeFail: "Read-aloud connection lost: retrying\u2026",
1556
+ ttsSkipNotice: "One sentence failed to read and was skipped (cloud TTS network hiccup \u2014 resend the message to retry)",
1555
1557
  enterFail: "Failed to enter voice mode",
1556
1558
  disabled: "Voice mode disabled (plugin enabled=false)",
1557
1559
  sendFailKept: "Send failed; text kept in draft",
@@ -2823,7 +2825,7 @@ var TELEMETRY_VIEW = [
2823
2825
  { stage: "first-tts-chunk", key: "telFirstChunk" },
2824
2826
  { stage: "first-audio-played", key: "telFirstPlayed" }
2825
2827
  ];
2826
- var BUILD_TAG = "3a98e88";
2828
+ var BUILD_TAG = "466b0f3";
2827
2829
  var TELEMETRY_FLAG = "dsh-voice-mode.telemetry";
2828
2830
  var telemetryEnabled = typeof localStorage !== "undefined" && localStorage.getItem(TELEMETRY_FLAG) === "1";
2829
2831
  console.log("[dsh-voice] build=" + BUILD_TAG);
@@ -2907,6 +2909,11 @@ function setLastVoiceSession(id) {
2907
2909
  }
2908
2910
  function apply(ctx) {
2909
2911
  const bus = createVoiceBus(void 0, ctx);
2912
+ if (typeof document !== "undefined") {
2913
+ const resumeAudio = () => bus.warmAudio();
2914
+ document.addEventListener("pointerdown", resumeAudio, { passive: true });
2915
+ document.addEventListener("keydown", resumeAudio, { passive: true });
2916
+ }
2910
2917
  ctx.slots.inject(
2911
2918
  "conversation.input.right",
2912
2919
  () => ctx.slots.register(
@@ -3080,6 +3087,7 @@ function createAudioEngine(setUi, onPlayed, onPlaybackRef, onAllPlayed) {
3080
3087
  };
3081
3088
  return {
3082
3089
  push(frame) {
3090
+ warm();
3083
3091
  if (fallback || !ctx) {
3084
3092
  pending.push(frame);
3085
3093
  if (fallbackAudio.paused) playFallback();
@@ -3393,6 +3401,16 @@ function createVoiceBus(basePath = BASE_PATH2, ctx) {
3393
3401
  } catch {
3394
3402
  }
3395
3403
  });
3404
+ source.addEventListener("tts-skip", (e) => {
3405
+ try {
3406
+ const p = JSON.parse(e.data);
3407
+ if (p.sessionId !== activeSessionId) return;
3408
+ ui.ttsNotice = t("ttsSkipNotice");
3409
+ notify();
3410
+ debugLog("tts-skip", { text: (p.text ?? "").slice(0, 40) });
3411
+ } catch {
3412
+ }
3413
+ });
3396
3414
  source.addEventListener("tts-error", (e) => {
3397
3415
  try {
3398
3416
  const p = JSON.parse(e.data);
@@ -3448,6 +3466,7 @@ function createVoiceBus(basePath = BASE_PATH2, ctx) {
3448
3466
  }
3449
3467
  if (frame.final) {
3450
3468
  if (frame.chunkId !== curChunkCount) {
3469
+ debugLog("tts-drop-sentence", { got: frame.chunkId, have: curChunkCount, sentenceId: frame.sentenceId });
3451
3470
  curSentenceId = null;
3452
3471
  curChunks = [];
3453
3472
  curBytes = 0;
package/lib/index.js CHANGED
@@ -1131,9 +1131,12 @@ var TtsQueue = class {
1131
1131
  engine;
1132
1132
  /** TTS 全体不可达通知(每会话去重,成功后复位)。 */
1133
1133
  onError;
1134
+ /** 单句合成重试耗尽被跳过通知(每句一次;此前是静默丢句——真机「回复偶尔不朗读」根因之一)。 */
1135
+ onSkip;
1134
1136
  constructor(options) {
1135
1137
  this.engine = options.engine;
1136
1138
  this.onError = options.onError;
1139
+ this.onSkip = options.onSkip;
1137
1140
  }
1138
1141
  /** 当前引擎音频 MIME(/preview 的 Content-Type 也用它)。 */
1139
1142
  get mime() {
@@ -1233,7 +1236,10 @@ var TtsQueue = class {
1233
1236
  }
1234
1237
  }
1235
1238
  if (item.epoch !== q.epoch) continue;
1236
- if (buf === null) continue;
1239
+ if (buf === null) {
1240
+ this.onSkip?.(sessionId, item.text);
1241
+ continue;
1242
+ }
1237
1243
  q.errorNotified = false;
1238
1244
  q.backoff = 0;
1239
1245
  const sentenceId = q.seq++;
@@ -2104,7 +2110,9 @@ function apply(ctx, config) {
2104
2110
  let activeKokoroModel = vset.kokoroModel;
2105
2111
  const queue = new TtsQueue({
2106
2112
  engine: makeEngine(engineKind),
2107
- onError: (sessionId) => broadcast("tts-error", { sessionId })
2113
+ onError: (sessionId) => broadcast("tts-error", { sessionId }),
2114
+ // 单句重试耗尽被跳过:显式下行(客户端提示 + 诊断),不再静默丢句。
2115
+ onSkip: (sessionId, text) => broadcast("tts-skip", { sessionId, text: text.slice(0, 80) })
2108
2116
  });
2109
2117
  queue.updateVoice(vset.voice, vset.rate);
2110
2118
  const unsubscribe = queue.subscribe((frame) => broadcast("audio", frame));
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-voice-mode",
3
3
  "description": "Full-duplex voice plugin for DeepSeek Harness: local zipformer2 streaming ASR (no API key) → editable draft; Edge TTS or local VITS / Kokoro read-aloud with live captions; true barge-in; hardened HTTP surface + model SHA256 pinning; compatible with all dsh releases since 0.1.1-rc.2 (verified 0.1.1 → 0.1.5-rc.2). · DSH 语音双工插件:流式识别入草稿、按句朗读+实时字幕、开口即打断;免 API Key、安全加固、全版本兼容(0.1.1-rc.2 起,含 0.1.5-rc.2 端到端验证)。",
4
- "version": "0.7.9",
4
+ "version": "0.7.10",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "repository": {
@@ -42,7 +42,7 @@
42
42
  "scripts": {
43
43
  "build": "node build.mjs",
44
44
  "prepack": "node build.mjs",
45
- "test": "node test/segmenter.test.mjs && node test/aec.test.mjs && node test/download.test.mjs && node test/endpoint.test.mjs && node test/endpoint-short.test.mjs && node test/resample.test.mjs && node test/sense-worker.test.mjs && node test/detect-route.test.mjs && node test/verify-client.mjs && node test/asr-host-rebuild.test.mjs && node test/barge-in-manual.test.mjs && node test/sense-lang.test.mjs && node test/caption-a11y.test.mjs && node test/emotion.test.mjs && node test/emotion-tts-local.test.mjs && node test/emotion-integration.test.mjs && node test/backchannel.test.mjs && node test/strings-coverage.test.mjs && node test/yield-ms-wiring.test.mjs && node test/matchBackchannel.test.mjs && node test/settings-load.test.mjs && node test/preview-error.test.mjs && node test/hold-clear.test.mjs && node test/barge-in-detect.test.mjs && node test/wakeword.test.mjs && node test/wake-standby.test.mjs && node test/wake-flow.test.mjs",
45
+ "test": "node test/segmenter.test.mjs && node test/aec.test.mjs && node test/download.test.mjs && node test/endpoint.test.mjs && node test/endpoint-short.test.mjs && node test/resample.test.mjs && node test/sense-worker.test.mjs && node test/detect-route.test.mjs && node test/verify-client.mjs && node test/asr-host-rebuild.test.mjs && node test/barge-in-manual.test.mjs && node test/sense-lang.test.mjs && node test/caption-a11y.test.mjs && node test/emotion.test.mjs && node test/emotion-tts-local.test.mjs && node test/emotion-integration.test.mjs && node test/backchannel.test.mjs && node test/strings-coverage.test.mjs && node test/yield-ms-wiring.test.mjs && node test/matchBackchannel.test.mjs && node test/settings-load.test.mjs && node test/preview-error.test.mjs && node test/hold-clear.test.mjs && node test/barge-in-detect.test.mjs && node test/wakeword.test.mjs && node test/wake-standby.test.mjs && node test/wake-flow.test.mjs && node test/tts-playback.test.mjs",
46
46
  "verify:client": "node test/verify-client.mjs",
47
47
  "prefetch": "node scripts/prefetch.mjs",
48
48
  "list-voices": "node scripts/list-voices.mjs",