macOSコンパニオンアプリ
音声オーバーレイ
対象読者: macOSアプリコントリビューター。目標: ウェイクワードとプッシュトゥトークが重複した際も、音声オーバーレイの動作を予測可能に保つ。
現在の意図
- オーバーレイがウェイクワードですでに表示されている状態でユーザーがホットキーを押した場合、ホットキーセッションはテキストをリセットせず、既存のテキストを引き継ぎます。オーバーレイはホットキーが押されている間表示されたままです。ユーザーがキーを離したとき: トリミングされたテキストがあれば送信、なければオーバーレイを閉じます。
- ウェイクワード単独の場合、無音時に自動送信されます。プッシュトゥトークはキーを離した瞬間に即時送信します。
実装済み (2025年12月9日)
- オーバーレイセッションは、キャプチャ(ウェイクワードまたはプッシュトゥトーク)ごとにトークンを持つようになりました。トークンが一致しない場合、部分/最終/送信/閉じる/レベル更新のコールバックは破棄され、古いコールバックによる影響を回避します。
- プッシュトゥトークは、表示されているオーバーレイのテキストを接頭辞として引き継ぎます(ウェイクオーバーレイが表示されている間にホットキーを押すと、テキストが保持され、新しい音声が追加されます)。最終トランスクリプトを最大1.5秒待ち、それがない場合は現在のテキストにフォールバックします。
- チャイム/オーバーレイのログは、カテゴリ
voicewake.overlay、voicewake.ptt、voicewake.chimeでinfoレベルで出力されます(セッション開始、部分、最終、送信、閉じる、チャイム理由)。
次のステップ
- VoiceSessionCoordinator (アクター)
- 常に1つの
VoiceSessionのみを所有します。 - API (トークンベース):
beginWakeCapture,beginPushToTalk,updatePartial,endCapture,cancel,applyCooldown。 - 古いトークンを含むコールバックを破棄します(古い認識エンジンがオーバーレイを再び開くのを防ぎます)。
- 常に1つの
- VoiceSession (モデル)
- フィールド:
token,source(wakeWord|pushToTalk), 確定/揮発性テキスト, チャイムフラグ, タイマー (自動送信, アイドル),overlayMode(表示|編集|送信中), クールダウン期限。
- フィールド:
- オーバーレイバインディング
VoiceSessionPublisher(ObservableObject) は、アクティブなセッションをSwiftUIに反映します。VoiceWakeOverlayViewはパブリッシャー経由でのみレンダリングし、グローバルシングルトンを直接変更することはありません。- オーバーレイでのユーザー操作 (
sendNow,dismiss,edit) は、セッショントークンとともにコーディネーターにコールバックします。
- 統一された送信パス
endCapture時: トリミングされたテキストが空 → 閉じる。それ以外 →performSend(session:)(送信チャイムを一度再生し、転送し、閉じます)。- プッシュトゥトーク: 遅延なし。ウェイクワード: 自動送信のためのオプションの遅延。
- プッシュトゥトーク終了後、ウェイクランタイムに短いクールダウンを適用し、ウェイクワードが即座に再トリガーされないようにします。
- ロギング
- コーディネーターは、サブシステム
ai.openclaw、カテゴリvoicewake.overlayおよびvoicewake.chimeで.infoログを出力します。 - 主要イベント:
session_started,adopted_by_push_to_talk,partial,finalized,send,dismiss,cancel,cooldown。
- コーディネーターは、サブシステム
デバッグチェックリスト
-
オーバーレイが固着するケースを再現しながらログをストリーミング:
コピー
sudo log stream --predicate 'subsystem == "ai.openclaw" AND category CONTAINS "voicewake"' --level info --style compact -
アクティブなセッショントークンが1つのみであることを確認します。古いコールバックはコーディネーターによって破棄されるべきです。
-
プッシュトゥトークのキー離しで、常にアクティブなトークンとともに
endCaptureが呼ばれることを確認します。テキストが空の場合、チャイムや送信なしでdismissされることを期待します。
移行手順 (提案)
VoiceSessionCoordinator、VoiceSession、VoiceSessionPublisherを追加します。VoiceWakeRuntimeをリファクタリングし、VoiceWakeOverlayControllerを直接操作する代わりに、セッションの作成/更新/終了を行うようにします。VoicePushToTalkをリファクタリングし、既存のセッションを引き継ぎ、キー離しでendCaptureを呼び出すようにします。ランタイムクールダウンを適用します。VoiceWakeOverlayControllerをパブリッシャーに接続します。ランタイム/PTTからの直接呼び出しを削除します。- セッション引き継ぎ、クールダウン、空テキスト時の閉じる動作の統合テストを追加します。