Open-Less#798 给 end_session 加了「转写 vs 取消」的 select! 赛跑,但取消信号本身
送不进来:hotkey bridge 线程为修 Open-Less#468/Open-Less#475 的 latch 竞态改成了串行
block_on,Hold 松手后 end_session 在 bridge 线程上同步跑完整段转写 +
润色,期间 Esc 的 HotkeyEvent::Cancelled 只能在同一条 channel 里排队。
流程跑完后才被取出,此时 phase 已回 Idle,cancel 变 no-op —— 用户按
Esc 毫无反应,必须干等转写完成。听写键为修饰键(默认 fn 等)时必现;
组合键用户不受影响(按键事件走 ComboHotkeyMonitor 的独立线程),这也是
Open-Less#798 验证时没暴露的原因。
修法:把 Cancelled 从 HotkeyEvent 枚举拆出,Esc 在 tap/hook 回调里改发
独立的 Sender<()>,由专用 esc-cancel-bridge 线程消费并调 cancel_session
(纯同步快路径,不 await)。cancelled 旗标即时置位,end_session 的
select! 赛跑与润色流的 should_cancel 立即命中。macOS CGEventTap 与
Windows WH_KEYBOARD_LL 同步修改;Less Computer 修饰键 monitor 的 Esc
转发同样迁移。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
User description
问题
转写(Transcribing)和 LLM 润色(Polishing)期间按 Esc 完全没有反应,必须干等整段流程跑完。#798 已经给
end_session加了「转写 vs 取消」的tokio::select!赛跑、润色流也有should_cancel检查——但取消信号本身送不进来。根因
Esc 的
HotkeyEvent::Cancelled与 Pressed/Released 共用同一条 mpsc channel + 同一条 bridge 线程。bridge 为修 #468/#475 的 latch 竞态改成了串行block_on(#489),Hold 松手后end_session就在 bridge 线程上同步跑完整段转写 + 润色:block_on(end_session),线程被占死;Cancelled进 channel 排队,没有人能 recv;wait_for_processing_cancel每 75ms 轮询的cancelled旗标只有cancel_session会设置,而cancel_session正等着这条被堵死的线程来调——fix(dictation): 转写阶段可中断——按 Esc 立即取消在途 ASR 请求 #798 的 select! 赛跑永远等不到信号;Cancelled,此时 phase 已回 Idle,cancel 变 no-op。听写键为修饰键(默认 fn 等)时必现;自定义组合键用户不受影响(按键事件走
ComboHotkeyMonitor的独立线程,主 monitor 的 channel 空闲)——这也是 #798 验证时没暴露的原因。修法
把
Cancelled从HotkeyEvent枚举中拆出(编译器强制所有平台改到位),Esc 在 tap/hook 回调里改发独立的Sender<()>,由专用esc-cancel-bridge线程消费并调cancel_session(纯同步快路径,不 await):hotkey.rs:HotkeyMonitor::start新增cancel_tx: Sender<()>参数;macOS CGEventTap 与 WindowsWH_KEYBOARD_LL的 Esc 分支改发 cancel 通道;Linux 占位 adapter / mobile stub 同步签名。hotkey_loops.rs:新增esc_cancel_bridge_loop+spawn_esc_cancel_bridge;两个 bridge loop 删除Cancelled臂;Less Computer 修饰键 monitor 的 Esc 转发同样迁移(cancel_session幂等,双 tap 重复触发无害)。Pressed/Released 的串行化语义(#468/#475)完全不动。
验证
cargo check/cargo check --tests通过;cargo test794 passed。should_cancel停止流式输出——两个阶段均恢复可中断。🤖 Generated with Claude Code
PR Type
Bug fix
Description
Esc 取消改走独立通道,避免被 bridge 线程阻塞
新增 esc-cancel-bridge 专用线程,即时处理 Esc 事件
macOS/Windows Esc 回调发送至独立 Sender<()>
更新各 hotkey bridge 循环,移除 Cancelled 枚举
Diagram Walkthrough
File Walkthrough
hotkey_loops.rs
Add esc-cancel-bridge loop and remove Cancelled handlingopenless-all/app/src-tauri/src/coordinator/hotkey_loops.rs
esc_cancel_bridge_loop和spawn_esc_cancel_bridgehotkey_bridge_loop和less_computer_modifier_bridge_loop移除Cancelled处理
coordinator.rs
Spawn esc-cancel-bridge on hotkey startopenless-all/app/src-tauri/src/coordinator.rs
spawn_esc_cancel_bridgecancel_tx给HotkeyMonitor::starthotkey.rs
Move Esc cancel to separate channel in hotkey moduleopenless-all/app/src-tauri/src/hotkey.rs
HotkeyEvent枚举移除Cancelledcancel_tx: Sender<()>参数到start和相关函数send_cancel_or_log函数cancel_tx发送取消事件hotkey.rs
Update mobile stub for HotkeyEvent changesopenless-all/app/src-tauri/src/mobile_stubs/hotkey.rs
HotkeyEvent移除Cancelledstart函数签名