Appearance
语音覆盖层生命周期(macOS)
受众:macOS 应用贡献者。目标:在唤醒词和按键说话(Push-to-Talk)重叠时,保持语音覆盖层行为可预测。
当前设计意图
- 如果覆盖层已因唤醒词而可见,此时用户按下热键,热键会话将采用现有文本而非重置。覆盖层在热键按住期间保持显示。用户松开时:若有有效文本则发送,否则关闭。
- 仅唤醒词触发时仍在静默后自动发送;按键说话在松开后立即发送。
已实现(2025 年 12 月 9 日)
- 覆盖层会话现在为每次捕获(唤醒词或按键说话)携带独立 token。当 token 不匹配时,partial/final/send/dismiss/level 更新会被丢弃,避免过期回调的干扰。
- 按键说话会采用任何可见的覆盖层文本作为前缀(在唤醒覆盖层显示时按热键会保留文本并追加新语音)。它最多等待 1.5 秒以获得最终转录,之后回退到当前文本。
- 提示音/覆盖层日志以
info级别在voicewake.overlay、voicewake.ptt和voicewake.chime分类下输出(会话开始、partial、final、发送、关闭、提示音原因)。
下一步计划
- VoiceSessionCoordinator(actor)
- 同一时刻只持有一个
VoiceSession。 - API(基于 token):
beginWakeCapture、beginPushToTalk、updatePartial、endCapture、cancel、applyCooldown。 - 丢弃携带过期 token 的回调(防止旧识别器重新打开覆盖层)。
- 同一时刻只持有一个
- VoiceSession(模型)
- 字段:
token、source(wakeWord|pushToTalk)、已提交/可变文本、提示音标志、定时器(自动发送、空闲)、overlayMode(display|editing|sending)、冷却截止时间。
- 字段:
- 覆盖层绑定
VoiceSessionPublisher(ObservableObject)将活动会话镜像到 SwiftUI。VoiceWakeOverlayView仅通过 publisher 渲染,不直接修改全局单例。- 覆盖层用户操作(
sendNow、dismiss、edit)通过会话 token 回调到 coordinator。
- 统一发送路径
endCapture时:若有效文本为空 → 关闭;否则执行performSend(session:)(播放一次发送提示音,转发,关闭)。- 按键说话:无延迟;唤醒词:可选延迟自动发送。
- 按键说话完成后对唤醒运行时应用短暂冷却,防止唤醒词立即重新触发。
- 日志
- Coordinator 在子系统
ai.openclaw、分类voicewake.overlay和voicewake.chime下输出.info日志。 - 关键事件:
session_started、adopted_by_push_to_talk、partial、finalized、send、dismiss、cancel、cooldown。
- Coordinator 在子系统
调试清单
复现覆盖层卡死时,实时流式查看日志:
bash
sudo log stream --predicate 'subsystem == "ai.openclaw" AND category CONTAINS "voicewake"' --level info --style compact- 验证只有一个活动会话 token;过期回调应被 coordinator 丢弃。
- 确保按键说话松开时始终以活动 token 调用
endCapture;若文本为空,预期行为是dismiss(无提示音,不发送)。
迁移步骤(建议)
- 添加
VoiceSessionCoordinator、VoiceSession和VoiceSessionPublisher。 - 重构
VoiceWakeRuntime,改为创建/更新/结束会话,而非直接操作VoiceWakeOverlayController。 - 重构
VoicePushToTalk,采用现有会话并在松开时调用endCapture;对运行时应用冷却。 - 将
VoiceWakeOverlayController接入 publisher,移除来自运行时/PTT 的直接调用。 - 添加会话采用、冷却和空文本关闭的集成测试。