dsh-speak 1.4.0 → 1.7.0
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 +166 -72
- package/README.zh-CN.md +160 -71
- package/adapters/dsh/speech-hook.js +498 -102
- package/client/client.js +304 -0
- package/docs/DESIGN.md +122 -30
- package/docs/DESIGN.zh-CN.md +108 -25
- package/engine/speak.ps1 +88 -14
- package/engine/speak.sh +66 -52
- package/package.json +36 -3
package/README.md
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
|
|
7
7
|
[](https://awesome-dsh-plugin.com)
|
|
8
8
|
|
|
9
|
+
[](https://www.npmjs.com/package/dsh-speak)
|
|
10
|
+
|
|
9
11
|
Let your agent **tell you** when a long task is done — no more staring at the screen.
|
|
10
12
|
|
|
11
13
|
dsh-speak reads the final assistant reply aloud through system speech synthesis —
|
|
@@ -15,53 +17,30 @@ on macOS using the built-in `say` (can follow a Siri natural voice). It was buil
|
|
|
15
17
|
for [DeepSeek Harness](https://github.com/deepseek-ai/dsh)
|
|
16
18
|
and is structured so any harness can plug in.
|
|
17
19
|
|
|
18
|
-
> **Project status**: this project exists only to provide an **already-verified
|
|
19
|
-
> solution** for users who want their harness to speak. Barring unexpected
|
|
20
|
-
> circumstances, it will not be updated further.
|
|
21
|
-
|
|
22
|
-
## TL;DR — install for DSH
|
|
23
|
-
|
|
24
|
-
1. Install the package into your web profile (pick one):
|
|
25
|
-
|
|
26
|
-
```powershell
|
|
27
|
-
dsh plugin --profile web add dsh-speak
|
|
28
|
-
# or, without pnpm:
|
|
29
|
-
npm install --prefix "$env:USERPROFILE\.dsh\profiles\web" dsh-speak
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
On macOS (bash):
|
|
33
|
-
|
|
34
|
-
```bash
|
|
35
|
-
npm install --prefix "$HOME/.dsh/profiles/web" dsh-speak
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
2. Append to `~/.dsh/profiles/web/cordis.patch.yml`:
|
|
39
|
-
|
|
40
|
-
```yaml
|
|
41
|
-
- insert:
|
|
42
|
-
- id: speech-hook
|
|
43
|
-
name: 'dsh-speak'
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
3. Restart the DSH web app — replies are now announced aloud.
|
|
47
|
-
|
|
48
|
-
> **Let your agent do it?** Paste this repo URL
|
|
49
|
-
> (`https://github.com/Alan2Z/dsh-speak`) into your DSH session and ask it to
|
|
50
|
-
> install the plugin — your agent follows this very README. Approving the
|
|
51
|
-
> out-of-workspace writes (`~/.dsh`) is all that's needed.
|
|
52
|
-
|
|
53
|
-
```
|
|
54
|
-
harness event (DSH session event / Claude Code Stop hook / anything)
|
|
55
|
-
│
|
|
56
|
-
▼ adapters/… (harness-specific trigger: filter, throttle, cancel)
|
|
57
|
-
▼ engine/speak.ps1 / speak.sh (harness-agnostic: clean text → SAPI5 / say)
|
|
58
|
-
▼ 🔊 you hear the final reply
|
|
59
|
-
```
|
|
60
|
-
|
|
61
20
|
## Features
|
|
62
21
|
|
|
63
22
|
- **Automatic**: DSH web plugin watches the session event stream and announces the
|
|
64
23
|
final reply (skips reasoning/tool-call narration, merges multi-step messages).
|
|
24
|
+
- **Gets your attention**: announces approval requests (hears "需要你的审批" when
|
|
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.
|
|
41
|
+
- **Bundle auto-registration** (1.3.0): declare the package in `dsh.profile.bundles`
|
|
42
|
+
and the plugin registers itself via the bundled `cordis.patch.yml` — no manual
|
|
43
|
+
patch entry needed.
|
|
65
44
|
- **Best-effort**: never throws, never blocks the harness, never breaks a session.
|
|
66
45
|
- **Natural voices**: Windows prefers natural voices — Windows 11 built-in packs,
|
|
67
46
|
or voices registered via NaturalVoiceSAPIAdapter on Windows 10 (e.g. Xiaoxiao);
|
|
@@ -72,6 +51,20 @@ harness event (DSH session event / Claude Code Stop hook / anything)
|
|
|
72
51
|
- **Portable engine**: any process can speak with one line:
|
|
73
52
|
Windows `powershell -File speak.ps1 -Text "你好"` / macOS `./speak.sh -t "你好"`.
|
|
74
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
|
+
|
|
75
68
|
## Prerequisites
|
|
76
69
|
|
|
77
70
|
Windows:
|
|
@@ -93,9 +86,9 @@ macOS:
|
|
|
93
86
|
- Chinese voices: see the [macOS](#macos) section (incl. the Siri natural-voice
|
|
94
87
|
picker and its pitfalls).
|
|
95
88
|
|
|
96
|
-
##
|
|
89
|
+
## Install & quick start
|
|
97
90
|
|
|
98
|
-
### Option A
|
|
91
|
+
### DSH — Option A: npm plugin (recommended)
|
|
99
92
|
|
|
100
93
|
```powershell
|
|
101
94
|
# 1. install the plugin into your web profile (adds dsh-speak to
|
|
@@ -117,11 +110,22 @@ dsh plugin --profile web add dsh-speak
|
|
|
117
110
|
> ```powershell
|
|
118
111
|
> npm install --prefix "$env:USERPROFILE\.dsh\profiles\web" dsh-speak
|
|
119
112
|
> ```
|
|
113
|
+
>
|
|
114
|
+
> On macOS (bash):
|
|
115
|
+
>
|
|
116
|
+
> ```bash
|
|
117
|
+
> npm install --prefix "$HOME/.dsh/profiles/web" dsh-speak
|
|
118
|
+
> ```
|
|
120
119
|
|
|
121
120
|
The engine ships inside the package (`node_modules/dsh-speak/engine/`), so no extra
|
|
122
121
|
copying is needed.
|
|
123
122
|
|
|
124
|
-
|
|
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)
|
|
125
129
|
|
|
126
130
|
```powershell
|
|
127
131
|
# 1. clone
|
|
@@ -145,14 +149,12 @@ What the file installer did:
|
|
|
145
149
|
| `adapters/dsh/speech-hook.js` | `%USERPROFILE%\.dsh\profiles\web\plugins\` |
|
|
146
150
|
| registration entry | appended to `%USERPROFILE%\.dsh\profiles\web\cordis.patch.yml` (backed up first) |
|
|
147
151
|
|
|
148
|
-
|
|
152
|
+
### macOS
|
|
149
153
|
|
|
150
154
|
The same adapter runs natively on macOS — the plugin auto-detects the platform and
|
|
151
155
|
calls `engine/speak.sh` (the built-in `say` command) instead of `speak.ps1`.
|
|
152
156
|
**Since 1.2.0 the macOS engine ships in the npm package** — no extra software.
|
|
153
157
|
|
|
154
|
-
### Install (npm — same as Windows)
|
|
155
|
-
|
|
156
158
|
```bash
|
|
157
159
|
# 1. install into your web profile (no pnpm needed — only `dsh plugin` requires it)
|
|
158
160
|
npm install --prefix "$HOME/.dsh/profiles/web" dsh-speak
|
|
@@ -162,13 +164,13 @@ npm install --prefix "$HOME/.dsh/profiles/web" dsh-speak
|
|
|
162
164
|
# - id: speech-hook
|
|
163
165
|
# name: 'dsh-speak'
|
|
164
166
|
|
|
165
|
-
# 3. no restart needed — the patch watcher hot-reloads;
|
|
166
|
-
#
|
|
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
|
|
167
169
|
```
|
|
168
170
|
|
|
169
171
|
> With pnpm installed, `dsh plugin --profile web add dsh-speak` works identically.
|
|
170
172
|
|
|
171
|
-
|
|
173
|
+
#### Voices (important — two pitfalls)
|
|
172
174
|
|
|
173
175
|
- By default the engine follows the **system reading voice** (*Settings →
|
|
174
176
|
Accessibility → Spoken Content → System Voice*). On **macOS 26** that picker has
|
|
@@ -187,7 +189,7 @@ npm install --prefix "$HOME/.dsh/profiles/web" dsh-speak
|
|
|
187
189
|
- Use `-v Eddy|Flo|Tingting` to force a specific voice (`say -v '?'` lists them).
|
|
188
190
|
- `say` has no volume flag — volume follows the system output volume.
|
|
189
191
|
|
|
190
|
-
|
|
192
|
+
#### Test the engine alone (no DSH needed)
|
|
191
193
|
|
|
192
194
|
```bash
|
|
193
195
|
curl -sfL -o ~/speak.sh "https://cdn.jsdelivr.net/gh/Alan2Z/dsh-speak@main/engine/speak.sh"
|
|
@@ -196,7 +198,7 @@ chmod +x ~/speak.sh
|
|
|
196
198
|
~/speak.sh -t "测试" -v Eddy -r 200 # explicit voice + rate
|
|
197
199
|
```
|
|
198
200
|
|
|
199
|
-
|
|
201
|
+
### Claude Code
|
|
200
202
|
|
|
201
203
|
Register the Stop hook in `~/.claude/settings.json`:
|
|
202
204
|
|
|
@@ -217,7 +219,7 @@ Register the Stop hook in `~/.claude/settings.json`:
|
|
|
217
219
|
}
|
|
218
220
|
```
|
|
219
221
|
|
|
220
|
-
|
|
222
|
+
### Any other harness
|
|
221
223
|
|
|
222
224
|
Call the engine directly from your agent / wrapper / script:
|
|
223
225
|
|
|
@@ -225,7 +227,7 @@ Call the engine directly from your agent / wrapper / script:
|
|
|
225
227
|
# announce a one-liner
|
|
226
228
|
powershell -NoProfile -ExecutionPolicy Bypass -File engine\speak.ps1 -Text "构建完成"
|
|
227
229
|
|
|
228
|
-
# announce a long summary (
|
|
230
|
+
# announce a long summary (blocking, returns when done)
|
|
229
231
|
powershell -NoProfile -ExecutionPolicy Bypass -File engine\speech-summary.ps1 -Text "…"
|
|
230
232
|
|
|
231
233
|
# ask for user attention (blocking, for prompts/approvals)
|
|
@@ -234,18 +236,104 @@ powershell -NoProfile -ExecutionPolicy Bypass -File engine\speech-prompt.ps1 -Te
|
|
|
234
236
|
|
|
235
237
|
## Configuration
|
|
236
238
|
|
|
237
|
-
Engine parameters
|
|
239
|
+
### Engine parameters
|
|
240
|
+
|
|
241
|
+
See [docs/DESIGN.md §5 configuration reference](docs/DESIGN.md#5-configuration-reference):
|
|
238
242
|
|
|
239
243
|
```powershell
|
|
240
244
|
speak.ps1 -Text "…" -Volume 50 -Rate 1 -MaxChars 300 -LongTextMessage "本次播报内容较长,请自行阅读。"
|
|
241
245
|
```
|
|
242
246
|
|
|
243
|
-
DSH plugin
|
|
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):
|
|
256
|
+
|
|
257
|
+
```yaml
|
|
258
|
+
# ~/.dsh/profiles/web/cordis.patch.yml
|
|
259
|
+
- insert:
|
|
260
|
+
- id: speech-hook
|
|
261
|
+
name: 'dsh-speak'
|
|
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
|
|
272
|
+
throttleMs: 1500 # merge delay before announcing (ms)
|
|
273
|
+
engine: '' # engine path override; '' = auto-resolve
|
|
274
|
+
announceApprovals: true # speak approval requests
|
|
275
|
+
announceQuestions: true # speak ask_user_question content
|
|
276
|
+
stripApprovalPrefix: true # strip "escalate sandbox to ...: " prefix
|
|
277
|
+
questionGapMs: 2000 # pause between multiple question announcements (ms)
|
|
278
|
+
longTextMode: message # message | heading (speak largest md heading)
|
|
279
|
+
longTextMessage: '本次播报内容较长,请自行阅读。' # fixed prompt for message mode
|
|
280
|
+
maxChars: 300 # per-utterance ceiling (macOS default 0 = unlimited)
|
|
281
|
+
volume: 50 # Windows only
|
|
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
|
|
289
|
+
```
|
|
244
290
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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).
|
|
249
337
|
|
|
250
338
|
## Customizing (survives npm updates)
|
|
251
339
|
|
|
@@ -259,20 +347,24 @@ You can tune behavior without forking, and your changes **survive `npm update`**
|
|
|
259
347
|
Copy-Item "$env:USERPROFILE\.dsh\profiles\web\node_modules\dsh-speak\engine\speak.ps1" "$env:USERPROFILE\.dsh\hooks\my-speak.ps1"
|
|
260
348
|
# macOS
|
|
261
349
|
cp ~/.dsh/profiles/web/node_modules/dsh-speak/engine/speak.sh ~/.dsh/hooks/my-speak.sh
|
|
262
|
-
# edit my-speak.ps1 / my-speak.sh to taste, then point the plugin at it:
|
|
263
|
-
setx DSH_SPEAK_ENGINE "$env:USERPROFILE\.dsh\hooks\my-speak.ps1" # Windows
|
|
264
|
-
echo 'export DSH_SPEAK_ENGINE=~/.dsh/hooks/my-speak.sh' >> ~/.zshrc # macOS
|
|
265
350
|
```
|
|
266
351
|
|
|
267
|
-
|
|
268
|
-
so your copy wins. `npm update` only touches the package — your engine stays.
|
|
352
|
+
Then point the plugin at your copy in the `config` block:
|
|
269
353
|
|
|
270
|
-
|
|
271
|
-
|
|
354
|
+
```yaml
|
|
355
|
+
- insert:
|
|
356
|
+
- id: speech-hook
|
|
357
|
+
name: 'dsh-speak'
|
|
358
|
+
config:
|
|
359
|
+
engine: 'C:/Users/<you>/.dsh/hooks/my-speak.ps1' # or ~/.dsh/hooks/my-speak.sh on macOS
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
The plugin resolves the engine as `config.engine` → package engine → `~/.dsh/hooks/`,
|
|
363
|
+
so your copy wins. `npm update` only touches the package — your engine stays.
|
|
272
364
|
|
|
273
|
-
|
|
365
|
+
2. **Edit the file inside `node_modules`** — works, but the next `npm update` overwrites it.
|
|
274
366
|
|
|
275
|
-
|
|
367
|
+
3. **Fork the repo** — full control, publish your own package if you want.
|
|
276
368
|
|
|
277
369
|
## Troubleshooting
|
|
278
370
|
|
|
@@ -296,10 +388,12 @@ engine/ harness-agnostic speech engine (PowerShell + SAPI5 / ba
|
|
|
296
388
|
speech-summary.ps1 blocking reply-summary announcement
|
|
297
389
|
adapters/
|
|
298
390
|
dsh/ DSH web plugin + one-command installer
|
|
299
|
-
speech-hook.js session-event trigger (throttle +
|
|
391
|
+
speech-hook.js session-event trigger (throttle/cancel + optional events + FIFO speech queue + WebSocket + settings registration)
|
|
300
392
|
install.ps1 copies + registers + backs up
|
|
301
393
|
claude-code/
|
|
302
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
|
|
303
397
|
docs/
|
|
304
398
|
DESIGN.md full design rationale, pitfalls, extension guide
|
|
305
399
|
```
|