音声オーバーレイのライフサイクル (macOS)
対象: macOS アプリのコントリビューター。目標: ウェイクワードとプッシュトゥトークが重なったときも、音声オーバーレイを予測可能に保つ。動作
- ウェイクワードによってオーバーレイがすでに表示されていて、ユーザーがホットキーを押した場合、ホットキーセッションはテキストをリセットせず、既存のテキストを引き継ぐ。ホットキーを押している間、オーバーレイは表示されたままになる。離したとき: トリム済みテキストがあれば送信し、なければ閉じる。
- ウェイクワード単独では、無音時に引き続き自動送信される。プッシュトゥトークは離した時点ですぐに送信する。
実装
VoiceSessionCoordinator(apps/macos/Sources/OpenClaw/VoiceSessionCoordinator.swift) は、アクティブな音声セッションの唯一の所有者。これは actor ではなく、@MainActor @Observableシングルトン。API:startSession,updatePartial,finalize,sendNow,dismiss,updateLevel,snapshot。各セッションはUUIDトークンを持つ。古いトークンや一致しないトークンでの呼び出しは破棄される。VoiceWakeOverlayController(VoiceWakeOverlayController+Session.swift) はオーバーレイを描画し、ユーザー操作 (requestSend,dismiss) をセッショントークン経由でコーディネーターへ戻す。セッション状態自体は所有しない。- プッシュトゥトーク (
VoicePushToTalk.begin()) は、表示中のオーバーレイテキストをadoptedPrefixとして引き継ぐ (VoiceSessionCoordinator.shared.snapshot()経由)。これにより、ウェイクオーバーレイが表示されている間にホットキーを押しても、テキストを保持し、新しい発話を追加できる。離したとき、最終トランスクリプトを最大 1.5 秒待ってから、現在のテキストへフォールバックする。 dismiss時、オーバーレイはVoiceSessionCoordinator.overlayDidDismissを呼び出す。これによりVoiceWakeRuntime.refresh(state:)がトリガーされ、手動の X 閉じ、空テキストでの閉じ、送信後の閉じのいずれでも、ウェイクワードのリスニングが再開される。- 統一された送信パス: トリム済みテキストが空なら閉じる。そうでなければ、
sendNowが送信チャイムを一度再生し、VoiceWakeForwarder経由で転送してから閉じる。
ログ
音声サブシステムはai.openclaw。各コンポーネントはそれぞれのカテゴリでログを記録する。
| カテゴリ | コンポーネント |
|---|---|
voicewake.coordinator | VoiceSessionCoordinator |
voicewake.overlay | VoiceWakeOverlayController/VoiceWakeOverlay |
voicewake.ptt | プッシュトゥトークのホットキーとキャプチャ |
voicewake.runtime | ウェイクワードランタイム |
voicewake.chime | チャイム再生 |
voicewake.sync | グローバル設定同期 |
voicewake.forward | トランスクリプト転送 |
voicewake.meter | マイクレベルモニター |
デバッグチェックリスト
-
固着したオーバーレイを再現しながらログをストリームする:
- アクティブなセッショントークンが 1 つだけであることを確認する。古いコールバックはコーディネーターによって破棄される。
-
プッシュトゥトークを離したとき、アクティブなトークンで必ず
end()が呼ばれることを確認する。テキストが空の場合は、チャイムや送信なしで閉じることを想定する。