dsh-speak 1.5.0 → 1.7.1
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.md +131 -63
- package/README.zh-CN.md +125 -61
- package/adapters/dsh/speech-hook.js +486 -140
- package/client/client.js +329 -0
- package/docs/DESIGN.md +112 -31
- package/docs/DESIGN.zh-CN.md +98 -26
- package/engine/speak.ps1 +64 -15
- package/engine/speak.sh +58 -66
- package/package.json +36 -3
- package/docs/CUSTOMIZATION.md +0 -89
- package/docs/CUSTOMIZATION.zh-CN.md +0 -88
package/README.md
CHANGED
|
@@ -17,55 +17,27 @@ on macOS using the built-in `say` (can follow a Siri natural voice). It was buil
|
|
|
17
17
|
for [DeepSeek Harness](https://github.com/deepseek-ai/dsh)
|
|
18
18
|
and is structured so any harness can plug in.
|
|
19
19
|
|
|
20
|
-
> **Project status**: this project exists only to provide an **already-verified
|
|
21
|
-
> solution** for users who want their harness to speak. Barring unexpected
|
|
22
|
-
> circumstances, it will not be updated further.
|
|
23
|
-
|
|
24
|
-
## TL;DR — install for DSH
|
|
25
|
-
|
|
26
|
-
1. Install the package into your web profile (pick one):
|
|
27
|
-
|
|
28
|
-
```powershell
|
|
29
|
-
dsh plugin --profile web add dsh-speak
|
|
30
|
-
# or, without pnpm:
|
|
31
|
-
npm install --prefix "$env:USERPROFILE\.dsh\profiles\web" dsh-speak
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
On macOS (bash):
|
|
35
|
-
|
|
36
|
-
```bash
|
|
37
|
-
npm install --prefix "$HOME/.dsh/profiles/web" dsh-speak
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
2. Append to `~/.dsh/profiles/web/cordis.patch.yml`:
|
|
41
|
-
|
|
42
|
-
```yaml
|
|
43
|
-
- insert:
|
|
44
|
-
- id: speech-hook
|
|
45
|
-
name: 'dsh-speak'
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
3. Restart the DSH web app — replies are now announced aloud.
|
|
49
|
-
|
|
50
|
-
> **Let your agent do it?** Paste this repo URL
|
|
51
|
-
> (`https://github.com/Alan2Z/dsh-speak`) into your DSH session and ask it to
|
|
52
|
-
> install the plugin — your agent follows this very README. Approving the
|
|
53
|
-
> out-of-workspace writes (`~/.dsh`) is all that's needed.
|
|
54
|
-
|
|
55
|
-
```
|
|
56
|
-
harness event (DSH session event / Claude Code Stop hook / anything)
|
|
57
|
-
│
|
|
58
|
-
▼ adapters/… (harness-specific trigger: filter, throttle, cancel)
|
|
59
|
-
▼ engine/speak.ps1 / speak.sh (harness-agnostic: clean text → SAPI5 / say)
|
|
60
|
-
▼ 🔊 you hear the final reply
|
|
61
|
-
```
|
|
62
|
-
|
|
63
20
|
## Features
|
|
64
21
|
|
|
65
22
|
- **Automatic**: DSH web plugin watches the session event stream and announces the
|
|
66
23
|
final reply (skips reasoning/tool-call narration, merges multi-step messages).
|
|
67
24
|
- **Gets your attention**: announces approval requests (hears "需要你的审批" when
|
|
68
25
|
the agent is waiting on you) and questions the agent asks via `ask_user_question`.
|
|
26
|
+
- **Final-reply replay** (1.7.0): every final reply (turn tail) has a 🔊 button
|
|
27
|
+
in its action bar — click to replay that message, click again to stop, click
|
|
28
|
+
another to switch. Speech execution stays fully owned by the DSH host (keeps
|
|
29
|
+
speaking even with the browser closed).
|
|
30
|
+
- **Host speech queue** (1.7.0): only one native speech process runs at a time;
|
|
31
|
+
queued items continue automatically. A WebSocket syncs the live state (which
|
|
32
|
+
message is speaking, queue length) to the UI.
|
|
33
|
+
- **Optional event announcements** (1.6.0): turn end, command done, goal changes,
|
|
34
|
+
tool errors, and todo updates can each be announced, toggled independently
|
|
35
|
+
(off by default).
|
|
36
|
+
- **Visual configuration** (1.7.0): a dedicated Settings → dsh-speak settings
|
|
37
|
+
page — every option (master switch, automatic speech, Markdown cleaning, code
|
|
38
|
+
blocks, event toggles, fixed prompt, …) is editable from the Web UI, no
|
|
39
|
+
hand-edited YAML.
|
|
40
|
+
- **Master switch** (1.6.0): silence everything with one toggle.
|
|
69
41
|
- **Bundle auto-registration** (1.3.0): declare the package in `dsh.profile.bundles`
|
|
70
42
|
and the plugin registers itself via the bundled `cordis.patch.yml` — no manual
|
|
71
43
|
patch entry needed.
|
|
@@ -79,6 +51,20 @@ harness event (DSH session event / Claude Code Stop hook / anything)
|
|
|
79
51
|
- **Portable engine**: any process can speak with one line:
|
|
80
52
|
Windows `powershell -File speak.ps1 -Text "你好"` / macOS `./speak.sh -t "你好"`.
|
|
81
53
|
|
|
54
|
+
## How it works
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
harness event (DSH session event / Claude Code Stop hook / anything)
|
|
58
|
+
│
|
|
59
|
+
▼ adapters/… (harness-specific trigger: filter, throttle, cancel)
|
|
60
|
+
▼ engine/speak.ps1 / speak.sh (harness-agnostic: clean text → SAPI5 / say)
|
|
61
|
+
▼ 🔊 you hear the final reply
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The adapter turns harness-specific events into engine calls; the engine cleans the
|
|
65
|
+
text and speaks it, fully decoupled from any harness. Full design:
|
|
66
|
+
[docs/DESIGN.md](docs/DESIGN.md).
|
|
67
|
+
|
|
82
68
|
## Prerequisites
|
|
83
69
|
|
|
84
70
|
Windows:
|
|
@@ -100,9 +86,9 @@ macOS:
|
|
|
100
86
|
- Chinese voices: see the [macOS](#macos) section (incl. the Siri natural-voice
|
|
101
87
|
picker and its pitfalls).
|
|
102
88
|
|
|
103
|
-
##
|
|
89
|
+
## Install & quick start
|
|
104
90
|
|
|
105
|
-
### Option A
|
|
91
|
+
### DSH — Option A: npm plugin (recommended)
|
|
106
92
|
|
|
107
93
|
```powershell
|
|
108
94
|
# 1. install the plugin into your web profile (adds dsh-speak to
|
|
@@ -124,11 +110,22 @@ dsh plugin --profile web add dsh-speak
|
|
|
124
110
|
> ```powershell
|
|
125
111
|
> npm install --prefix "$env:USERPROFILE\.dsh\profiles\web" dsh-speak
|
|
126
112
|
> ```
|
|
113
|
+
>
|
|
114
|
+
> On macOS (bash):
|
|
115
|
+
>
|
|
116
|
+
> ```bash
|
|
117
|
+
> npm install --prefix "$HOME/.dsh/profiles/web" dsh-speak
|
|
118
|
+
> ```
|
|
127
119
|
|
|
128
120
|
The engine ships inside the package (`node_modules/dsh-speak/engine/`), so no extra
|
|
129
121
|
copying is needed.
|
|
130
122
|
|
|
131
|
-
|
|
123
|
+
> **Let your agent do it?** Paste this repo URL
|
|
124
|
+
> (`https://github.com/Alan2Z/dsh-speak`) into your DSH session and ask it to
|
|
125
|
+
> install the plugin — your agent follows this very README. Approving the
|
|
126
|
+
> out-of-workspace writes (`~/.dsh`) is all that's needed.
|
|
127
|
+
|
|
128
|
+
### DSH — Option B: file install (no npm needed)
|
|
132
129
|
|
|
133
130
|
```powershell
|
|
134
131
|
# 1. clone
|
|
@@ -152,14 +149,12 @@ What the file installer did:
|
|
|
152
149
|
| `adapters/dsh/speech-hook.js` | `%USERPROFILE%\.dsh\profiles\web\plugins\` |
|
|
153
150
|
| registration entry | appended to `%USERPROFILE%\.dsh\profiles\web\cordis.patch.yml` (backed up first) |
|
|
154
151
|
|
|
155
|
-
|
|
152
|
+
### macOS
|
|
156
153
|
|
|
157
154
|
The same adapter runs natively on macOS — the plugin auto-detects the platform and
|
|
158
155
|
calls `engine/speak.sh` (the built-in `say` command) instead of `speak.ps1`.
|
|
159
156
|
**Since 1.2.0 the macOS engine ships in the npm package** — no extra software.
|
|
160
157
|
|
|
161
|
-
### Install (npm — same as Windows)
|
|
162
|
-
|
|
163
158
|
```bash
|
|
164
159
|
# 1. install into your web profile (no pnpm needed — only `dsh plugin` requires it)
|
|
165
160
|
npm install --prefix "$HOME/.dsh/profiles/web" dsh-speak
|
|
@@ -169,13 +164,13 @@ npm install --prefix "$HOME/.dsh/profiles/web" dsh-speak
|
|
|
169
164
|
# - id: speech-hook
|
|
170
165
|
# name: 'dsh-speak'
|
|
171
166
|
|
|
172
|
-
# 3. no restart needed — the patch watcher hot-reloads;
|
|
173
|
-
#
|
|
167
|
+
# 3. no restart needed — the patch watcher hot-reloads; replies are announced
|
|
168
|
+
# after the throttle (~1.5 s); tool-calling replies are announced at turn end
|
|
174
169
|
```
|
|
175
170
|
|
|
176
171
|
> With pnpm installed, `dsh plugin --profile web add dsh-speak` works identically.
|
|
177
172
|
|
|
178
|
-
|
|
173
|
+
#### Voices (important — two pitfalls)
|
|
179
174
|
|
|
180
175
|
- By default the engine follows the **system reading voice** (*Settings →
|
|
181
176
|
Accessibility → Spoken Content → System Voice*). On **macOS 26** that picker has
|
|
@@ -194,7 +189,7 @@ npm install --prefix "$HOME/.dsh/profiles/web" dsh-speak
|
|
|
194
189
|
- Use `-v Eddy|Flo|Tingting` to force a specific voice (`say -v '?'` lists them).
|
|
195
190
|
- `say` has no volume flag — volume follows the system output volume.
|
|
196
191
|
|
|
197
|
-
|
|
192
|
+
#### Test the engine alone (no DSH needed)
|
|
198
193
|
|
|
199
194
|
```bash
|
|
200
195
|
curl -sfL -o ~/speak.sh "https://cdn.jsdelivr.net/gh/Alan2Z/dsh-speak@main/engine/speak.sh"
|
|
@@ -203,7 +198,7 @@ chmod +x ~/speak.sh
|
|
|
203
198
|
~/speak.sh -t "测试" -v Eddy -r 200 # explicit voice + rate
|
|
204
199
|
```
|
|
205
200
|
|
|
206
|
-
|
|
201
|
+
### Claude Code
|
|
207
202
|
|
|
208
203
|
Register the Stop hook in `~/.claude/settings.json`:
|
|
209
204
|
|
|
@@ -224,7 +219,7 @@ Register the Stop hook in `~/.claude/settings.json`:
|
|
|
224
219
|
}
|
|
225
220
|
```
|
|
226
221
|
|
|
227
|
-
|
|
222
|
+
### Any other harness
|
|
228
223
|
|
|
229
224
|
Call the engine directly from your agent / wrapper / script:
|
|
230
225
|
|
|
@@ -232,7 +227,7 @@ Call the engine directly from your agent / wrapper / script:
|
|
|
232
227
|
# announce a one-liner
|
|
233
228
|
powershell -NoProfile -ExecutionPolicy Bypass -File engine\speak.ps1 -Text "构建完成"
|
|
234
229
|
|
|
235
|
-
# announce a long summary (
|
|
230
|
+
# announce a long summary (blocking, returns when done)
|
|
236
231
|
powershell -NoProfile -ExecutionPolicy Bypass -File engine\speech-summary.ps1 -Text "…"
|
|
237
232
|
|
|
238
233
|
# ask for user attention (blocking, for prompts/approvals)
|
|
@@ -241,14 +236,23 @@ powershell -NoProfile -ExecutionPolicy Bypass -File engine\speech-prompt.ps1 -Te
|
|
|
241
236
|
|
|
242
237
|
## Configuration
|
|
243
238
|
|
|
244
|
-
Engine parameters
|
|
239
|
+
### Engine parameters
|
|
240
|
+
|
|
241
|
+
See [docs/DESIGN.md §5 configuration reference](docs/DESIGN.md#5-configuration-reference):
|
|
245
242
|
|
|
246
243
|
```powershell
|
|
247
244
|
speak.ps1 -Text "…" -Volume 50 -Rate 1 -MaxChars 300 -LongTextMessage "本次播报内容较长,请自行阅读。"
|
|
248
245
|
```
|
|
249
246
|
|
|
250
|
-
DSH plugin
|
|
251
|
-
|
|
247
|
+
### DSH plugin config
|
|
248
|
+
|
|
249
|
+
**Either way works, and they stay in sync** (both write the same settings
|
|
250
|
+
document):
|
|
251
|
+
|
|
252
|
+
1. **Web UI (1.7.0, recommended)**: a dedicated Settings → dsh-speak settings
|
|
253
|
+
page. Every option is editable and saved there (visible in `dsh --dump-config`,
|
|
254
|
+
per-profile, survives npm updates).
|
|
255
|
+
2. **Profile patch `config` block** (equivalent):
|
|
252
256
|
|
|
253
257
|
```yaml
|
|
254
258
|
# ~/.dsh/profiles/web/cordis.patch.yml
|
|
@@ -256,18 +260,80 @@ DSH plugin configuration — **prefer the profile patch `config` block** (visibl
|
|
|
256
260
|
- id: speech-hook
|
|
257
261
|
name: 'dsh-speak'
|
|
258
262
|
config:
|
|
263
|
+
enabled: true # master switch: false silences everything
|
|
264
|
+
automaticSpeech: true # auto-speak final replies
|
|
265
|
+
queueAllMessages: false # true = enqueue every assistant message as it arrives
|
|
266
|
+
replayFullRead: false # true = manual replay skips the long-text truncation, reads everything
|
|
267
|
+
cleanMarkdownFormatting: true # convert Markdown to natural speech
|
|
268
|
+
readInlineCode: true # read inline code without backticks
|
|
269
|
+
codeBlocks: smart # all | smart | replace (fenced code blocks)
|
|
270
|
+
codeBlockMaxChars: 300 # smart-mode code block character limit
|
|
271
|
+
codeBlockReplacementText: 'You can see the code in our history.' # replace-mode text
|
|
259
272
|
throttleMs: 1500 # merge delay before announcing (ms)
|
|
260
273
|
engine: '' # engine path override; '' = auto-resolve
|
|
261
274
|
announceApprovals: true # speak approval requests
|
|
262
275
|
announceQuestions: true # speak ask_user_question content
|
|
263
|
-
stripApprovalPrefix: true # strip
|
|
276
|
+
stripApprovalPrefix: true # strip "escalate sandbox to ...: " prefix
|
|
277
|
+
questionGapMs: 2000 # pause between multiple question announcements (ms)
|
|
264
278
|
longTextMode: message # message | heading (speak largest md heading)
|
|
265
|
-
|
|
279
|
+
longTextMessage: '本次播报内容较长,请自行阅读。' # fixed prompt for message mode
|
|
280
|
+
maxChars: 300 # per-utterance ceiling (macOS default 0 = unlimited)
|
|
266
281
|
volume: 50 # Windows only
|
|
267
282
|
rate: 0 # 0 = engine default (Windows SAPI scale / macOS wpm)
|
|
283
|
+
# —— optional event announcements (1.6.0, all off by default) ——
|
|
284
|
+
announceTurnEnd: false # turn/end — "第 N 轮对话完成"
|
|
285
|
+
announceCommandDone: false # command/done — command finished/failed
|
|
286
|
+
announceGoalChange: false # goal/change — goal created/updated/completed
|
|
287
|
+
announceToolErrors: false # tool/result error — announce (english dropped)
|
|
288
|
+
announceTodoWrite: false # todo/write — todo list updated
|
|
268
289
|
```
|
|
269
290
|
|
|
270
|
-
|
|
291
|
+
> Resolution order: schema default → patch `config` → UI user settings. Fields
|
|
292
|
+
> written in YAML show up in the UI too. Platform note: `maxChars` defaults to
|
|
293
|
+
> 0 on macOS (`say` has no ceiling) and 300 on Windows (SAPI safe limit).
|
|
294
|
+
|
|
295
|
+
#### Option reference
|
|
296
|
+
|
|
297
|
+
| option | default | effect |
|
|
298
|
+
| ------ | ------- | ------ |
|
|
299
|
+
| `enabled` | `true` | **master switch**: when off, nothing is ever announced (final reply / approvals / questions / optional events / replay) |
|
|
300
|
+
| `automaticSpeech` | `true` | auto-speak final replies; manual replay always remains available |
|
|
301
|
+
| `queueAllMessages` | `false` | `true` enqueues every assistant message as it arrives (intermediate messages spoken too, FIFO); default only speaks the throttled final reply |
|
|
302
|
+
| `replayFullRead` | `false` | `true` makes manual replay skip the long-text heading truncation (`longTextMode: heading`) and read everything in chunks |
|
|
303
|
+
| `cleanMarkdownFormatting` | `true` | converts Markdown into natural speech text (link labels kept, URLs/heading/emphasis cleaned) |
|
|
304
|
+
| `readInlineCode` | `true` | read inline code without backtick markers |
|
|
305
|
+
| `codeBlocks` | `smart` | fenced code blocks: `all` read / `smart` (read when ≤ `codeBlockMaxChars`) / `replace` with the replacement text |
|
|
306
|
+
| `codeBlockMaxChars` | `300` | code block character limit for `smart` mode |
|
|
307
|
+
| `codeBlockReplacementText` | `You can see the code in our history.` | replacement spoken in `replace` mode (or over-limit `smart`) |
|
|
308
|
+
| `throttleMs` | `1500` | how long a reply's text waits before being announced (merges multi-step messages) |
|
|
309
|
+
| `engine` | `''` | explicit engine script path; `''` auto-resolves: `<package>/engine/<platform>` → `~/.dsh/hooks/<platform>` |
|
|
310
|
+
| `announceApprovals` | `true` | announce `approval/asked` events (reason, or the fixed prompt) |
|
|
311
|
+
| `announceQuestions` | `true` | announce `ask_user_question`: each question spoken separately with a "问题N" prefix (when several) and "选项N" prefixes matching the UI numbering; a `questionGapMs` pause between questions |
|
|
312
|
+
| `questionGapMs` | `2000` | pause between multiple question announcements (ms); 0 = no pause |
|
|
313
|
+
| `stripApprovalPrefix` | `true` | strip the fixed English template prefix (`escalate sandbox to danger-full-access: `) from approval reasons, keeping the human explanation |
|
|
314
|
+
| `longTextMode` | `message` | `message` = fixed prompt for over-long text; `heading` = speak the largest markdown heading instead (see below) |
|
|
315
|
+
| `longTextMessage` | `本次播报内容较长,请自行阅读。` | the fixed prompt spoken for over-long text in `message` mode (editable in the UI) |
|
|
316
|
+
| `maxChars` | platform | per-utterance ceiling. **macOS default 0 (`say` has no ceiling); Windows default 300** (SAPI fails silently beyond ~375-470) |
|
|
317
|
+
| `volume` | `50` | Windows only (0-100); macOS volume follows the system |
|
|
318
|
+
| `rate` | `0` | `0` = engine default (Windows SAPI scale, e.g. 1; macOS words-per-minute, e.g. 175) |
|
|
319
|
+
| `announceTurnEnd` | `false` | announce "第 N 轮对话完成/中断/异常结束" on turn end (`turn/end`) |
|
|
320
|
+
| `announceCommandDone` | `false` | announce when a command finishes or fails (`command/done`) |
|
|
321
|
+
| `announceGoalChange` | `false` | announce goal created/updated/completed/paused/resumed (`goal/change`, objective head) |
|
|
322
|
+
| `announceToolErrors` | `false` | announce "工具调用出错" when a tool call returns an error (`tool/result` with `error` or an `isError` content block; English details / technical codes dropped, Chinese details kept) |
|
|
323
|
+
| `announceTodoWrite` | `false` | announce "待办已更新:n/m 完成" when the agent updates its todos (`todo/write`) |
|
|
324
|
+
|
|
325
|
+
#### Long-text modes
|
|
326
|
+
|
|
327
|
+
When cleaned text exceeds `maxChars`:
|
|
328
|
+
|
|
329
|
+
- **`message`** (default): speak `longTextMessage` (`本次播报内容较长,请自行阅读。`,
|
|
330
|
+
editable in the UI or YAML).
|
|
331
|
+
- **`heading`**: pick the *largest* markdown heading in the raw text — fewest `#`
|
|
332
|
+
wins, tie → first; if there is no heading line, the first non-empty line is used.
|
|
333
|
+
The chosen candidate is still cleaned and subject to the `maxChars` ceiling,
|
|
334
|
+
falling back to the message if it is itself too long.
|
|
335
|
+
|
|
336
|
+
Full architecture and design rationale: [docs/DESIGN.md](docs/DESIGN.md).
|
|
271
337
|
|
|
272
338
|
## Customizing (survives npm updates)
|
|
273
339
|
|
|
@@ -322,10 +388,12 @@ engine/ harness-agnostic speech engine (PowerShell + SAPI5 / ba
|
|
|
322
388
|
speech-summary.ps1 blocking reply-summary announcement
|
|
323
389
|
adapters/
|
|
324
390
|
dsh/ DSH web plugin + one-command installer
|
|
325
|
-
speech-hook.js session-event trigger (throttle +
|
|
391
|
+
speech-hook.js session-event trigger (throttle/cancel + optional events + FIFO speech queue + WebSocket + settings registration)
|
|
326
392
|
install.ps1 copies + registers + backs up
|
|
327
393
|
claude-code/
|
|
328
394
|
stop-hook.ps1 Claude Code Stop hook trigger
|
|
395
|
+
client/
|
|
396
|
+
client.js DSH browser bundle: turn-tail Speak/Stop button + Settings → dsh-speak settings page
|
|
329
397
|
docs/
|
|
330
398
|
DESIGN.md full design rationale, pitfalls, extension guide
|
|
331
399
|
```
|
package/README.zh-CN.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# dsh-speak 🔊 — 为 AI 编程 harness 提供语音播报
|
|
2
2
|
|
|
3
|
+
**中文** · [English](README.md)
|
|
4
|
+
|
|
3
5
|

|
|
4
6
|
|
|
5
7
|
[](https://awesome-dsh-plugin.com)
|
|
@@ -15,53 +17,23 @@ dsh-speak 通过系统语音合成把 Agent 的最终回复朗读出来——Win
|
|
|
15
17
|
[DeepSeek Harness](https://github.com/deepseek-ai/dsh) 而生,但结构上
|
|
16
18
|
任何 harness 都能接入。
|
|
17
19
|
|
|
18
|
-
> **项目定位**:本项目只是为了给想让 harness 开口说话的用户提供一种**已经验证过的方案**;
|
|
19
|
-
> 没有意外的话,后续不会再更新。
|
|
20
|
-
|
|
21
|
-
## 三分钟安装 — DSH
|
|
22
|
-
|
|
23
|
-
1. 把包装进你的 web profile(二选一):
|
|
24
|
-
|
|
25
|
-
```powershell
|
|
26
|
-
dsh plugin --profile web add dsh-speak
|
|
27
|
-
# 或(没有 pnpm 时):
|
|
28
|
-
npm install --prefix "$env:USERPROFILE\.dsh\profiles\web" dsh-speak
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
macOS 上(bash):
|
|
32
|
-
|
|
33
|
-
```bash
|
|
34
|
-
npm install --prefix "$HOME/.dsh/profiles/web" dsh-speak
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
2. 在 `~/.dsh/profiles/web/cordis.patch.yml` 末尾追加:
|
|
38
|
-
|
|
39
|
-
```yaml
|
|
40
|
-
- insert:
|
|
41
|
-
- id: speech-hook
|
|
42
|
-
name: 'dsh-speak'
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
3. 重启 DSH web 应用——之后回复就会被朗读出来。
|
|
46
|
-
|
|
47
|
-
> **想让 Agent 帮你装?** 把本仓库地址(`https://github.com/Alan2Z/dsh-speak`)
|
|
48
|
-
> 丢给你的 DSH 会话,让它照着这份 README 安装即可——它读的就是你正在看的这份文档。
|
|
49
|
-
> 只需要同意它对 `~/.dsh`(工作区外)的写入审批。
|
|
50
|
-
|
|
51
|
-
```
|
|
52
|
-
harness 事件(DSH 会话事件 / Claude Code Stop hook / 任意方式)
|
|
53
|
-
│
|
|
54
|
-
▼ adapters/… (harness 专属触发器:过滤、节流、取消)
|
|
55
|
-
▼ engine/speak.ps1 / speak.sh (与 harness 无关:清洗文本 → SAPI5 / say)
|
|
56
|
-
▼ 🔊 你听到最终回复
|
|
57
|
-
```
|
|
58
|
-
|
|
59
20
|
## 特性
|
|
60
21
|
|
|
61
22
|
- **全自动**:DSH web 插件监听会话事件流,自动播报最终回复
|
|
62
23
|
(跳过 reasoning/工具调用旁白,合并同一回复的多步消息)。
|
|
63
24
|
- **提醒你**:审批请求(Agent 等你操作时会播"需要你的审批")和 Agent 通过
|
|
64
25
|
`ask_user_question` 提出的问题都会播报。
|
|
26
|
+
- **最终回复重播**(1.7.0):每条最终回复(回合尾部)操作栏有 🔊 按钮——点击
|
|
27
|
+
重播该条回复、再点停止、点另一条切换。语音执行完全由 DSH host 拥有(浏览器
|
|
28
|
+
关掉也继续读)。
|
|
29
|
+
- **host 语音队列**(1.7.0):同一时间只运行一个语音进程,队列自动串行;
|
|
30
|
+
WebSocket 实时同步"正在读哪条、队列长度"到 UI。
|
|
31
|
+
- **多事件可选播报**(1.6.0):回合结束、命令完成、目标变更、工具出错、
|
|
32
|
+
待办更新等事件都可选播报,各自独立开/关(默认关)。
|
|
33
|
+
- **可视化配置**(1.7.0):设置 → dsh-speak 设置独立设置页,所有配置项(总开关、
|
|
34
|
+
自动朗读、Markdown 清洗、代码块、事件开关、固定提示语…)直接改,无需手写
|
|
35
|
+
YAML。
|
|
36
|
+
- **总开关**(1.6.0):一键静音所有播报。
|
|
65
37
|
- **Bundle 自动注册**(1.3.0):把包声明进 `dsh.profile.bundles`,插件通过包内
|
|
66
38
|
自带的 `cordis.patch.yml` 自动注册,无需手动写 patch 条目。
|
|
67
39
|
- **尽力而为**:绝不抛错、绝不阻塞 harness、绝不破坏会话。
|
|
@@ -73,9 +45,22 @@ harness 事件(DSH 会话事件 / Claude Code Stop hook / 任意方式)
|
|
|
73
45
|
- **引擎可移植**:任意进程一行即可朗读:
|
|
74
46
|
Windows `powershell -File speak.ps1 -Text "你好"` / macOS `./speak.sh -t "你好"`。
|
|
75
47
|
|
|
48
|
+
## 工作原理
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
harness 事件(DSH 会话事件 / Claude Code Stop hook / 任意方式)
|
|
52
|
+
│
|
|
53
|
+
▼ adapters/… (harness 专属触发器:过滤、节流、取消)
|
|
54
|
+
▼ engine/speak.ps1 / speak.sh (与 harness 无关:清洗文本 → SAPI5 / say)
|
|
55
|
+
▼ 🔊 你听到最终回复
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
适配器负责把 harness 专属事件转成引擎调用;引擎负责清洗文本并朗读,
|
|
59
|
+
与 harness 完全解耦。完整设计见 [docs/DESIGN.zh-CN.md](docs/DESIGN.zh-CN.md)。
|
|
60
|
+
|
|
76
61
|
## 前置条件
|
|
77
62
|
|
|
78
|
-
Windows
|
|
63
|
+
### Windows
|
|
79
64
|
|
|
80
65
|
- Windows 10 或 11,任意较新的 PowerShell。
|
|
81
66
|
- 自然语音:
|
|
@@ -86,14 +71,14 @@ Windows:
|
|
|
86
71
|
并用它的 VoiceDownloader 手动下载你需要的中文或其他语言的自然语音包。
|
|
87
72
|
- 没有自然语音时,引擎回退到系统自带语音(如 Huihui)。
|
|
88
73
|
|
|
89
|
-
macOS
|
|
74
|
+
### macOS 要求
|
|
90
75
|
|
|
91
76
|
- macOS(Apple Silicon / Intel 均可),系统自带 `say` 命令,**无需安装任何软件**。
|
|
92
|
-
-
|
|
77
|
+
- 中文音色与 Siri 音色的选择入口/坑见 [macOS](#macos) 一节。
|
|
93
78
|
|
|
94
|
-
##
|
|
79
|
+
## 安装与快速开始
|
|
95
80
|
|
|
96
|
-
### 方式 A
|
|
81
|
+
### DSH — 方式 A:npm 插件(推荐)
|
|
97
82
|
|
|
98
83
|
```powershell
|
|
99
84
|
# 1. 把插件装进你的 web profile(会写入 ~/.dsh/profiles/web/package.json 的 dependencies)
|
|
@@ -113,10 +98,20 @@ dsh plugin --profile web add dsh-speak
|
|
|
113
98
|
> ```powershell
|
|
114
99
|
> npm install --prefix "$env:USERPROFILE\.dsh\profiles\web" dsh-speak
|
|
115
100
|
> ```
|
|
101
|
+
>
|
|
102
|
+
> macOS(bash):
|
|
103
|
+
>
|
|
104
|
+
> ```bash
|
|
105
|
+
> npm install --prefix "$HOME/.dsh/profiles/web" dsh-speak
|
|
106
|
+
> ```
|
|
116
107
|
|
|
117
108
|
引擎随包分发(`node_modules/dsh-speak/engine/`),无需额外拷贝。
|
|
118
109
|
|
|
119
|
-
|
|
110
|
+
> **想让 Agent 帮你装?** 把本仓库地址(`https://github.com/Alan2Z/dsh-speak`)
|
|
111
|
+
> 丢给你的 DSH 会话,让它照着这份 README 安装即可——它读的就是你正在看的这份文档。
|
|
112
|
+
> 只需要同意它对 `~/.dsh`(工作区外)的写入审批。
|
|
113
|
+
|
|
114
|
+
### DSH — 方式 B:文件安装(不需要 npm)
|
|
120
115
|
|
|
121
116
|
```powershell
|
|
122
117
|
# 1. 克隆
|
|
@@ -140,14 +135,12 @@ powershell -NoProfile -ExecutionPolicy Bypass -File "$env:USERPROFILE\.dsh\hooks
|
|
|
140
135
|
| `adapters/dsh/speech-hook.js` | `%USERPROFILE%\.dsh\profiles\web\plugins\` |
|
|
141
136
|
| 注册条目 | 追加到 `%USERPROFILE%\.dsh\profiles\web\cordis.patch.yml`(先备份) |
|
|
142
137
|
|
|
143
|
-
|
|
138
|
+
### macOS
|
|
144
139
|
|
|
145
140
|
同一套适配层原生支持 macOS——插件自动检测平台,改调 `engine/speak.sh`
|
|
146
141
|
(系统自带的 `say` 命令)而不是 `speak.ps1`。**自 1.2.0 起 macOS 引擎随 npm 包
|
|
147
142
|
正式分发**,无需安装任何额外软件。
|
|
148
143
|
|
|
149
|
-
### 安装(npm 方式,与 Windows 等价)
|
|
150
|
-
|
|
151
144
|
```bash
|
|
152
145
|
# 1. 装进你的 web profile(没有 pnpm 也能装——dsh plugin 才依赖 pnpm)
|
|
153
146
|
npm install --prefix "$HOME/.dsh/profiles/web" dsh-speak
|
|
@@ -157,13 +150,13 @@ npm install --prefix "$HOME/.dsh/profiles/web" dsh-speak
|
|
|
157
150
|
# - id: speech-hook
|
|
158
151
|
# name: 'dsh-speak'
|
|
159
152
|
|
|
160
|
-
# 3. 无需重启——patch
|
|
161
|
-
#
|
|
153
|
+
# 3. 无需重启——patch 监视器会热更新;回复在节流后(约 1.5 秒)自动播报;
|
|
154
|
+
# 带工具调用的回复会在回合结束时补播最终回复
|
|
162
155
|
```
|
|
163
156
|
|
|
164
157
|
> 装过 pnpm 也可以 `dsh plugin --profile web add dsh-speak`,效果相同。
|
|
165
158
|
|
|
166
|
-
|
|
159
|
+
#### 音色(重要,有两个坑)
|
|
167
160
|
|
|
168
161
|
- 默认跟随**系统朗读声音**(系统设置 → 辅助功能 → 朗读内容 → 系统朗读声音)。
|
|
169
162
|
**macOS 26** 上该选择框旁有个 **ⓘ 圆圈图标**,点开才是完整音色列表——普通
|
|
@@ -178,7 +171,7 @@ npm install --prefix "$HOME/.dsh/profiles/web" dsh-speak
|
|
|
178
171
|
- 想强制指定音色用 `-v Eddy|Flo|Tingting`(`say -v '?'` 列出可用音色)。
|
|
179
172
|
- `say` 没有音量参数——音量跟随系统输出音量。
|
|
180
173
|
|
|
181
|
-
|
|
174
|
+
#### 单独测试引擎(不装 DSH 也行)
|
|
182
175
|
|
|
183
176
|
```bash
|
|
184
177
|
curl -sfL -o ~/speak.sh "https://cdn.jsdelivr.net/gh/Alan2Z/dsh-speak@main/engine/speak.sh"
|
|
@@ -187,7 +180,7 @@ chmod +x ~/speak.sh
|
|
|
187
180
|
~/speak.sh -t "测试" -v Eddy -r 200 # 指定音色 + 语速
|
|
188
181
|
```
|
|
189
182
|
|
|
190
|
-
|
|
183
|
+
### Claude Code
|
|
191
184
|
|
|
192
185
|
在 `~/.claude/settings.json` 注册 Stop hook:
|
|
193
186
|
|
|
@@ -208,7 +201,7 @@ chmod +x ~/speak.sh
|
|
|
208
201
|
}
|
|
209
202
|
```
|
|
210
203
|
|
|
211
|
-
|
|
204
|
+
### 其他任何 harness
|
|
212
205
|
|
|
213
206
|
直接从你的 Agent / 包装脚本 / 工具里调用引擎:
|
|
214
207
|
|
|
@@ -225,13 +218,21 @@ powershell -NoProfile -ExecutionPolicy Bypass -File engine\speech-prompt.ps1 -Te
|
|
|
225
218
|
|
|
226
219
|
## 配置
|
|
227
220
|
|
|
228
|
-
|
|
221
|
+
### 引擎参数
|
|
222
|
+
|
|
223
|
+
详见 [docs/DESIGN.zh-CN.md §5 配置参考](docs/DESIGN.zh-CN.md#5-配置参考):
|
|
229
224
|
|
|
230
225
|
```powershell
|
|
231
226
|
speak.ps1 -Text "…" -Volume 50 -Rate 1 -MaxChars 300 -LongTextMessage "本次播报内容较长,请自行阅读。"
|
|
232
227
|
```
|
|
233
228
|
|
|
234
|
-
DSH
|
|
229
|
+
### DSH 插件配置
|
|
230
|
+
|
|
231
|
+
**两种改法,任选其一**(改 UI 或改 YAML 都写进同一个 settings 文档,彼此同步):
|
|
232
|
+
|
|
233
|
+
1. **Web UI(1.7.0,推荐)**:设置 → dsh-speak 设置独立设置页。所有配置项都能直接改并
|
|
234
|
+
保存(`dsh --dump-config` 可见、按 profile 隔离、升级不丢)。
|
|
235
|
+
2. **profile patch 的 `config` 块**(等效):
|
|
235
236
|
|
|
236
237
|
```yaml
|
|
237
238
|
# ~/.dsh/profiles/web/cordis.patch.yml
|
|
@@ -239,18 +240,79 @@ DSH 插件配置——**优先用 profile patch 的 `config` 块**(`dsh --dump
|
|
|
239
240
|
- id: speech-hook
|
|
240
241
|
name: 'dsh-speak'
|
|
241
242
|
config:
|
|
243
|
+
enabled: true # 总开关:false 时完全不播报
|
|
244
|
+
automaticSpeech: true # 自动朗读最终回复
|
|
245
|
+
queueAllMessages: false # true = 所有 assistant 消息立即入队朗读(中间消息也读)
|
|
246
|
+
replayFullRead: false # true = 手动重播跳过超长文本截断,完整朗读
|
|
247
|
+
cleanMarkdownFormatting: true # Markdown 转自然语音
|
|
248
|
+
readInlineCode: true # 朗读行内代码(去掉反引号)
|
|
249
|
+
codeBlocks: smart # all | smart | replace(围栏代码块)
|
|
250
|
+
codeBlockMaxChars: 300 # smart 模式下的代码块字数上限
|
|
251
|
+
codeBlockReplacementText: 'You can see the code in our history.' # replace 时的替代文本
|
|
242
252
|
throttleMs: 1500 # 播报前的合并延迟(毫秒)
|
|
243
253
|
engine: '' # 引擎路径覆盖;'' = 自动解析
|
|
244
254
|
announceApprovals: true # 播报审批请求
|
|
245
255
|
announceQuestions: true # 播报 ask_user_question 提问内容
|
|
246
256
|
stripApprovalPrefix: true # 剥离审批原因里的 "escalate sandbox to ...: " 前缀
|
|
257
|
+
questionGapMs: 2000 # 多个提问播报之间的停顿(毫秒)
|
|
247
258
|
longTextMode: message # message | heading(念最大字号 markdown 标题)
|
|
248
|
-
|
|
259
|
+
longTextMessage: '本次播报内容较长,请自行阅读。' # message 模式下的固定提示语
|
|
260
|
+
maxChars: 300 # 引擎单次朗读字数上限(macOS 默认 0 = 不限)
|
|
249
261
|
volume: 50 # 仅 Windows
|
|
250
262
|
rate: 0 # 0 = 引擎默认(Windows SAPI 刻度 / macOS wpm)
|
|
263
|
+
# —— 可选事件播报(1.6.0,默认全关)——
|
|
264
|
+
announceTurnEnd: false # 回合结束("第 N 轮对话完成")
|
|
265
|
+
announceCommandDone: false # 命令完成/失败(command/done)
|
|
266
|
+
announceGoalChange: false # 目标创建/更新/完成(goal/change)
|
|
267
|
+
announceToolErrors: false # 工具调用出错时播报(英文详情截掉,tool/result)
|
|
268
|
+
announceTodoWrite: false # 待办列表更新(todo/write)
|
|
251
269
|
```
|
|
252
270
|
|
|
253
|
-
|
|
271
|
+
> 解析顺序:schema 默认值 → patch `config` → UI 用户设置。写进 YAML 的字段
|
|
272
|
+
> 同样出现在 UI 中。平台差异:`maxChars` 在 macOS 默认 0(`say` 无上限),
|
|
273
|
+
> Windows 默认 300(SAPI 安全上限)。
|
|
274
|
+
|
|
275
|
+
#### 选项说明
|
|
276
|
+
|
|
277
|
+
| 选项 | 默认值 | 效果 |
|
|
278
|
+
| ---- | ------ | ---- |
|
|
279
|
+
| `enabled` | `true` | **总开关**:关闭后所有播报都不触发(最终回复/审批/提问/可选事件/重播) |
|
|
280
|
+
| `automaticSpeech` | `true` | 自动朗读最终回复;手动重播始终可用 |
|
|
281
|
+
| `queueAllMessages` | `false` | `true` 时每条 assistant 消息立即入队朗读(中间消息也读,FIFO);默认只读节流后的最终回复 |
|
|
282
|
+
| `replayFullRead` | `false` | `true` 时手动重播跳过超长文本的标题截断(`longTextMode: heading`),完整分段朗读 |
|
|
283
|
+
| `cleanMarkdownFormatting` | `true` | 把 Markdown 转成自然语音文本(链接保留文字去 URL、标题/强调符号清理) |
|
|
284
|
+
| `readInlineCode` | `true` | 朗读行内代码(去掉反引号标记) |
|
|
285
|
+
| `codeBlocks` | `smart` | 围栏代码块处理:`all` 全读 / `smart`(≤`codeBlockMaxChars` 才读)/ `replace` 用替代文本 |
|
|
286
|
+
| `codeBlockMaxChars` | `300` | `smart` 模式下的代码块字数上限 |
|
|
287
|
+
| `codeBlockReplacementText` | `You can see the code in our history.` | `replace` 模式(或超限的 `smart`)下朗读的替代文本 |
|
|
288
|
+
| `throttleMs` | `1500` | 回复文本等待多久才播报(合并同一回复的多步消息) |
|
|
289
|
+
| `engine` | `''` | 显式引擎脚本路径;`''` 自动解析:包内 `engine/<平台>` → `~/.dsh/hooks/<平台>` |
|
|
290
|
+
| `announceApprovals` | `true` | 播报 `approval/asked` 事件(审批原因,或固定提示语) |
|
|
291
|
+
| `announceQuestions` | `true` | 播报 `ask_user_question`:每个问题单独朗读,带"问题N"序号(多问题时)与"选项N"序号(与 UI 编号一致);多个问题之间停顿 `questionGapMs` |
|
|
292
|
+
| `questionGapMs` | `2000` | 多个提问播报之间的停顿(毫秒),0 = 不停顿 |
|
|
293
|
+
| `stripApprovalPrefix` | `true` | 剥离审批原因里的固定英文模板前缀(`escalate sandbox to danger-full-access: `),保留中文说明 |
|
|
294
|
+
| `longTextMode` | `message` | `message` = 超长念固定提示语;`heading` = 改念最大字号 markdown 标题(规则见下) |
|
|
295
|
+
| `longTextMessage` | `本次播报内容较长,请自行阅读。` | `message` 模式下超长文本改念的固定提示语(UI 可编辑) |
|
|
296
|
+
| `maxChars` | 平台相关 | 引擎单次朗读上限。**macOS 默认 0(`say` 无上限);Windows 默认 300**(SAPI 超过约 375-470 字会静默失败) |
|
|
297
|
+
| `volume` | `50` | 仅 Windows(0-100);macOS 音量跟随系统 |
|
|
298
|
+
| `rate` | `0` | `0` = 引擎默认(Windows SAPI 刻度如 1;macOS words-per-minute 如 175) |
|
|
299
|
+
| `announceTurnEnd` | `false` | 回合结束时播报"第 N 轮对话完成/中断/异常结束"(`turn/end`) |
|
|
300
|
+
| `announceCommandDone` | `false` | 命令执行完成/失败时播报(`command/done`) |
|
|
301
|
+
| `announceGoalChange` | `false` | 目标创建/更新/完成/暂停/恢复时播报(`goal/change`,含目标标题前 40 字) |
|
|
302
|
+
| `announceToolErrors` | `false` | 工具调用返回错误时播报"工具调用出错"(英文错误详情/技术 code 截掉,只保留中文详情;`tool/result` 带 `error` 或 `isError` 内容块时) |
|
|
303
|
+
| `announceTodoWrite` | `false` | agent 更新待办列表时播报"待办已更新:n/m 完成"(`todo/write`) |
|
|
304
|
+
|
|
305
|
+
#### 超长文本模式
|
|
306
|
+
|
|
307
|
+
清洗后文本超过 `maxChars` 时:
|
|
308
|
+
|
|
309
|
+
- **`message`**(默认):念 `longTextMessage`(`本次播报内容较长,请自行阅读。`,
|
|
310
|
+
可在 UI 或 YAML 里编辑)。
|
|
311
|
+
- **`heading`**:在原始文本里挑**最大字号**的 markdown 标题——`#` 数量最少者优先,
|
|
312
|
+
并列取第一个;没有标题行则取第一个非空行。选中的候选仍会清洗并受 `maxChars`
|
|
313
|
+
上限约束,若其本身仍超长则回退提示语。
|
|
314
|
+
|
|
315
|
+
完整架构与设计取舍见 [docs/DESIGN.zh-CN.md](docs/DESIGN.zh-CN.md)。
|
|
254
316
|
|
|
255
317
|
## 自定义(升级不丢)
|
|
256
318
|
|
|
@@ -304,10 +366,12 @@ engine/ 与 harness 无关的语音引擎(PowerShell + SAPI5
|
|
|
304
366
|
speech-summary.ps1 阻塞式回复总结播报
|
|
305
367
|
adapters/
|
|
306
368
|
dsh/ DSH web 插件 + 一键安装脚本
|
|
307
|
-
speech-hook.js
|
|
369
|
+
speech-hook.js 会话事件触发器(节流/取消 + 可选事件 + FIFO 语音队列 + WebSocket + settings 注册)
|
|
308
370
|
install.ps1 拷贝 + 注册 + 备份
|
|
309
371
|
claude-code/
|
|
310
372
|
stop-hook.ps1 Claude Code Stop hook 触发器
|
|
373
|
+
client/
|
|
374
|
+
client.js DSH 浏览器端 bundle:回合尾部 Speak/Stop 按钮 + 设置 → dsh-speak 设置页
|
|
311
375
|
docs/
|
|
312
376
|
DESIGN.zh-CN.md 完整设计文档:设计取舍、踩坑记录、扩展指南
|
|
313
377
|
```
|