Alan2Z/dsh-speak

Alan2Z/dsh-speak

零外部依赖、事件驱动、无需额外模型、不消耗任何 token的语音播报插件。使用系统自带自然语音进行播报,支持win/mac双平台;最终回复、审批与提问提醒、可选事件播报(回合结束/命令完成/目标变更/工具出错/待办更新)、最终回复可重播、双语言可视化设置。

语音与音频dsh-speak

dsh-speak 🔊 — Voice announcements for AI coding harnesses

English · 中文

Let your agent tell you when a long task is done — no more staring at the screen.

dsh-speak reads the final assistant reply aloud through system speech synthesis — on Windows using natural voices (Windows 11 built-in, or [NaturalVoiceSAPIAdapter] on Windows 10) with graceful fallback to stock voices; on macOS using the built-in say (can follow a Siri natural voice). It was built for DeepSeek Harness and is structured so any harness can plug in.

Features

  • Automatic: DSH web plugin watches the session event stream and announces the

final reply (skips reasoning/tool-call narration, merges multi-step messages).

  • Gets your attention: announces approval requests (hears "需要你的审批" when

the agent is waiting on you) and questions the agent asks via ask_user_question.

  • Final-reply replay (1.7.0): every final reply (turn tail) has a 🔊 button

in its action bar — click to replay that message, click again to stop, click another to switch. Speech execution stays fully owned by the DSH host (keeps speaking even with the browser closed).

  • Host speech queue (1.7.0): only one native speech process runs at a time;

queued items continue automatically. A WebSocket syncs the live state (which message is speaking, queue length) to the UI.

  • Optional event announcements (1.6.0): turn end, command done, goal changes,

tool errors, and todo updates can each be announced, toggled independently (off by default).

  • Visual configuration (1.7.0): a dedicated Settings → dsh-speak settings

page — every option (master switch, automatic speech, Markdown cleaning, code blocks, event toggles, fixed prompt, …) is editable from the Web UI, no hand-edited YAML.

  • Master switch (1.6.0): silence everything with one toggle.
  • Bundle auto-registration (1.3.0): declare the package in dsh.profile.bundles

and the plugin registers itself via the bundled cordis.patch.yml — no manual patch entry needed.

  • Best-effort: never throws, never blocks the harness, never breaks a session.
  • Natural voices: Windows prefers natural voices — Windows 11 built-in packs,

or voices registered via NaturalVoiceSAPIAdapter on Windows 10 (e.g. Xiaoxiao); macOS uses the system reading voice (Siri natural voices on recent macOS). Both fall back to any installed voice.

  • Robust text cleaning: strips markdown/URLs/emoji that make speech synthesis

fail silently, and guards the adapter's per-utterance character ceiling.

  • Portable engine: any process can speak with one line:

Windows powershell -File speak.ps1 -Text "你好" / macOS ./speak.sh -t "你好".

How it works

harness event (DSH session event / Claude Code Stop hook / anything)
      │
      ▼  adapters/…  (harness-specific trigger: filter, throttle, cancel)
      ▼  engine/speak.ps1 / speak.sh  (harness-agnostic: clean text → SAPI5 / say)
      ▼  🔊 you hear the final reply

The adapter turns harness-specific events into engine calls; the engine cleans the text and speaks it, fully decoupled from any harness. Full design: docs/DESIGN.md.

Prerequisites

Windows:

  • Windows 10 or 11, PowerShell (any recent version).
  • Natural voices:
  • Windows 11 (21H2–23H2): natural voice packs are built into the system —

no extra installation. Enable/switch them in *Settings → Accessibility → Narrator* or *Settings → Time & Language → Speech*.

  • Windows 11 24H2/25H2: natural voices moved to MSIX app packages, which

System.Speech may not enumerate (falls back to a robotic stock voice). As on Windows 10, install to bridge them.

  • Windows 10: install

and use its VoiceDownloader to download the natural voice pack(s) you want (Chinese or any other language).

  • Without natural voices, the engine falls back to a stock voice (e.g. Huihui).

macOS:

  • macOS (Apple Silicon or Intel), built-in say command — no extra software.
  • Chinese voices: see the macOS section (incl. the Siri natural-voice

picker and its pitfalls).

DSH web app:

  • Tested against DSH 0.1.7-rc.2. Host/client APIs changed twice since 0.1.1,

both handled here:

  • 0.1.7 replaced the settings provider API with Config projection: the

settings service now reads each active Loader entry's own exported Config schema and projects its *volatile* fields into the settings UI (ctx.settings.describe() on the host, ctx.configForms in the browser). settings.register(namespace, schema, { base }) — the 1.6.0–1.8.x wiring — is gone, and the browser service settingsScope was replaced by configForms. This plugin therefore exports its schema as Config and the settings namespace is its entry id (dsh-speak; a leftover speech-hook row from 1.8.x is still bound, see below).

  • 0.1.2 stopped putting Conversation target data into the Session snapshot

(the 🔊 button resolves the clicked message through the Chat target hook useChat), and deleted @deepseek-ai/dsh-settings's installSettingsSection / settingsNamespace helpers.

  • The host floor lives where dsh-market reads it: engines.dsh in package.json

(>=0.1.7-rc.2). The catalog card and its "compatible with current DSH" filter read exactly that field, so the floor moves only after a release has been verified against the new host.

  • 1.8.2 requires the settings model introduced in 0.1.7: on an older host the

browser half would wait forever for the configForms service. Use 1.8.1 for DSH ≤ 0.1.6.

Install & quick start

DSH — Option A: npm plugin (recommended)

# 1. install the plugin into your web profile (adds dsh-speak to
#    ~/.dsh/profiles/web/package.json dependencies AND to dsh.profile.bundles)
dsh plugin --profile web add dsh-speak

# 2. that is all — the package ships its own bundle patch, which registers the
#    `dsh-speak` entry. Do NOT also hand-write an `insert:` row for it:
#    registering the same entry twice makes the id non-unique, and DSH's config
#    editor then rejects every settings write with
#    `settings/rejected: Configuration for "dsh-speak" is overridden by a home
#    patch or command-line overlay` (speech keeps working, the settings page
#    silently bounces).
#
#    To pin options in YAML anyway, add ONLY this top-level row (the settings page
#    edits it in place; `config` on a top-level row is the shape the editor
#    supports — see "DSH plugin config"):
#    - id: dsh-speak
#      name: 'dsh-speak'
#      config: {}
#
#    The entry id IS the settings namespace on DSH >= 0.1.7: your options are
#    stored under that key in this same file. A row left over from 1.8.x
#    (id: speech-hook) keeps working — the browser half binds either id.

# 3. restart the DSH web app — replies are now announced automatically
Installing from the in-app marketplace is the same path: it adds the package to
dsh.profile.bundles. Only the manual installs below need hand-written rows.
One registration path per profile — never both.
No pnpm? dsh plugin forwards to pnpm, which is not installed on every
machine. The exact same install can be done with npm directly:
```powershell
npm install --prefix "$env:USERPROFILE.dsh\profiles\web" dsh-speak
`
On macOS (bash):
```bash
npm install --prefix "$HOME/.dsh/profiles/web" dsh-speak
`
An npm install alone does not register the plugin: add either the bundle
name to ~/.dsh/profiles/web/package.json → dsh.profile.bundles, or the two
rows from the file-install section — not both.

The engine ships inside the package (node_modules/dsh-speak/engine/), so no extra copying is needed.

Let your agent do it? Paste this repo URL
(https://github.com/Alan2Z/dsh-speak) into your DSH session and ask it to
install the plugin — your agent follows this very README. Approving the
out-of-workspace writes (~/.dsh) is all that's needed.

DSH — Option B: file install (no npm needed)

# 1. clone
git clone https://github.com/Alan2Z/dsh-speak.git
cd dsh-speak

# 2. one-command install: copies engine + plugin, registers in cordis.patch.yml
powershell.exe -NoProfile -ExecutionPolicy Bypass -File adapters\dsh\install.ps1

# 3. verify the engine speaks
powershell -NoProfile -ExecutionPolicy Bypass -File "$env:USERPROFILE\.dsh\hooks\speak.ps1" -Text "你好,语音播报已就绪。"

# 4. restart the DSH web app — replies are now announced automatically

What the file installer did:

file · destination engine/*.ps1 · %USERPROFILE%.dsh\hooks adapters/dsh/speech-hook.js · %USERPROFILE%.dsh\profiles\web\plugins registration entry · appended to %USERPROFILE%.dsh\profiles\web\cordis.patch.yml (backed up first)

macOS

The same adapter runs natively on macOS — the plugin auto-detects the platform and calls engine/speak.sh (the built-in say command) instead of speak.ps1. Since 1.2.0 the macOS engine ships in the npm package — no extra software.