小锋语音伴侣 —— 技术常见问题
（给帮忙排查的技术支持人员看）

========================================
开发者：星夜高飞
开发工具：WorkBuddy（腾讯“小龙虾”）
本软件公益免费，祝大家使用便利，创业成功！
========================================


零、急救：一说话就报错、消息发不出去
  症状：工作台每次提交都弹
        UserPromptSubmit operation blocked by hook:
        ["...python.exe" "...\busy.py" on]:
        can't open file ... No such file or directory
  原因：settings.json 里留着指向**已被删除文件**的死钩子（旧版本残留、
        或上一次安装中途失败、或程序目录被手工删掉）。
        平台调用不到脚本，就把用户这一次提交**整个阻断** —— 人彻底说不了话。
  处理（一条命令，立刻恢复）：
        cd %USERPROFILE%\XiaofengVoice
        .venv-voice\Scripts\python.exe fix_hooks.py scan     :: 先看
        .venv-voice\Scripts\python.exe fix_hooks.py fix      :: 再清
  说明：fix_hooks.py 不依赖安装目录，程序目录没了也能跑；
        只删「我们家的死钩子」，别人的钩子一根不碰，改前自动备份 settings.json。
  根本原因已在 v2.3.8 堵住：安装器第一步先清死钩子（保证人不会卡死），
        挂钩子前逐个核验脚本真实存在（保证不再产出死钩子）。


一、目录结构
  XiaofengVoice\          程序主目录（在用户目录下）
    voice_bridge.py       主服务（HTTP 127.0.0.1:17890）
    hotkey.py             全局热键客户端
    perm_watch.py         权限确认框监视（界面扫描，兜底）
    perm_say.py           权限确认播报（平台钩子，主力）
    notify_say.py         平台通知播报
    activity.py           上报 AI 活动中（忙碌判据之一）
    config.json           配置
    logs\                 日志（bridge.log / hotkey.log）
    models\sense-voice-zh\  离线识别模型（228MB）
    tools\                安装/体检/卸载工具
  .venv-voice\            独立 Python 环境

二、常用命令（Python 用 .venv-voice\Scripts\python.exe）
  say.py "文本"          播报
  vlisten.py 90          等待用户语音输入，返回文字
  status.py              查看服务状态
  _start_wmi.py          启动服务
  stop_bridge.py         停止服务
  install_autostart.py   重装自启并重启服务
  ptt_test.py 5          录音 5 秒自检（不粘贴）
  diag_audio2.py         输入设备探测
  diag_output.py         输出设备探测
  install_hook.py status 检查平台钩子装没装
  perm_watch.py once     手动扫一次确认框并播报

三、排障要点
1. 服务启动后很快消失
   → 必须用 WMI 派生（_start_wmi.py 已实现）。
     普通 Popen 会被父会话回收；schtasks 在部分环境被禁用。

2. 连不上 127.0.0.1（返回 502）
   → 系统代理拦截了本地请求。代码里已用 ProxyHandler({}) 绕过。

3. 麦克风打不开
   → 有些机器（尤其是专业声卡）只有 WDM-KS 内核流模式可用，
     MME / WASAPI / DirectSound 会超时。
     安装器会自动逐个探测，挑第一个能采到声音的设备。
     手工排查运行 diag_audio2.py。

4. 播报没声音（sd.play 报 MME error 1）
   → 声卡输出被独占。程序已改为用 Windows 原生
     System.Media.SoundPlayer 播放，可 kill 中断。

5. 按住键松开后没反应
   → keyboard 的 release 事件可能丢失。程序已加
     轮询 is_pressed 兜底 + 120 秒硬超时。

6. 识别结果没有标点
   → SenseVoice 模型的正常行为。

7. 焦点切不回输入框（文字没落进输入框）
   → focus_helper.py 优先用 UIA 定位 EditControl；少数环境会失败。
     兜底：在 config.json 里设 ptt.input_click_pos = [x, y]（输入框屏幕坐标），
     程序会改为点击该坐标。排查直接运行 focus_helper.py，会打印失败原因。
     依赖：uiautomation（安装器已装）。

8. 用户一直说不了话（忙碌守卫误拦）
   现象：按热键只听到"当前任务正在进行，请耐心等待"，但任务其实早结束了。
   → 查 status.py 里的 busy 字段；执行 busy.py off 强制解除。
     设计要点：Agent 播报「请等待」提示时必须用 mark=False，
     否则会被当成「已回应」而错误解除忙碌状态。
     guard.hint_cooldown 控制提示语的重复间隔（默认 8 秒）。

9. 单按 Ctrl 键不录音
   → 这是刻意设计：Ctrl 与读屏软件大量热键冲突。
     可用键：F4 录音、F8 指令优化，另有 F1（打开快捷键说明）与 Ctrl+F1 到 Ctrl+F7，共八个辅助键。
     对应 config.json 的 ptt.hotkey / ptt.refine_hotkeys / ptt.aux_hotkeys。

10. Ctrl+F4 / Ctrl+F3 没反应
   → 先看 config.json 的 ptt.aux_hotkeys 里有没有这两条。
     换机器后 Ctrl+F3 找不到个人中心时，把该机器上的用户名
     填进 ptt.personal_center_keywords（如 ["张三"]），重启生效。
     仍不行就设 ptt.personal_center_pos = [x, y] 用屏幕坐标兜底。

11. Ctrl+F4 焦点没进输入框
   → WorkBuddy 对话里的代码块在 UIA 里也是 EditControl，
     但坐标全是 0（离屏）。程序按「可见且面积最大」挑主输入框，
     一般能命中。极少数情况设 ptt.input_click_pos = [x, y]。

12. Ctrl+F3 变成「一次展开、一次折叠」
   → 旧版是无脑点击，等于开关（toggle），所以会来回切。
     新版改成状态感知：先查窗口里有没有 MenuControl
     （WorkBuddy 的菜单是 Electron 的 DOM 弹出层，展开时才有，
     且不产生新顶层窗口），有就只聚焦不点击，没有才点开。
     focus_helper.find_open_menu() 就是判据，换机器若失效，
     把里面的 ControlTypeName 改成该机器上实际的菜单类型。

13. 语音反馈断了（半天不念了）
   → 先看 bridge.log 里有没有 synth / 系统播放 记录：
     有记录 = 播报被触发过，是音量或播放设备问题；
     一条都没有 = 根本没触发。
   → 根本原因通常是「靠 AI 记得每轮调 say.py」——对话被压缩后
     这条规则就从上下文里消失了，于是静悄悄。
     正解是装 Stop 钩子（auto_say.py + install_hook.py），
     由平台在每轮结束时自动调用，不依赖上下文。
     检查：python install_hook.py status

14. 权限确认框没有语音提示
   → 三道保险，按顺序查：
     ① 平台钩子（主力）：python install_hook.py status
        应看到 PermissionRequest / PermissionDenied 都是"已安装"。
        钩子由平台在弹框那一刻调用 perm_say.py，最可靠。
        注意 matcher：除 Stop 用 "" 外，其余必须写 "*"，写错不触发。
     ② 界面扫描（兜底）：perm_watch.py 常驻，1.2 秒轮询一次，
        抓独立弹窗和主窗口内 DOM 弹层两种形态，
        把"问题 + 选项"完整念出来。看 logs\perm_watch.log。
     ③ 平台通知：notify_say.py 挂在 Notification 事件上。
   → 两个来源同时命中时，靠 logs\_perm_last_say.txt 做 5 秒冷却，
     不会念两遍。

15. 权限确认只念"需要你确认"，没念具体问题
   → 界面扫描没抓到问题文本（不同版本界面结构可能不同）。
     优先用平台钩子（第 14 条①），它直接从平台拿工具名和参数，
     不依赖界面。看 logs\perm_say.log 里的"播报:"行确认内容。

16. Ctrl+F2 说"现在没有需要确认的内容"
   → 正常。这个键是重念最近一次确认框，缓存有效期 180 秒
     （guard.perm_repeat_ttl），过期或确认框已处理完就这样回答。

17. 按 Alt+F4 会开始录音 / 辅助键在别的软件里乱触发
   → 已修（v1.3）。三条规则：
     ① 单键（F4 / F8）带任何修饰键时都不触发，
        一律让给系统（Alt+F4 关窗口、Shift+F4 等照旧归系统）；
     ② 组合键要求修饰键完全吻合，
        Ctrl+Shift+F4 不会触发 Ctrl+F4 的辅助动作；
     ③ 辅助键默认 window 作用域（ptt.aux_scope），
        只在 WorkBuddy 窗口内生效；改成 "global" 可全局生效。
   → 仍需注意的已知冲突（本工具不拦按键，所以两边都会响应）：
       Ctrl+F4 在浏览器/编辑器里是「关闭标签页」；
       Ctrl+F2 在 Word/Excel 里是「打印预览」；
       Ctrl+F5 在浏览器里是「强制刷新」。
     想彻底避开，把 ptt.aux_hotkeys 改成 Win+F4 / Win+F3 / Win+F2
     （Win 组合键的系统占用最少）。
   → 回归测试：python _test_conflict.py（14 项用例）

18. 离线包 vs 精简包，怎么选、怎么装
   → 完整离线包：根目录带 wheels/（约 42 MB 组件）、models/（228 MB 模型）、
     runtime/（28 MB 的 Python 3.13 安装器），全套约 300 MB。
     装的时候 pip 走 --no-index --find-links=wheels，完全不联网。
   → 精简包：只有源码，安装时联网下组件和模型。
   → 为什么要锁 Python 3.13：离线 wheel 是按 cp313 编译的，
     目标机器若是 3.9/3.11 装上去不兼容。所以包里自带安装器，
     检测到系统 Python 不是 3.13 时静默装一个到用户目录
     （不需要管理员权限）。
   → 安装前想先探一下机器：双击"check.bat"，
     会检查 Python、组件、VC++ 运行库、模型、麦克风、钩子，
     结果会念出来。

Q：安装的时候，工作台（WorkBuddy）开着有没有影响？要不要先关掉？
A：**先关掉最保险**。推荐顺序是：
       ① 把工作台整个退出  →  ② 运行安装  →  ③ 装完再打开工作台。
   开着也能装完（复制文件、装运行环境、模型、麦克风、开机自启、
   启动语音服务，这些都不依赖工作台），但有两个隐患：
     ① 语音钩子是写进工作台自己的配置文件里的，
        工作台**退出时**有可能把配置回写回去，把刚装的钩子盖掉；
     ② 其中一部分提醒（比如「需要你确认」的播报）要重启会话后才加载，
        不重启会遇到「装好了却不响」。
   先关掉再装，这两条就都绕过去了。
   要是开着装的，装完务必「整个退出、重新打开」一次，
   再用「一键体检」复查。装完那句提示会按你当时的状态告诉你该怎么做。

Q：工作台（WorkBuddy）升级以后，语音伴侣会不会失效？
A：有可能，所以我们做了自动检测：
   每次开机自动比对工作台版本；版本一变就自动跑六项自检
   （服务、热键、窗口、输入框、综述、钩子），结果会念给你听。
   钩子被升级清掉是最常见的故障，自检会**自动重新装上**。
   自己想查：运行「check.bat」，最后一段就是兼容性检查。
   手动跑：python _compat_check.py force
   只看不念：python _compat_check.py force quiet

Q：版本没变，但我想确认一下还好不好用？
A：加 force 参数强制自检，见上一条。

Q：工作台在运行中升级了，语音伴侣会自己发现吗？
A：会。后台守护每 45 秒巡视一次，发现版本变了**立刻**跑完整自检并念结果，
   不用等下次开机。语音服务、热键、播报钩子断了也一样，当场重启并告知。
   想马上查：按 Ctrl+F7，或运行 python _selfheal.py now。

Q：它会不会一直念，吵得我没法干活？
A：不会。同一类问题五分钟内只处理一次；连续三次修不好就停手，
   只说一句「请运行一键体检」，不会反复重试。

Q：按了优化键，等了很久也没听到改写结果，怎么办？
A：多半是工作台没按「只改写」来做，而是直接去执行了 —— 任务不结束，
   改写结果就回不来（实测过：说「帮我画一张……」，它真的去画了）。
   现在有兜底：超过 refine.timeout 秒（默认 90）还没回来，它会主动说
   「改写结果迟迟没回来，工作台多半是直接去执行了，按 Esc 可以停下来」。
   听到这句就按 Esc（四秒内再按一次确认），然后重新说一遍。

四、配置项说明（config.json）
  tts.voice              音色，默认 zh-CN-XiaoxiaoNeural（晓晓）
                         可换 zh-CN-YunxiNeural（男声）等
  tts.rate               语速，如 "+8%"
  tts.engine             auto（在线优先，默认）/ edge（只在线）/
                         sapi（只离线，断网机器建议设它）
  tts.edge_cooldown      在线合成失败后的冷却秒数（默认 300），
                         避免每次播报都先卡一次连接超时
  tts.edge_connect_timeout  在线连接超时秒数（默认 8）
  tts.edge_receive_timeout  在线接收超时秒数（默认 30）
  notify.idle_match      命中这些英文就改念中文（平台空闲提醒）
  notify.idle_text       空闲提醒的中文播报内容
  ptt.hotkey / alt_hotkeys   热键
  ptt.combo_hotkeys         组合热键，数组形式。
                            注意：含 windows 的组合一律不生效
                            （与系统热键冲突且收不到稳定的按下/松开），
                            默认已是空数组 []。
  ptt.cancel_scope          Esc 取消的作用域：record_only（默认，
                            仅录音中响应）/ always（按下即取消）
  ptt.refine_hotkeys         指令优化键（默认 F8，数组形式）
  refine.enabled             是否启用指令优化（默认 true）
  refine.max_chars           改写结果最多保留多少字（默认 600）
  refine.say_prefix          念改写结果时的开场白
  refine.done_text           念完后的操作提示
  refine.prompt              改写用的提示词模板，{text} 会换成你的原话；
                             留空则使用程序内置的那一份
  ptt.esc_stop.enabled      没录音时按 Esc = 急停（默认 true，需二次确认）
  ptt.esc_stop.window       二次确认的有效秒数（默认 4）
  ptt.esc_stop.ask_text     第一次按 Esc 时的询问语
  ptt.esc_stop.done_text    确认停止后的提示语
  ptt.esc_stop.cancel_text  按其他键或超时取消时的提示语
  ptt.esc_stop.send_esc_to_app  急停后是否把 Esc 转发给工作台（默认 true）
  ptt.esc_stop.perm_grace   确认框出现后多少秒内 Esc 让给确认框（默认 25）
  ptt.always_focus          录音前是否自动把焦点切回输入框（默认 true）
  ptt.auto_send          识别后是否自动回车发送
  ptt.auto_send_window_keywords   仅在这些窗口内自动发送
  audio.input_device_index        安装器探测到的麦克风索引
  ptt.aux_hotkeys           辅助动作键：ctrl+f1 窗口综述 / ctrl+f4 输入框 /
                            ctrl+f3 个人中心 / ctrl+f2 重念确认框 /
                            ctrl+f5 恢复读屏 / ctrl+f6 音频闪避
                            单个键可加 "scope": "global" 覆盖 ptt.aux_scope
  screen_reader.enabled     录音时自动暂停读屏（默认 true）
  screen_reader.mute_key    发给读屏的暂停键（默认 pause）
  screen_reader.also_on_speak   播报时是否也暂停读屏（默认 false）
  guard.tool_active_gap     AI 最后一次调工具后仍算忙碌的秒数（默认 12）
  guard.manual_max          手动忙碌锁最长自动解除时间（秒，默认 180）
  guard.hint_elapsed        忙碌提示是否附带「已等待 N 秒」（默认 true）
  guard.hint_elapsed_from   等待超过多少秒才开始报秒数（默认 30）
  guard.perm_hint           确认框没抓到内容时的兜底提示
  ptt.aux_scope             辅助键作用域：window=仅 WorkBuddy 内（默认）/
                            global=全局生效
  guard.perm_tail           选项之后念的操作说明
  guard.perm_denied         操作被拒绝时的提示
  guard.perm_repeat_ttl     Ctrl+F2 重听的缓存有效期（秒，默认 180）


========================================
安装时报错怎么办（2026-09-15 新增）
========================================

问：双击"install.bat"以后，走到一半就红了，说脚本有错误？

答：请先看这三个最常见的原因，按顺序排查：

  1. 是不是在 Windows 7 上装的？
     Windows 7 不支持。自带的运行环境 Python 3.13
     已经不再支持 Win 7。请换 Windows 10 以上的电脑。

  2. 是不是右键"以管理员身份运行"的？
     不要提权。直接双击就行。提权会把程序装进
     Administrator 目录，你自己的账号反而用不上。

  3. 电脑上是不是有微软商店的 Python？
     Win10/11 里输入 python 可能会跳到微软商店，
     那是个空壳。安装程序已经会自动跳过它，
     改用自带的运行环境；如果还是不行，
     请先到"设置 - 应用 - 应用执行别名"里
     把 python 的两个开关关掉，再重新运行安装。

  另外，安装包目录里会生成一个"XiaofengVoice_install_log.txt"，
  把最后十几行发给开发者，能最快定位问题。


