Skip to content

Add streaming TTS daemon mode (intercept Claude output via hooks)#1

Open
yncat wants to merge 1 commit into
mainfrom
feature/streaming-tts-daemon
Open

Add streaming TTS daemon mode (intercept Claude output via hooks)#1
yncat wants to merge 1 commit into
mainfrom
feature/streaming-tts-daemon

Conversation

@yncat

@yncat yncat commented Jun 9, 2026

Copy link
Copy Markdown
Owner

背景・目的

現状の notifier は Notification/Stop フックで「待機中」「停止」を読み上げるワンショット exe。しかし Claude がターミナルでスピナー/ストリーミング表示を再描画するたびにスクリーンリーダーが不要なものまで読んでしまう問題があった。

claude -p(ヘッドレス)はセッション継続不可・課金別の懸念があるため、インタラクティブセッションのまま Claude の出力を横取りして SAPI に流す方式にする。

調査(2026-06、公式 docs 確認)で以下が利用できることを確認:

  • MessageDisplay フック: 応答テキストと中間テキストを delta でストリーミング供給(turn_id/message_id/index/final
  • PreToolUse: tool_name/tool_input(コマンド・ファイルパス等)
  • type:"http" フック: イベント JSON をローカル HTTP サーバーに POST(プロセス起動ゼロ)

アプローチ

ワンショット exe を常駐デーモン + HTTP フックに拡張。

Claude Code (WSL) --HTTP POST--> 常駐デーモン (Windows, 127.0.0.1:8765)
                                   ├─ hook_event_name で分岐
                                   ├─ テキスト整形
                                   └─ 非同期発話キュー (ISpVoice) ─ turn_id 変化でフラッシュ
フック 読み上げ
MessageDisplay 応答+中間テキスト(delta
PreToolUse 「ファイルを読みます」「コマンド実行…」
PostToolUse ツール完了(既定オフ)
Notification / Stop 待機・許可・停止
UserPromptSubmit 読み上げなし(バージイン用フラッシュ)

主な変更

新規: src/speech_queue.*(専用ワーカースレッド+非同期SAPI+バージイン)、src/daemon.*(cpp-httplib)、src/event_dispatch.*src/text_shaper.*src/config.*src/httplib.h(vendoring, MIT)

改修: src/main.cpp--daemon/--ensure-daemon/従来ワンショット)、src/tts_speaker.cpp(UTF-8 変換バグ修正)、src/json_parser.*Makefile/utf-8 + ws2_32.lib)、README(en/ja)・.claude.md

設計上の決定(ユーザー確認済み)

  • 常駐デーモン + HTTP フック / 画面表示は非改変(displayContent 不使用)/ 新ターン・新入力でフラッシュ
  • 各イベントは settings.json と config.json で個別オン/オフ

検証方法(要 Windows)

  1. nmake all
  2. claudecode-notifier.exe --daemoncurl http://127.0.0.1:8765/healthok
  3. POST /hookMessageDisplay を投げて発話確認、別 turn_id でフラッシュ確認
  4. settings.json を設定して実セッション確認

注意点

  • WSL ネットワーキング: HTTP は WSL→Windows のため WSL2 ミラーモード推奨。NAT モードなら config.jsonhost0.0.0.0 にしてフック URL を Windows ホスト IP に(README に手順)。
  • ⚠️ ビルド・実機の発話テストは未実施(開発は WSL/Linux 上のため)。Windows でのビルド確認が必要。
  • Stop は MessageDisplay と重複しがち → 邪魔なら speak_stop: false

🤖 Generated with Claude Code

Extend the one-shot notifier into a resident daemon that speaks Claude's
actual output during an interactive session, instead of relying on the
terminal (which a screen reader reads noisily as the spinner/stream redraws).

- speech_queue: dedicated worker thread owning one ISpVoice, async speak
  (SPF_ASYNC), barge-in flush (SPF_PURGEBEFORESPEAK), UTF-8 -> UTF-16 fix
- daemon: cpp-httplib server, POST /hook + GET /health, single-instance bind
- event_dispatch: MessageDisplay (response + intermediate delta), PreToolUse
  (tool/command/file announcements), PostToolUse, Notification/Stop,
  UserPromptSubmit (flush); turn_id change also flushes
- text_shaper: markdown/code-fence cleanup for speech
- config: %APPDATA% config.json toggles (per-event, port, rate, markdown)
- main: --daemon / --ensure-daemon (detached launch) / legacy stdin one-shot
- tts_speaker: fix UTF-8 conversion so Japanese is spoken correctly
- Makefile: /utf-8 + ws2_32.lib + new objects; vendored src/httplib.h
- README (en/ja) + .claude.md: daemon mode, HTTP hooks, WSL networking

Hooks are wired with type:"http" so no process is spawned per event,
handling the high-frequency MessageDisplay stream smoothly. Each event is
individually on/off via settings.json and config.json.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant