dsh-speak 1.2.0 → 1.4.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 CHANGED
@@ -4,6 +4,8 @@
4
4
 
5
5
  ![鲸鱼娘大喇叭](鲸鱼娘大喇叭.png)
6
6
 
7
+ [![Awesome DSH Plugin](https://awesome-dsh-plugin.com/badge.svg)](https://awesome-dsh-plugin.com)
8
+
7
9
  Let your agent **tell you** when a long task is done — no more staring at the screen.
8
10
 
9
11
  dsh-speak reads the final assistant reply aloud through system speech synthesis —
@@ -245,6 +247,33 @@ DSH plugin environment variables:
245
247
  | `DSH_SPEAK_ENGINE` | empty (auto-resolved) | engine path override; otherwise resolved as `<package>/engine/<platform script>` → `~/.dsh/hooks/<platform script>` (Windows `speak.ps1` / macOS `speak.sh`) |
246
248
  | `DSH_SPEAK_THROTTLE_MS` | `1500` | merge delay before announcing |
247
249
 
250
+ ## Customizing (survives npm updates)
251
+
252
+ You can tune behavior without forking, and your changes **survive `npm update`**:
253
+
254
+ 1. **Copy the engine out and edit it** (recommended — this is where defaults live: volume,
255
+ rate, `MaxChars`, `LongTextMessage`, voice logic):
256
+
257
+ ```powershell
258
+ # Windows
259
+ Copy-Item "$env:USERPROFILE\.dsh\profiles\web\node_modules\dsh-speak\engine\speak.ps1" "$env:USERPROFILE\.dsh\hooks\my-speak.ps1"
260
+ # macOS
261
+ 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
+ ```
266
+
267
+ The plugin resolves the engine as `DSH_SPEAK_ENGINE` → package engine → `~/.dsh/hooks/`,
268
+ so your copy wins. `npm update` only touches the package — your engine stays.
269
+
270
+ 2. **Environment variables** (no code changes): `DSH_SPEAK_ENGINE` (engine path),
271
+ `DSH_SPEAK_THROTTLE_MS` (announcement merge delay).
272
+
273
+ 3. **Edit the file inside `node_modules`** — works, but the next `npm update` overwrites it.
274
+
275
+ 4. **Fork the repo** — full control, publish your own package if you want.
276
+
248
277
  ## Troubleshooting
249
278
 
250
279
  | symptom | cause | fix |
package/README.zh-CN.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  ![鲸鱼娘大喇叭](鲸鱼娘大喇叭.png)
4
4
 
5
+ [![Awesome DSH Plugin](https://awesome-dsh-plugin.com/badge.svg)](https://awesome-dsh-plugin.com)
6
+
5
7
  让 Agent 在长任务完成时**开口告诉你**——不用再盯着屏幕等。
6
8
 
7
9
  dsh-speak 通过系统语音合成把 Agent 的最终回复朗读出来——Windows 上优先使用自然
@@ -230,6 +232,31 @@ DSH 插件环境变量:
230
232
  | `DSH_SPEAK_ENGINE` | 空(自动解析) | 引擎路径覆盖;否则按"包内 `engine/<平台脚本>` → `~/.dsh/hooks/<平台脚本>`"顺序解析(Windows `speak.ps1` / macOS `speak.sh`) |
231
233
  | `DSH_SPEAK_THROTTLE_MS` | `1500` | 播报前的合并延迟(毫秒) |
232
234
 
235
+ ## 自定义(升级不丢)
236
+
237
+ 想调行为又不想 fork,而且改完**不会被 `npm update` 覆盖**:
238
+
239
+ 1. **把引擎复制出来改**(推荐——默认参数都在这:音量、语速、字数上限、超长提示语、音色逻辑):
240
+
241
+ ```powershell
242
+ # Windows
243
+ Copy-Item "$env:USERPROFILE\.dsh\profiles\web\node_modules\dsh-speak\engine\speak.ps1" "$env:USERPROFILE\.dsh\hooks\my-speak.ps1"
244
+ # macOS
245
+ cp ~/.dsh/profiles/web/node_modules/dsh-speak/engine/speak.sh ~/.dsh/hooks/my-speak.sh
246
+ # 按喜好编辑 my-speak.ps1 / my-speak.sh,然后让插件指向它:
247
+ setx DSH_SPEAK_ENGINE "$env:USERPROFILE\.dsh\hooks\my-speak.ps1" # Windows
248
+ echo 'export DSH_SPEAK_ENGINE=~/.dsh/hooks/my-speak.sh' >> ~/.zshrc # macOS
249
+ ```
250
+
251
+ 插件按 `DSH_SPEAK_ENGINE` → 包内引擎 → `~/.dsh/hooks/` 的顺序解析引擎,所以你的副本
252
+ 优先生效;`npm update` 只动包本身,你的引擎安然无恙。
253
+
254
+ 2. **环境变量**(零改代码):`DSH_SPEAK_ENGINE`(引擎路径)、`DSH_SPEAK_THROTTLE_MS`(播报合并延迟)。
255
+
256
+ 3. **直接改 `node_modules` 里的文件**——能改,但下次 `npm update` 会被覆盖。
257
+
258
+ 4. **fork 仓库**——完全掌控,想发自己的包也行。
259
+
233
260
  ## 排障
234
261
 
235
262
  | 现象 | 原因 | 解决 |
@@ -9,7 +9,11 @@
9
9
  // * only events with a `text` block are announced (reasoning / tool_use blocks
10
10
  // are skipped)
11
11
  // * when a tool/call event arrives, that round's assistant text is treated as
12
- // process narration, so any pending announcement is cancelled
12
+ // process narration, so any pending announcement is cancelled — EXCEPT a
13
+ // call to `ask_user_question`, which is a question for the user and keeps
14
+ // the pending text so it is announced
15
+ // * `approval/asked` is announced immediately (approval reason, or a fixed
16
+ // prompt) since approvals are time-sensitive
13
17
  // * a final reply with no following tool/call is announced after a throttle
14
18
  // delay (merges multi-step messages from the same reply)
15
19
  //
@@ -101,12 +105,29 @@ module.exports = {
101
105
  if (type !== 'assistant/chunk') {
102
106
  log('事件 type=', type, 'surfaceOp=', event && event.surfaceOp, 'seq=', event && event.seq)
103
107
  }
104
- // tool-call round: cancel pending announcement (that round's assistant
105
- // text is process narration, not the final reply)
108
+ // tool-call round: a call to ask_user_question is a question to the
109
+ // user — keep the pending text so it gets announced (the user should
110
+ // hear the question); any other tool call cancels the pending
111
+ // announcement (that round's assistant text is process narration)
106
112
  if (type === 'tool/call') {
113
+ const toolName = event.data && event.data.name
114
+ if (toolName === 'ask_user_question') {
115
+ log('提问工具调用(ask_user_question)— 保留待播报文本')
116
+ return
117
+ }
107
118
  cancelPending()
108
119
  return
109
120
  }
121
+ // approval requested: announce it right away (time-sensitive), using
122
+ // the approval reason if present
123
+ if (type === 'approval/asked') {
124
+ cancelPending()
125
+ const reason = event.data && event.data.reason
126
+ const text = reason && reason.trim() ? reason : '需要你的审批,请查看界面。'
127
+ log('审批请求,播报:', text.slice(0, 60))
128
+ speak(text)
129
+ return
130
+ }
110
131
  if (!event || type !== 'assistant/message') return
111
132
  if (event.surfaceOp && event.surfaceOp !== 'append') return
112
133
  // the message object lives at event.data.message (event.data wraps { turn, step, message })
@@ -0,0 +1,5 @@
1
+ # dsh-speak bundle patch: auto-register the speech-hook plugin when this package
2
+ # is used as a DSH bundle (declared in dsh.profile.bundles).
3
+ - insert:
4
+ - id: speech-hook
5
+ name: dsh-speak
package/docs/DESIGN.md CHANGED
@@ -147,12 +147,14 @@ returns immediately. (Async spawning is safe here — the nested-spawn restricti
147
147
 
148
148
  ## 4. Event-flow truth table (DSH)
149
149
 
150
- | assistant round contains | announced? |
151
- | ------------------------------- | ----------- |
152
- | final text reply, no tool call | ✅ after throttle |
153
- | text + tool/call(s) | ❌ (cancelled — narration) |
154
- | reasoning only, no text | (no text block) |
155
- | streaming chunks | (filtered) |
150
+ | assistant round / event | announced? |
151
+ | -------------------------------- | ----------- |
152
+ | final text reply, no tool call | ✅ after throttle |
153
+ | text + tool/call(s) | ❌ (cancelled — narration) |
154
+ | text + `ask_user_question` call | the question text is kept and announced |
155
+ | `approval/asked` | immediately (reason, else a fixed prompt) |
156
+ | reasoning only, no text | ❌ (no text block) |
157
+ | streaming chunks | ❌ (filtered) |
156
158
 
157
159
  ## 5. Configuration reference
158
160
 
@@ -134,12 +134,14 @@ Claude Code *确实*有 Stop hook。hook JSON(含 `transcript_path`)从 stdi
134
134
 
135
135
  ## 4. 事件流真值表(DSH)
136
136
 
137
- | assistant 轮次包含 | 是否播报 |
138
- | --------------------------------- | -------- |
139
- | 最终文本回复,无工具调用 | ✅ 节流后播报 |
140
- | 文本 + tool/call(s) | ❌(取消——旁白) |
141
- | 只有 reasoning,无文本 | ❌(无 text 块) |
142
- | 流式分块 | ❌(被过滤) |
137
+ | assistant 轮次 / 事件 | 是否播报 |
138
+ | -------------------------------- | -------- |
139
+ | 最终文本回复,无工具调用 | ✅ 节流后播报 |
140
+ | 文本 + tool/call(s) | ❌(取消——旁白) |
141
+ | 文本 + `ask_user_question` 调用 | 保留提问文本并播报 |
142
+ | `approval/asked`(审批请求) | 立即播报(审批原因,否则固定提示语) |
143
+ | 只有 reasoning,无文本 | ❌(无 text 块) |
144
+ | 流式分块 | ❌(被过滤) |
143
145
 
144
146
  ## 5. 配置参考
145
147
 
package/package.json CHANGED
@@ -1,17 +1,23 @@
1
1
  {
2
2
  "name": "dsh-speak",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
4
4
  "description": "Make your AI harness speak — verified voice announcements for DSH and other AI coding harnesses (Windows SAPI5 + macOS system voices)",
5
5
  "main": "adapters/dsh/speech-hook.js",
6
6
  "files": [
7
7
  "adapters/dsh/speech-hook.js",
8
8
  "adapters/dsh/install.ps1",
9
+ "cordis.patch.yml",
9
10
  "engine/",
10
11
  "docs/",
11
12
  "README.md",
12
13
  "README.zh-CN.md",
13
14
  "LICENSE"
14
15
  ],
16
+ "dsh": {
17
+ "bundle": {
18
+ "patch": "./cordis.patch.yml"
19
+ }
20
+ },
15
21
  "keywords": [
16
22
  "dsh",
17
23
  "dsh-plugin",