97 Commits
Author SHA1 Message Date
appergb 6aad6e814f chore(release): 1.3.10-1 (beta) — audit fixes batch 1+2 (critical Android updater, security hardening, correctness bugs) 2026-06-17 11:45:38 +08:00
appergb 4f73dbd5e4 fix(cask): revert openless to 1.3.8 — v1.3.9 unpublished (unauthorized buggy release) [skip ci] 2026-06-16 21:08:08 +08:00
吕柏青andClaude Fable 5 7b71dc55ca feat(history): 转录失败保留录音 + 历史「重新转录」(issue #613)
ASR 转录失败时若本次录音已归档,写一条 transcribeFailed 历史记录,让用户能在
历史页回放原始录音并对其重新转录,不再丢失可恢复的语音内容。

后端:
- dictation_end: 新增 write_transcribe_failed_history 辅助函数,在 14 处真实
  转录失败分支(各 provider 的 Ok(Err)/超时;不含 cancel/empty 分支)写历史。
  history id 用 coordinator session_id,与归档 wav 文件名对齐,回放/重转才能命中。
  仅在录音确已归档时写(沿用 empty-transcript「以实际归档状态为准」语义)。
- coordinator: retranscribe_pcm —— 复用 build_qa_asr_start,用当前 provider 对一段
  PCM 重新转录,统一覆盖流式/批处理全部 provider,整段超时兜底。
- persistence: HistoryStore::update_entry,原地按 id 替换历史条目(保持位置)。
- commands/history: retranscribe_recording 命令 —— 读归档 wav→取 PCM→重转→回写
  rawTranscript/finalText 并清 error_code,返回更新后的整条记录。已注册到 lib.rs。

前端:
- ipc: retranscribeRecording。
- History.tsx: 失败条目(有 errorCode)且录音仍在时显示「重新转录」按钮,
  loading 态防重复点击,成功后局部刷新该条。
- i18n: history.retranscribe / retranscribing / retranscribeFailed(5 语言)。

仅做 ASR 重转,不自动二次润色(依赖 LLM 凭据且 issue 标为待定,留作后续)。
验证:cargo check + cargo test(481 通过)+ npm run build 均通过。

Closes #613

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 15:26:03 +08:00
吕柏青andClaude Fable 5 050927e719 feat(asr): Apple Speech 本地 ASR 接线 — dictation/QA/cancel + 平台门控 (issue #574)
承接草稿 PR #625 的 provider 基础,补完后端接线:
- coordinator: ActiveAsr::AppleSpeech 变体(macOS only)
- asr_setup: build_apple_speech + ensure_asr_credentials 平台门控 +
  build_qa_asr_start QA 接线 + is_keyless_local_asr_provider
- dictation_session/dictation_end: 听写 start + 批处理 transcribe(沿用
  local_qwen 动态超时)
- qa_session: QA transcribe arm
- resources: cancel arm
- commands: active_apple_speech_asr_is_supported(非 macOS 返回 false)+
  keyless 校验白名单 + asr_configured_for_provider + set_active_asr_provider
  非 macOS 拒绝门控

provider 仅 macOS 编译;非 macOS 由门控判不可用。前端 UI/i18n 已随 #625 落地。
验证:cargo check(macOS)通过、provider 单测 5/5 通过、npm run build 通过。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 15:00:14 +08:00
吕柏青 7ed72be76e feat(asr): Apple Speech 本地 ASR provider 基础(Draft, issue #574)
从旧 WIP 分支抢救 provider 并重新落到当前 beta 结构;只含可干净合并的基础,coordinator/dictation 接线、命令、完整编译验证待后续(见 PR 待办)。

- asr/local/apple_speech_provider.rs:SFSpeechRecognizer 批处理识别 + 授权 + objc2 桥接 + 单测
- asr/local/mod.rs:注册 + APPLE_SPEECH_PROVIDER_ID + is_apple_speech
- build.rs:链接 Speech.framework(macOS);Info.plist:NSSpeechRecognitionUsageDescription
- 前端:LocalAsr / ProvidersSection / Overview / shared + 5 语言 i18n
2026-06-10 14:49:24 +08:00
吕柏青 7ddd73a057 fix(vault): 保存凭据跳过未变化的 keychain 条目,修复切换供应商弹窗风暴 (#602)
切换供应商只改 root.active 一个字段,但 save_credentials 会先读旧 manifest、
再重写所有 chunks、再重写 manifest —— macOS 上每个 keychain 条目各自 ACL,
ad-hoc 签名/未永久授权时每次访问各弹一次「访问密码」弹窗,一次切换弹 3+ 次。

三处收口(都基于已有的进程级凭据缓存,行为冷路径不变):
1. manifest 进程缓存(CREDENTIALS_MANIFEST_CACHE):load/save 成功后回填,
   后续 save 不再回 keychain 读旧 manifest(省 1 次 ACL 访问);冷缓存仍读
   真实 manifest,UUID 代际旧 chunks 的清理信息不丢。
2. chunk_skip_mask:用缓存里上次落盘的 root 反推旧 chunk 内容,逐字节一致的
   chunk 跳过重写。仅旧 manifest 已是稳定名(generation=None)时启用;缓存
   序列化顺序差异只会多写(回退旧行为),不会漏写。
3. manifest 内容只由 chunks 数决定:数量不变且已是稳定名 → 跳过重写。

典型单 chunk 凭据库:切换供应商 3 次 keychain 访问 → 1 次。
2026-06-10 09:36:08 +08:00
吕柏青 287a0ff4b4 fix(capsule): 终态/空闲让胶囊点击穿透,修复贴近输入框误触弹主界面 (#631)
胶囊窗口 220×110 远大于可见 pill,录音完成后 2s toast + 360ms 离场动画期间
透明区域仍会吃掉点击并激活 OpenLess(把主界面带到前台)。在 emit_capsule
主线程闭包按状态切换 set_ignore_cursor_events:Done/Cancelled/Error/Idle
点击穿透到下层应用;Recording/Transcribing/Polishing 保留 ✕/✓ 按钮可点。
按状态变化去重,录音中 ~30Hz 电平帧不重复调系统 API。Linux 分支不操作
胶囊窗口,不受影响。
2026-06-10 09:26:22 +08:00
吕柏青 36f8b7291e fix(polish): Traditional 模式强制中文字形转换,不再只靠 prompt (#622)
繁体用户实测:选 Traditional 后最终插入文字仍可能含简体。根因——

1. finalize_polished_text 仅在 Raw / 翻译 / 润色失败时强制 apply_chinese_script_preference;
   普通模式 LLM 润色成功时只靠 system prompt 指示 LLM 输出繁体,而部分 provider 会
   跟随简体 ASR 输入继续输出简体,prompt 指示不可靠(issue #622)。
2. 流式插入路径逐字落字,刻意跳过 apply_chinese_script_preference(dictation_streaming.rs
   注释明示),无法回退已落字符。

修复:
- finalize_polished_text 在非流式路径下无条件套用 apply_chinese_script_preference
  (对 Auto 是 no-op,不影响自动模式行为;非 Auto 时保证最终文字不混简体)。
- streaming_insert_eligible 增加 chinese_script_preference 门:非 Auto 时关闭流式,
  转走会做字形转换的一次性路径(满足 issue「无法安全转换则 fallback 到 one-shot」)。
- 新增单测:finalize 对 issue 两个验收用例输出繁体;非 Auto 时流式不 eligible。

Auto 模式保持原行为不变。
2026-06-09 20:06:11 +08:00
吕柏青 2ff80d0f72 fix(settings): 设置弹窗用 Portal 渲染避免被页面滚动条覆盖 (#580)
在历史/风格页滚动后打开设置弹窗,WebKitGTK(Debian/KDE Wayland)下页面自绘滚动条
(.ol-thinscroll) 会盖在弹窗之上。根因:弹窗用 position:absolute 处于页面 overflow:hidden
祖容器的同一 stacking context 内,而 WebKitGTK 滚动条不创建独立合成层,z-index 对其无效
(issue #580 中 position:fixed、isolation:isolate 均无效已验证)。

- SettingsModal 改用 createPortal 渲染到 document.body,彻底脱离页面 DOM 树与
  stacking context(与 SelectLite 的 popover 同款做法)。
- 遮罩层 position 由 absolute 改为 fixed,覆盖整窗;视觉与交互不变。
- 设计 token 定义在 :root,body 下的 portal 正常继承,无样式丢失。
2026-06-09 19:58:34 +08:00
吕柏青 ac1f723afc fix(history): 历史页搜索框可点击、可搜索 (#612)
历史页顶部带搜索图标的区域此前是静态 div,只显示「共 N 条 · 显示 M」统计,
无法获得焦点、无法输入、无过滤逻辑(issue #612,Windows beta 实测)。

- 把静态 span 改为真实 `<input type="search">`,可聚焦输入;统计文案下移为独立小字。
- 按关键词实时过滤左侧列表,匹配 finalText / rawTranscript / appName(大小写不敏感、trim);
  与现有 mode 筛选叠加。
- 清空关键词恢复显示全部(受当前 mode 筛选)。
- ⌘K / Ctrl+K 聚焦搜索框(设计稿预期)。
- 搜索无结果时显示专门提示 history.searchNoMatch,区别于「暂无历史」。
- 新增 i18n: history.searchPlaceholder / history.searchNoMatch(en/ja/ko/zh-CN/zh-TW)。
2026-06-09 19:56:06 +08:00
吕柏青 4b4d21912b fix(insertion): macOS 插入后恢复用户剪贴板 (#525)
此前 macOS 的 insert() 只写剪贴板 + post Cmd+V,完全忽略 restore_clipboard_after_paste
参数;整套剪贴板恢复机制被 #[cfg(not(target_os = "macos"))] 排除,导致设置里的
「恢复剪贴板」开关在 macOS 上无效(issue #525,实测 macOS 26.5 / M4 Pro)。

- 把剪贴板恢复机制(保存原内容 → 延迟回写 → 仅当剪贴板仍是刚插入文字时才恢复)改为
  跨平台可用,去掉 cfg(not macos) 门控;insert_with_clipboard_restore 仍仅 Windows/
  Linux 使用(依赖 PasteShortcut 模拟),故保留其门控。
- macOS insert() 改为:保存原剪贴板 → 写转写文字 → Cmd+V → 粘贴成功且开关开启时按
  CLIPBOARD_RESTORE_DELAY 延迟恢复;粘贴失败则保留转写文字供手动粘贴、不恢复。
- CLIPBOARD_RESTORE_DELAY 合并为统一常量;删除随之失效的 macos_insert_status_after_paste。
- should_restore_clipboard 守卫测试改为跨平台运行,新增 macOS 成功路径测试。
2026-06-09 19:50:23 +08:00
吕柏青 e2af9ba38e chore(release): 1.3.6-3 2026-06-08 17:30:38 +08:00
吕柏青 5a8757d3c2 feat(polish): 清晰结构提示词前置第一指令,≥2 件事强制编号罗列
弱模型(deepseek-v4-flash)被 8 段长提示词稀释、抓不到关键的'必须罗列'规则。把
'≥2 件事必须编号清单、揉成一段=失败'前置到最顶(高于角色),让模型第一眼读到。
2026-06-08 17:30:38 +08:00
吕柏青 6b9b0ddd89 fix(less-computer): 彩虹边框首次半屏卡死修复
glow 窗口 conf 初始 800×600 且 visible:false,首次 show 前从未 realize →
current_monitor()/set_size 没贴住整屏,webview 首帧按 800×600 画半屏描边。改为
realize(orderFront) 后再铺满整屏一次,强制按整屏重排重绘。
2026-06-08 17:30:38 +08:00
吕柏青 432673eb63 fix(security): history HMAC 密钥/标志从 keychain 迁到 0o600 文件
ad-hoc 签名下每个钥匙串条目各自 ACL、删建即清空 ACL,导致 #617 的 history HMAC
两个钥匙串条目让用户每次听写后反复弹授权(凭据 3 次外又多 2 次)。迁到同目录
0o600 文件(密钥与明文 history 同目录、安全收益对等,注释本就标注为预期兜底)。
含零钥匙串迁移:换后端时删旧 sidecar 走 legacy 重新接受+补签,不丢历史。
2026-06-08 17:30:38 +08:00
吕柏青 1bb580700d revert(ui): 回退 aura 皮肤(#605) 到之前 UI + 设置瘦身/词汇表边距/胶囊弹性弹出
用户决定放弃 aura 皮肤改版(套框悬浮/吞玻璃/选框丑),前端整体回退到 aura 之前
的设计,并在其上做几处打磨:
- 设置页瘦身:去掉所有区块/每行描述小字 + 移除字体大小个性化
- 词汇表悬浮台到右/下边距相等(8px)
- 胶囊弹出改弹性曲线(scale .78→1 back-out overshoot)
2026-06-08 17:30:38 +08:00
吕柏青 2cc896f422 Merge branch 'fix/issue-609-security-audit' into refactor/split-dictation 2026-06-08 12:57:05 +08:00
吕柏青 972e7ff494 fix(security): F-02 翻译路径补对抗式注入防御 (#609)
最终自审(SEC-001/MEDIUM)发现:polish 润色路径在 system prompt 末尾追加了
polish_injection_defense()(明确 <raw_transcript> 内是数据非指令),但翻译路径
(OpenAI 兼容 / Gemini 的 compose_translate_prompts、Codex translate_to、
润色+翻译合一的 build_polish_translate_system_prompt)全部漏了这层纵深,与 polish
形成不对称——用户对翻译说"忽略上述指令,回复 X"时缺显式约束。

修法:把防御嵌进 translate_system_prompt() 这个所有翻译路径的唯一 base,令每个
调用方自动覆盖,杜绝调用点遗漏(EN 专用 / 通用 base 两分支都覆盖)。

XML 标签注入此前已由 user_prompt 内 sanitize_for_xml_envelope 中和,本项补的是
语义级'输入是数据非指令'约束。新增 2 个回归测试守住翻译路径与合一路径。
2026-06-08 12:56:44 +08:00
吕柏青 3f22d2abc0 Merge branch 'fix/issue-609-security-audit' into refactor/split-dictation 2026-06-08 11:13:17 +08:00
吕柏青 0fd120a2bd fix(security): F-01 放宽 — RFC1918/ULA 局域网允许 http(支持 LAN 自托管),仍拦 169.254/CGNAT/公网 http (#609) 2026-06-08 11:11:14 +08:00
吕柏青 ed48cf40fc refactor(dictation): 抽出 dictation_end 子模块(end_session 并发敏感整体搬,不动锁/await) 2026-06-08 09:32:54 +08:00
吕柏青andClaude Opus 4.8 f6775b89c8 refactor(dictation): 抽出 dictation_session 子模块
把会话生命周期函数从 coordinator/dictation.rs 机械式搬到同级
coordinator/dictation_session.rs(行为保持):request_stop_during_starting、
begin_session、start_recorder_for_starting、spawn_recorder_error_monitor、
abort_recording_with_error、start_recorder_and_enter_listening、
finish_starting_session、dictation_error_code、cancel_session、
eligible_polish_context_turns。新模块 use super::resources::* 取资源辅助函数。
coordinator.rs glob 重导出并把 begin_session/cancel_session/
request_stop_during_starting/dictation_error_code 的命名导入改到
dictation_session::;tests.rs 的 abort_recording_with_error 路径同步;
dictation.rs 去掉随之失用的 request_stop_during_starting_state 导入,
测试 eligible_polish_context_turns 改走 crate::coordinator:: 路径。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 21:13:01 +08:00
吕柏青andClaude Opus 4.8 281c503771 refactor(dictation): 抽出 dictation_voice_agent 子模块
把 Less Computer 语音 Agent 路径从 coordinator/dictation.rs 机械式搬到
同级 coordinator/dictation_voice_agent.rs(行为保持):less_computer_approvals、
resolve_less_computer_approval、emit_less_computer、run_voice_agent_transcript、
LessComputerOutcome、run_less_computer_once、maybe_request_approval、
coding_agent_mode_from_pref,以及 LESS_COMPUTER_EVENT/LESS_COMPUTER_APPROVALS。
coordinator.rs glob 重导出新模块并把 resolve_less_computer_approval 调用改到
dictation_voice_agent:: 路径;dictation.rs 去掉随之失用的 AtomicBool 导入。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 21:07:18 +08:00
吕柏青andClaude Opus 4.8 73ea5e3af8 refactor(dictation): 抽出 dictation_streaming 子模块
把流式润色与文本收尾相关函数从 coordinator/dictation.rs 机械式搬到
同级 coordinator/dictation_streaming.rs(行为保持):run_streaming_polish、
drain/flush streaming insert、finalize_polished_text、streaming_insert_eligible、
default_done_message、append_typed_prefix,以及 STREAMING_INSERT_FLUSH_INTERVAL。
coordinator.rs glob 重导出新模块,dictation.rs 测试改走 crate::coordinator:: 路径。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 21:02:46 +08:00
吕柏青 3342e4218d fix(security): F-01 收口 — ASR/provider 自定义 endpoint 也做 SSRF 校验 (#609) 2026-06-07 20:54:26 +08:00
吕柏青andClaude Opus 4.8 4e65ce15e1 fix(security): #609 双审收口(C-01 堵 legacy HMAC 绕过/H-01 Gemini SSRF 校验/H-02 常量时间/0o600 race/补测试)
C-01: 引入 keyring 持久化的 history.hmac_enrolled.v1 标志。首次写 sidecar 或
legacy 迁移时幂等置位;读路径遇 sidecar 缺失时——已置位则判定攻击者删除、
fail-safe 返回空(绝不接受/补签),未置位才走 legacy 接受+迁移。堵住「删
sidecar 伪装 legacy 绕过 HMAC」。

H-01: read_gemini_credentials 抽出 resolve_gemini_base_url,base_url 同样过
validate_llm_endpoint SSRF 校验。

H-02: hmac_hex_eq 改用 subtle::ConstantTimeEq(subtle 已在 Cargo.lock 传递依赖)。

M-01: history.json 与 sidecar 改 atomic_write_private,rename 前对 tmp 设 0o600,
消除 umask 世界可读窗口。

M-02: 补 SSRF 绕过变体测试(0x7f.0.0.1 / 2130706433 归一化为本地放行;
user@192.168.1.1 拒绝;0.0.0.0 拒绝)。

M-03: keyring 不可用记一次 warn;文件密钥 fallback 文档留作后续。

rust 收口: encode_hex 用 write!;validate_llm_endpoint 统一剥 IPv6 方括号后判定
(修正「url 已去括号」的错误前提——host_str 对 IPv6 保留括号,原 fallback 实为
必需路径,已保留语义);history 读取 TOCTOU 改直接 fs::read + NotFound 返空。

测试 460→467 passed,0 failed;cargo check 0 error;npm build 通过。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 19:31:29 +08:00
吕柏青andClaude Opus 4.8 b146f9f660 test(security): F-05 golden/snapshot prompt 测试 + qa/resources 纯函数单测 (#609)
补 prompt 组装的 golden/快照测试与零覆盖纯函数单测(部分):

- user_prompt_golden_envelope_structure:锁死 <raw_transcript> 信封完整结构。
- build_polish_history_messages_sanitizes_prior_turn_raw_text:历史轮 raw 也被
  F-02 信封化 + 转义(防历史投毒里夹注入标签)。
- coordinator/resources.rs::take_session_resource 三个单测:id 匹配取走 / 不匹配
  保留(stale session 守卫)/ 空槽返回 None。
- coordinator/qa.rs::QaSessionState::default 不变量单测(Idle/clean 起点)。

F-01/F-02/F-06 已分别附 endpoint 校验、信封转义/截断/防御措辞、QA 信封的
golden/快照断言,合计构成 prompt 组装的不变量护栏。

剩余项(不在本 PR 范围):完整 mock-LLM/ASR 端到端集成 harness
(ASR→Polish→Insert 全链路,issue 估 ~4h)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 19:04:11 +08:00
吕柏青andClaude Opus 4.8 0765cb92b1 fix(security): F-03 history HMAC 完整性 + F-04 0o600/at-rest 文档 (#609)
history.json 此前明文存储且无完整性校验,本地攻击者可注入伪造的
assistant 历史污染对话感知 polish 的 LLM 上下文(CWE-349)。

F-03(HMAC 完整性):
- 新增依赖 hmac=0.12(复用已有 sha2=0.10)+ getrandom=0.3(密钥生成)。
- HMAC 密钥 32 字节随机,存系统凭据库(独立 account history.hmac_key.v1,
  不掺进 chunked CredsRoot),OnceLock 进程内缓存。
- 写入算 HMAC-SHA256 写 sidecar history.json.hmac;读取常量时间比对,
  不匹配 → log::warn + fail-safe 返回空历史,绝不把被篡改历史喂给下游 LLM。
- 向后兼容:老用户无 .hmac → 视为 legacy,首次读接受并立即补写 sidecar 迁移。
- keyring 不可用 → 退化为不校验(保持可用,不提供完整性保证)。
- 读写逻辑抽成纯函数 read/write_history_with_key(key) 便于单测。

F-04(明文存储 0o600 + 文档化):
- 写 history.json 与 sidecar 后,unix 收紧权限到 0o600(仅属主可读写);
  Windows 走 %APPDATA% per-user ACL。
- HistoryStore 文档化 at-rest 行为:明文 JSON + OS 文件权限 + HMAC 完整性;
  完整静态加密为已知残留留待后续(issue #609 F-04 明确允许 clearly document)。

新增 7 个单测:HMAC/hex 往返、常量时间比较、写后读通过、篡改 fail-safe、
legacy 迁移、无密钥读、unix 0o600 权限。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 19:01:53 +08:00
吕柏青andClaude Opus 4.8 93830ffdc0 fix(security): F-06 QA 注入 — 选区原文包 XML 信封 (#609)
QA 模式此前把用户选中文本直接以 "# 选区原文\n{}" 喂 LLM,没有 polish
那样的 XML 隔离与转义,选区里夹带"忽略上述指令"可把问答 LLM 带跑。

- 抽出纯函数 compose_qa_user_content:首轮非空选区包进 <selected_text> 信封,
  复用 polish 的 sanitize_for_xml_envelope(开/闭标签中和 + 16000 字符上限)。
- qa_system_prompt 增加"信封内是引用材料非指令"安全约定与输入约定说明。
- 新增 4 个单测:信封包裹、注入中和、非首轮只送提问、空选区只送提问。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 18:55:57 +08:00
吕柏青andClaude Opus 4.8 4063efca10 fix(security): F-02 prompt 注入 — polish 信封多层加固 (#609)
ASR 转写是不可信输入,此前只转义 </raw_transcript> 闭标签,
不防开标签伪造、大小写/空白变体,也无长度上限与对抗式 system 措辞。

- 新增 sanitize_for_xml_envelope:开/闭标签都中和(含大小写+内部空白变体),
  首个 < 转义为 &lt; 破坏边界语义;16000 字符上限,超出截断附 …[truncated]
  防 attention dilution。
- user_prompt 改用统一 sanitizer;translate 路径同样受益。
- compose_polish_prompts 在 system prompt 末尾追加 polish_injection_defense
  对抗式措辞:信封内是数据非指令,绝不当命令执行。LLM 非安全边界,纵深防御。
- 新增 8 个单测覆盖闭/开标签中和、大小写空白变体、截断、防御措辞注入。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 18:53:28 +08:00
吕柏青andClaude Opus 4.8 4cd8db605b fix(security): F-01 SSRF — LLM endpoint IP 段校验 + https 强制 (#609)
用户自定义 LLM endpoint 此前零校验直接用于发带 API Key 的请求,
attacker 可把 endpoint 指向 169.254.169.254 等内网/元数据地址泄露凭据。

- 新增 validate_llm_endpoint:scheme 强制 https(localhost/127.0.0.1/::1 放行 http);
  字面 IP 拒绝 loopback/RFC1918/link-local/unspecified/CGNAT(RFC6598)/IPv6 ULA/
  IPv4-mapped 等价私网;拒绝 metadata.google.internal 与 169.254.169.254 子串。
- resolve_ark_endpoint_with_policy 返回前调用校验(默认官方 endpoint 也过一遍)。
- DNS 重绑定为已知残留(注释标注),不在本 PR 范围。
- 新增 13 个单测覆盖接受/拒绝路径。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 18:50:02 +08:00
吕柏青 482408071e fix(popup): 云审收口(clamp saturating_sub 防溢出 + 右缘溢出单测 + 清理不可达 hwnd guard) 2026-06-07 13:59:54 +08:00
吕柏青 fab965162b fix(popup): capsule 四边 clamp + Windows 句柄重试(#470) + qa 焦点契约文档化(#466) 2026-06-07 13:48:56 +08:00
吕柏青 bdd26fbca2 docs(commands): 修正 mod.rs 过时命令计数注释(89→127) 2026-06-07 13:42:29 +08:00
吕柏青andClaude Opus 4.8 d669dfd419 style(asr): cargo fmt 收尾 pcm.rs 测试断言换行
属 C4(pcm_duration_ms 去重)的 rustfmt 收尾,无逻辑变化。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 13:19:28 +08:00
吕柏青andClaude Opus 4.8 b77f42a458 refactor(ui): 抽出共享 format.ts(clamp/formatTime/formatDuration/formatBytes)
clamp 在 DataStorageSection/DebugToolsSection 字节级相同;formatTime/
formatDuration 在 Overview/History 高度重复(仅日期分支/i18n key 略不同);
formatBytes 在 AutoUpdate/LocalAsr 行为有差异(封顶 MB+guard vs 到 GB)。
统一收口到 src/lib/format.ts,用 option 区分各分支:formatTime 的
withTimeOnOtherDays、formatDuration 的 minutesAsClock、formatBytes 的
maxUnit/mbDigits/guard。各调用点改为 import + 薄封装,显示行为逐处保持不变。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 13:18:45 +08:00
吕柏青andClaude Opus 4.8 16fa62d082 refactor(coordinator): 合并 less_computer 两个 bridge loop
less_computer_modifier_bridge_loop 与 less_computer_combo_bridge_loop 循环体
逐字相同,只有事件枚举类型不同(HotkeyEvent vs ComboHotkeyEvent)。抽出泛型
less_computer_bridge_loop<E>,调用方传入把各自事件映射到归一 LessComputerEdge
的闭包;两个公开入口改为薄封装。block_on(handle_less_computer_*) 调用语义不变。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 13:15:47 +08:00
吕柏青andClaude Opus 4.8 cbf3be0d50 refactor(coordinator): 合并 take_*_combo_hotkey_on_main_thread 三处复制
combo_hotkey / translation_hotkey / coding_agent_combo_hotkey 三个
take_*_on_main_thread 的主线程释放(issue #118)marshaling 体逐字相同,仅
take 字段不同。抽出共享 take_combo_monitor_on_main_thread(inner, slot),
slot 闭包按 &Arc<Inner> 取回对应 Mutex<Option<ComboHotkeyMonitor>> 字段,
三处改为薄封装。action_hotkey 类已用 action_hotkey_slot 单独泛化,不动。
语义不变。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 13:14:31 +08:00
吕柏青andClaude Opus 4.8 db51e95fde refactor(asr): 抽出共享 pcm_duration_ms
(pcm.len()/2)*1000/16000 这段时长换算原本在 foundry/sherpa_runtime/
sherpa_provider/whisper/mimo 五处各自重复(带魔法数 2/16000)。收口到新
模块 asr/pcm.rs(pcm_duration_ms + pcm_duration_ms_from_bytes),各处改为
薄封装调用,参考 wav::encode_wav_16k_mono 的共享先例。行为不变。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 13:11:58 +08:00
吕柏青andClaude Opus 4.8 72f9ced533 refactor(commands): commands.rs 拆为 commands/ 域模块(glob 重导出,lib.rs 零改)
把 4857 行的单文件 commands.rs 按单一职责拆成 commands/ 下 16 个域模块
(settings / credentials / providers / history / dictionary / dictation /
style_packs / permissions_cmds / qa / hotkeys / local_asr / foundry_asr /
sherpa_asr / marketplace / github_oauth / misc)。纯机械式、行为保持的剪切 +
可见性提升,未改动任何 #[tauri::command] 函数体。

- commands.rs → commands/mod.rs:内含 `mod <domain>;` + `pub use <domain>::*;`,
  glob 重导出每个域模块。`commands::<name>` 路径全部不变 → lib.rs 的
  generate_handler! 清单(346-492)与所有类型/helper 引用零改动。
  #[tauri::command] 生成的 __cmd__<name> 伴生项随 glob 一并重导出。
- 跨域共享项提到 mod.rs 并 pub(crate):is_valid_session_id /
  is_valid_local_pack_id、open_path_in_file_manager(3 个 cfg 变体)、
  共享状态类型(MicrophoneMonitorState / TrayMicrophoneMenuState 等)、
  常用 crate 级 import 以 `pub(crate) use` 重导出供子模块 `use super::*` 取用。
- MARKETPLACE_BASE_URL 提为 pub(crate)(misc::check_network 引用);
  hotkeys.rs 的 reject_* 整套 + sync_dictation_hotkey_legacy_fields 保留在
  hotkeys.rs 但提 pub(crate)(被 settings::persist_settings、qa::set_qa_hotkey 引用);
  style_packs.rs 的 activate_builtin_style_mode 等保留并 pub(crate)(lib.rs 引用)。
- 测试:保留单个 tests mod 在 mod.rs,把 `use super::{...}` 改为 per-module
  路径,被测 helper 提为 pub(crate)。不改任何测试逻辑。
- sherpa_onnx_asr_* 整组 #[cfg(windows)],非 Windows 平台 glob 重导出无人引用,
  对该 `pub use sherpa_asr::*` 加 #[allow(unused_imports)]。

验证:cargo check 0 error;cargo test --lib 417 passed(基线不回归);
npm run build 通过;capsule Spaces / hotkey-injection 契约测试通过。
命令面 127 个 #[tauri::command] 与原文件逐一对齐,无新增/缺失/重复。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 13:05:53 +08:00
吕柏青andClaude Opus 4.8 7a5ae5fbe2 test(coordinator): capsule-spaces 契约测试改读 ime_insertion.rs
show_capsule_window_no_activate 随 SRP 拆分搬到 coordinator/ime_insertion.rs,
契约函数本身未改;测试路径与 pub(crate) 前缀同步更新。npm run check:macos-capsule-spaces 通过。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 12:44:18 +08:00
吕柏青andClaude Opus 4.8 c228ce87ad refactor(coordinator): 把 #[cfg(test)] mod tests 移到 coordinator/tests.rs
机械式行为保持:把 ~850 行单元测试模块体搬到独立文件,父模块改为 #[cfg(test)] mod tests;
声明。tests 里的 super::* / super::dictation::* 从子文件解析路径一致,417 测试不变。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 12:40:21 +08:00
吕柏青andClaude Opus 4.8 682603077a refactor(coordinator): 抽出 qa_session 子模块
机械式行为保持拆分:从 coordinator.rs 搬出划词语音问答会话生命周期 —— QA 录音 error
监听、begin/end_qa_session、错误/静默/取消收尾。与 qa.rs(热键/面板状态)互补。
end_qa_session 跨 await 持/放 qa_state 锁、驱动 SSE 流与窗口焦点 marshaling 原样保留。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 12:37:58 +08:00
吕柏青andClaude Opus 4.8 a21e46cbb9 refactor(coordinator): 抽出 asr_setup 子模块
机械式行为保持拆分:从 coordinator.rs 搬出 ASR 引擎构建与生命周期 —— 麦克风/凭证
前置校验、本地模型释放调度(Foundry/sherpa/local-qwen3)、QaAsrStart provider 构建、
全局超时常量与各 provider 超时、音频线程驱动的 DeferredAsrBridge。schedule_*_release
session 守卫与 DeferredAsrBridge attach/flush 交错原样保留。ActiveAsr 及 QaAsrStart/
DeferredAsrBridge 的跨模块方法提升为 pub(crate);ActiveAsr/分类器留在父模块。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 12:35:18 +08:00
吕柏青andClaude Opus 4.8 f9a531be6b refactor(coordinator): 抽出 llm_pipeline 子模块
机械式行为保持拆分:从 coordinator.rs 搬出 LLM 润色/翻译/provider 构建/凭证读取管线
—— 流式与一次性润色、润色+翻译合并、各 ASR/LLM provider 凭证读取与 ActiveLLMProvider
构建、provider 分类器、中文字形偏好、词库 phrases/hotwords 读取。polish-translate
marker 常量经 glob 重导出供父模块 tests 的 super:: 引用。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 12:30:40 +08:00
吕柏青andClaude Opus 4.8 0430c3ec32 refactor(coordinator): 抽出 capsule 子模块
机械式行为保持拆分:从 coordinator.rs 搬出 capsule HUD 呈现层 —— 显示策略、
emit_capsule 状态广播、布局快照/底部居中定位、自动回 Idle 调度,及相关 const/static。
emit_capsule 的 ~30Hz 音频回调线程 run_on_main_thread marshaling 原样保留。
CapsuleLayoutState 经 glob 重导出供 Inner 字段类型可见。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 12:25:33 +08:00
吕柏青andClaude Opus 4.8 64e97f5a8c refactor(coordinator): 抽出 ime_insertion 子模块
机械式行为保持拆分:从 coordinator.rs 搬出 Windows IME 会话/TSF 插入/非 TSF 回退,
以及焦点目标抓取恢复、前台 App 抓取、capsule 窗口 no-activate 显示/隐藏等 OS 级窗口
插入管线。大量 unsafe + Win32/objc2 FFI 与 cfg 分支、SAFETY/issue 注释原样跟随。
PreparedWindowsImeSessionSlot 留在父模块(Inner 字段类型)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 12:22:11 +08:00
吕柏青andClaude Opus 4.8 90e74b84d2 refactor(coordinator): 抽出 hotkey_supervisors / voice_agent_hotkeys 子模块
机械式行为保持拆分:从 coordinator.rs 搬出听写/QA/combo/翻译/action/window
热键的 supervisor/bridge/事件循环,以及 Less Computer 语音 Agent 热键簇。
保持 use super::* 重导出契约(pub(crate) use 子模块::*),Inner/ActionHotkeyKind
随之提升为 pub(crate)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 12:14:16 +08:00
吕柏青 17e69f217a Merge origin/beta into feat/fast-agent(解决 insertion/App/main 冲突) 2026-06-07 11:54:58 +08:00
吕柏青 31580b7cbe fix(less-computer): 护栏复审跟进(deny 家目录改 ~/ 前缀/控制台去 WebFetch/git clean 等价/补测试) 2026-06-07 11:49:23 +08:00
吕柏青 bbce0013b1 fix(less-computer): 修复云审护栏安全问题(写失败 fail-closed/deny 等价放行/deny 覆盖/钳制 bypass/去 WebFetch/Starting 熄 glow/删死代码) 2026-06-07 11:36:20 +08:00
吕柏青 8c1e4df764 fix(less-computer): polish agent capsule UX 2026-06-06 20:35:48 +08:00
吕柏青 120cc152dc fix(less-computer): 全屏描边贴边修复(逻辑坐标+菜单栏层级)+苹果式高斯柔光(细带blur内化)+按下即亮; 弹框可拖动(顶栏拖把手+resize原位锚定); agent超时120→300s/预算0.5→2$ 2026-06-05 21:12:48 +08:00
吕柏青 cf933cb347 fix(less-computer): 长按松手停录音(latch不再被5s轮询重置) + 全屏弧形彩虹描边浮层(点击穿透/无缝) + 流式进场动画 + 文案 2026-06-05 20:52:49 +08:00
吕柏青 93c54ca2d5 feat(less-computer): 连续对话(--continue 续会话+多轮气泡) + 彩虹跑马灯描边/流动呼吸背景 2026-06-05 19:28:24 +08:00
吕柏青 26e7c48289 fix(less-computer): 把 less-computer 窗口加进 capabilities,否则 webview 收不到事件也调不了命令(卡片空白根因) 2026-06-05 16:14:25 +08:00
吕柏青andClaude Opus 4.8 d6ca97d493 feat(less-computer): streaming chat popup window + inline approval scaffolding
Adds a macOS-only fluid chat popup (window label=less-computer) for the Less Computer voice agent. Streams user bubble + assistant Delta + tool chips + final result/cost as the agent runs, instead of only showing the final capsule. Content-adaptive height (bottom-anchored, clamped 120-520, scrolls past max), non-movable, always-on-top, frost aesthetic mirroring the QA panel. Esc / close button dismiss. High-risk guard hits surface an inline Approve/Deny card; Approve re-runs the task once with that pattern lifted from the deny list. Windows/Linux keep it hidden and inert (no hotkey, no emit). i18n added to all 5 locales.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 15:07:08 +08:00
吕柏青 f735ae9155 chore(less-computer): 精简文案 desc 与 voiceHotkeyDesc 缩短 5 语言 2026-06-05 14:42:30 +08:00
吕柏青 5546e50db5 feat(settings): 模型改抽拉下拉(Haiku/Sonnet/Opus,不用记ID)+ Cloud 控制台默认折叠 2026-06-05 14:34:20 +08:00
吕柏青 5ee5cb1df8 feat(less-computer): 处理态文案 thinking 改为 using(Agent 操控电脑时);并入 origin/beta 的 bug 修复 2026-06-05 14:31:05 +08:00
吕柏青 4aa293e1e0 Merge remote-tracking branch 'origin/beta' into feat/fast-agent 2026-06-05 14:22:12 +08:00
吕柏青 8fd3ca4a19 fix(less-computer): Agent 真正动手干活——放行+护栏(acceptEdits + Bash/工具 + 高风险拦截)
根因:run_voice_agent_transcript 之前硬编码 Plan 只读 + 无工具,所以 Agent 只能返回文本、
根本不会真的开 Chrome / 跑命令。改成项目既有的「放行 + 护栏」动作模式:
- permission_mode = acceptEdits(尊重高级里的设置;coding_agent_mode_from_pref 映射)
- allowed_tools = Bash/Read/Edit/Write/Glob/Grep/WebFetch/WebSearch(能真正动手)
- 护栏 settings:deny 高风险(rm -rf / sudo / 强推等)自动拦截 = 用户选的「混合:仅高风险」MVP
- cwd = workdir 或 $HOME(--add-dir 限定文件作用域);运行前 git 快照便于回滚;用后清理临时护栏文件

真实链路实测(claude v2.1.161,haiku):让它「在 /tmp 建 lesscomputer_probe.txt 内容 ok」
→ 文件真的被创建、result=success/「done」、15.8s/$0.05。证明无头链路确实会执行动作。
「开 Chrome」走同一条 Bash 路径(open -a)。

注:交互式弹审批(高风险在弹框点同意)+ 自适应弹框 UI 为后续;当前高风险=自动拦截。
cargo check 0 / less_computer + guard + autonomous_prompt 单测全过。
2026-06-05 14:13:44 +08:00
吕柏青 dae966ffd1 feat(less-computer): 「按住说话键」挪到 通用→快捷键,去掉高级里的重复
- 通用 → 快捷键 新增 Less Computer 行(macOS):可直接启用并录制按住说话键,
  和听写/翻译/QA/切风格/打开应用一排,用户一眼就能看到(之前埋在高级里)。
  启用按钮一键置 codingAgentEnabled=true + 默认键,保存即触发后端重新注册。
- 高级 → Less Computer 移除重复的按住说话键行,只留后端/模型/权限/工作目录。
- tsc 0。
2026-06-05 13:30:01 +08:00
吕柏青 0082e0bf0e feat(less-computer): 专用语音键(Codex 实现,已审核)
把「长按听写键」方案换成独立专用键 coding_agent_voice_hotkey(默认 LeftControl,
支持 Ctrl/Option/Fn 等单修饰键,亦支持组合键),按住说话、松开处理:
- 单修饰键 → 第二个 CGEventTap(HotkeyMonitor,Hold 模式);组合键 → ComboHotkeyMonitor
- handle_less_computer_pressed/released:按下开始录音并标记 voice_agent,松开 end_session
  → 复用既有 run_voice_agent_transcript(autonomous_prompt + Plan 只读)
- 完整冲突校验(Less Computer vs 听写/QA/翻译/切风格/打开应用)
- 移除旧 coding_agent_hotkey(global-hotkey panel/quick)模块;macOS-only
- 设置 UI、i18n(5 语言)、mock/types 同步

审核结论:后端实现完整、已测试可跑。65 coordinator + 20 coding_agent 单测全过
(含 less_computer_press_emits_visible_capsule、冲突校验测试)。

注:当前 Less Computer 配置在「高级」里,下一步按用户要求挪到「通用」并做弹框 + UI 整理。
2026-06-05 13:25:57 +08:00
吕柏青 ff918ece31 feat: 发给无头 Claude 的需求统一包一层「一次性完成」自动化前置说明
无头 claude -p 单次运行没有多轮兜底,模型中途提问/给半成品这一轮就废了。新增
coding_agent::autonomous_prompt(task):把真实需求包进目标驱动(/goal 式)的自动化指令——
要求先定目标与完成标准、自主连续执行到彻底完成、不提问、不给计划/半成品、长任务也在本次跑完、
只回最终结果。语音 Agent 改用它构造 prompt。

验证:autonomous_prompt 单测过 / cargo check 0 error
2026-06-05 10:53:45 +08:00
吕柏青 e9e7ba50c4 fix: 长按阈值从会话开始计时,扣除 ASR 握手耗时
arm_voice_agent_long_press 之前在 begin_session(含 ASR 握手)之后才睡 550ms,
握手慢时实际长按阈值被拉长、判定不稳。改为从 state.started_at(≈按下时刻)算起
睡剩余时间,阈值稳定。
2026-06-05 10:47:55 +08:00
吕柏青 ee50665278 feat: 长按听写键唤起 Cloud Agent 语音;移除 ⌘⇧J/⌘⇧Enter 组合键
按用户新交互方案:toggle 听写模式下,长按右 Option(>550ms)把当前会话升级成
语音 Agent —— 录音→ASR→把转写交给无头 Claude 跑任务→结果弹胶囊(不插入光标)。
短按仍是普通听写,行为完全不变。

实现(最大化复用、最小化对核心听写的改动):
- coordinator_state: SessionState 加 voice_agent 标志,begin_session_state 必复位(安全护栏 + 单测)
- dictation: handle_pressed(Toggle,Idle) 武装长按计时器;计时器到点且仍按住→置 voice_agent;
  handle_released(Toggle) 若 voice_agent→end_session(按住说话语义);
  end_session 拿到转写后分流到 run_voice_agent_transcript(Plan 只读、安全 MVP)
- 退役组合键:supervisor 清空 panel/quick 绑定并停止注册;移除两个设置行,
  改为只读提示「长按听写键」;删除已无用的 sanitize_agent_hotkeys + 测试 + default_quick
- i18n(5 语言):新增 agentVoiceHint / agentVoiceTrigger

限制(已知):仅 toggle 听写模式接长按;hold 模式的「点按=Agent」未接。结果走胶囊,
长文本展示有限。权限固定 Plan 只读,不产生副作用。

验证:cargo check 0 error / coordinator 64 + dictation 33 + coding_agent 23 单测全过 / tsc 0
2026-06-05 10:45:16 +08:00
吕柏青 6c8e2843f2 fix: Cloud Agent 占位提示改为中性色(不再是红色报错)
handle_coding_agent_panel 之前用 CapsuleState::Error(红色),用户读成"报错"。
其实只是"敬请期待"信息提示 → 改用 Done(中性色)。同步更新集成测试断言。

日志确认:之前的红色就是这条占位提示,并非真实崩溃。
2026-06-05 10:30:26 +08:00
吕柏青 1b2a15dd75 test: 加胶囊内省 seam + 集成测试证明「按下面板键→弹可见胶囊」
emit_capsule 在校验 app 句柄前记录 last_capsule_state(廉价单锁,用户行为不变),
让无 GUI 测试能断言热键事件确实触发了可见胶囊。新增集成测试 coding_agent_panel_
press_emits_visible_capsule:开启功能→驱动 handler→断言下发 Error 胶囊(开发中提示),
headless 复现并验证目标 2(a)「按下有反馈」。

验证:cargo test coding_agent 27 passed / cargo check 仅余既有 warning
2026-06-05 10:09:01 +08:00
吕柏青 51d158754c test: 自愈逻辑抽成纯函数 sanitize_agent_hotkeys + 4 个单测
把 supervisor 里内联的「无效单修饰键→有效组合键」自愈逻辑抽成可测纯函数,
新增单测复现用户故障(Right ⌘ 面板键 → 自愈为可注册的 ⌘⇧Enter),并覆盖
快取键、有效组合键不动、None 不动等分支。

验证:cargo test coding_agent_hotkey 7 passed / tsc ✅
2026-06-05 10:01:21 +08:00
吕柏青 b532ef9033 fix: 快速 Agent 热键自愈 + 统一停用旋钮布局
目标 2(快速 Agent 完全不生效)根因:用户把 Cloud Agent 绑成单修饰键
"Right ⌘",而 global-hotkey 无法注册单修饰键 → 注册无限失败、按下无任何反应。

- coding_agent_hotkey: 单个坏 binding 只跳过、不再拖垮整体注册,也不再无限重试空转
- coordinator: 启动时自愈无效绑定(单修饰键)→ 换成有效组合键(⌘⇧Enter/⌘⇧J)并持久化+通知前端
- types: 新增 default_coding_agent_quick_hotkey,panel/quick 默认值供自愈回退
- ShortcutRecorder: 新增 comboOnly(拒绝单修饰键并提示)+ 内联停用旋钮,
  停用按钮紧贴录制按钮左侧成组靠右(两个旋钮布局)
- ShortcutsSection: 切风格/打开App/选区问答/选中文本润色/Cloud Agent 全部统一为内联停用旋钮
- i18n(5 语言): 新增 settings.recording.comboNeedKey;选中文本润色标签去掉"(Claude)"后缀

验证:cargo check ✅ / cargo test(hotkey+binding) ✅ / tsc ✅ / hotkey-injection gate ✅
2026-06-05 09:51:25 +08:00
吕柏青andClaude Opus 4.8 8211dea3d7 feat(coding-agent): 拆分两功能 — 选中润色(快取用键)+ Cloud Agent(语音键,占位);热键移到通用设置
- 快取用键 = 选中文本润色:选中→Claude 润色→回插替换;全程胶囊反馈
  (没选中提示"请先选中文本";润色中/完成/出错都显示),纯文本 plan 只读、无 guard、便宜可靠。
- 面板键 = Cloud Agent(语音→ASR→Claude→结果弹窗),完整流程开发中,先给可见提示。
- 两个热键从「高级」移到「通用→快捷键」,停用按钮置于录制器左边、常驻行。
- CodingAgentSection 精简为开关/后端/模型/权限/工作目录 + 热键位置提示;5 语言 i18n。

cargo check + tsc + vite 均绿。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 09:21:35 +08:00
吕柏青andClaude Opus 4.8 0463c30282 feat(coding-agent): 接线快取用执行链路(热键 → 选中 → 无头 Claude → 回插)
- 新增 coding_agent_hotkey.rs:双绑定全局热键监听器(面板键 + 快取用键),
  镜像 qa_hotkey,3 单测。
- coordinator 接线:Inner 加 coding_agent_hotkey;新增 supervisor(仅
  coding_agent_enabled 时注册)+ bridge loop + handler;lib.rs 启动调用。
- handler:capture_selection → 护栏化 run_claude_agent(sonnet 默认、deny 清单、
  git 快照、成本封顶)→ TextInserter 回插光标。本版面板键暂同走快取用,
  语音 + 面板随后续版本。

cargo check 绿;coding_agent 全模块 22 单测通过。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 08:34:04 +08:00
吕柏青andClaude Opus 4.8 d8dc54695d feat(coding-agent): 语音 Agent 设置 UI(开关 + Claude/OpenCode 选择 + 双热键 + 模型/权限/工作目录)
- UserPreferences 新增 7 字段(coding_agent_*):启用开关、后端 provider、模型、
  权限模式、工作目录、面板热键、快取用热键;贯穿 struct/Wire/双向转换/默认。
- 前端 types.ts/ipc.ts 同步类型与 mock;CodingAgentPermissionMode 收敛到 types.ts 单一来源。
- 新增 CodingAgentSection(设置→高级):启用开关、Claude/OpenCode 选择、两个
  ShortcutRecorder(可启用/停用)、权限模式、模型、工作目录。
- i18n 全 5 语言新增 settings.codingAgent.*。

热键实时注册与触发执行链路(coordinator 接线)随后续提交。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 08:20:56 +08:00
吕柏青 d5756db8a6 Merge remote-tracking branch 'origin/beta' into feat/fast-agent 2026-06-05 08:08:50 +08:00
吕柏青 83e4135175 chore(capsule): #470 诊断 v2 —— 给胶囊不显示的暗点补一次性日志(零行为改动)
#470「Windows 语音输入看不到录音胶囊」根因仍未定(PR #471 已明说只加诊断、需真机
日志;byte92 报 macOS 也复现,疑跨平台)。本 PR 不改任何 show/hide/position 行为,
仅在几个静默路径补日志,把根因收敛到 A/B/C 单一分支,帮助拿日志定位:

- coordinator.rs show_capsule_window_no_activate(Windows):两处 window_handle() /
  RawWindowHandle::Win32 取不到时此前静默 return false,补 warn(Win32 show 失败最可能的暗点)。
- coordinator.rs emit_capsule:get_webview_window("capsule") 取不到时此前静默 return,
  补一次性 warn(暗点 A0:窗口压根没创建/已销毁)。沿用 AtomicBool 一次性门,避免 ~30Hz 刷屏。
- lib.rs position_capsule_bottom_center(Windows):定位只夹了上边(.max(mon.top)),
  多显示器/负坐标/异常 DPI 下可能算到屏幕外却无观测,补显示器几何 + 最终落点 debug log。
- Capsule.tsx:webview 收到第一个 capsule:state 事件时打一次 console.info,区分
  「后端没 emit」与「emit 了但窗口没显示/没渲染」。

注:Windows-cfg 部分本机无法交叉编译验证(ring 的 C 代码需 MSVC),依赖 Windows CI。
2026-06-04 20:45:47 +08:00
吕柏青 d8ec007fcd feat(hotkey): 全局快捷键可停用/清空开关 (#576)
0602 版本默认注册的「切换风格 / 唤起 App」全局快捷键无法关闭,与其他软件冲突
且会拦截软件内快捷键。本 PR 复用 QA 快捷键已有的 `Option<ShortcutBinding>=None`
停用模式,给这两个 action 键加上「可清空停用」能力。

默认仍开启(Some 默认键),对老用户零行为变化,仅新增可关。

后端:
- types.rs: 内层 AppPrefs switch_style_hotkey/open_app_hotkey 改 Option;default fn
  返回 Some(默认键);Wire::default 改携带 Some(缺字段=启用);from-wire 去掉
  unwrap_or_else 直传 Option(None=停用不再被塌缩成默认键——这正是「无法关闭」的根因)。
- coordinator.rs: action_hotkey_binding 返回 Option;supervisor / update 加 None 反注册
  分支;reset-latch 对 Option 解包。
- commands.rs: set_switch_style_hotkey/set_open_app_hotkey 接受 Option(None=停用,跳过
  validate/overlap);所有 reject_*_overlap 读取处对 Option 解包(停用项不参与冲突检测)。

前端:
- types.ts/ipc.ts: switchStyleHotkey/openAppHotkey 与 setter 改 nullable。
- ShortcutsSection: 有值→录制器+「停用」按钮;停用→「启用」按钮恢复默认(仿 QA 行)。
- lib/hotkey.ts: defaultSwitchStyleShortcut/defaultOpenAppShortcut helper。
- 5 份 i18n 加 settings.shortcuts.enable/disable。

测试:默认启用、缺字段启用、None 往返为 null、显式绑定往返、overlap 拒绝(含停用项跳过)。
2026-06-04 20:38:40 +08:00
吕柏青 1d4a3adaad feat(asr): 支持 OpenRouter 作为 ASR 提供商 (#582)
OpenRouter 的 /audio/transcriptions 与 OpenAI 路径相同、也走 Bearer,但请求体是
application/json:{model, input_audio:{data:<base64 wav>, format:"wav"}},而非 OpenAI
官方的 multipart/form-data。直接当 whisper 兼容 provider 会被 OpenRouter 拒绝。

- whisper.rs: 新增 AsrRequestFormat{Multipart,OpenRouterJson},transcribe_chunk 按格式
  分发;OpenRouterJson 走 JSON+标准 base64(带 padding),不带 multipart 专属的
  prompt/response_format 字段。用 with_request_format builder 避免改 new() 签名(不动
  既有 4 处构造点)。
- coordinator.rs: is_whisper_compatible_provider 注册 openrouter;新增
  whisper_request_format;verbose_json 对 openrouter 保持关闭;base64 膨胀 ~33%,长
  录音按 30s 切分(同 zhipu)。
- 前端: ASR_PRESETS / AsrPresetId / Overview 名称映射 + 5 份 i18n 加 openrouter。
- 测试: OpenRouterJson 发 JSON body 单测 + 注册/格式断言;既有 transcribe 测试不变。

默认 model 用报告人已验证的 openai/whisper-large-v3-turbo。
新增直接依赖 base64=0.22(此前已是传递依赖,未引入新 crate)。
2026-06-04 20:19:40 +08:00
吕柏青 14c3874eb8 fix(overview): port 凭据保存后状态不刷新修复到 beta (#573)
issue #532/#573:在 Settings 填写/更新凭据但不切换提供商时,Overview 页
原有的 useEffect 不会重跑,asrConfigured 仍停留在「未配置」。

该修复曾随 #538 合入 main,但被 beta→main 合并覆盖,beta(开发主分支)上缺失,
导致用户(如 Ubuntu 24.04)配好火山引擎 ASR 后界面仍显示「未配置」。

- commands.rs: set_credential 保存/删除成功后 emit "credentials:changed"
- Overview.tsx: 监听该事件调用 refreshCredentials()(复用 credentialsRequestSeq 防竞态,
  比 #538 原版裸 getCredentials() 更稳)
2026-06-04 20:07:56 +08:00
吕柏青andClaude Opus 4.8 333ba3dd1c feat(coding-agent): 快速 Agent 引擎 + Claude 控制台(无头 Claude Code)
issue #579 骨架的首个垂直切片(UCW 版):只接 Claude Code、放行 + 护栏。

后端 coding_agent/ 模块(19 单测全绿,已对真实 claude v2.1.161 验证链路):
- args: claude -p 无头流式 argv 构造
- stream: stream-json 逐行解析为 CodingAgentEvent
- guard: 高风险命令分类 + acceptEdits + deny 清单护栏 settings
- detect: 解析 claude --version / mcp list(computer use 检测口径)
- mod: tokio 异步运行器(超时 / 取消 / stderr 排空)+ git stash create 快照
- commands: detect / run_test(护栏流式)/ cancel / command_risk,注册入 lib.rs

前端 Claude 控制台(设置 → 高级):
- 检测面板 + 终端观感流式输出 + 用量;权限模式 / 工作目录 / 可执行路径
- ipc 封装 + 类型 + 5 语言 i18n;控制台测试默认 sonnet 控成本

未含(后续阶段):语音双热键 + coordinator 接线 + 面板 + prefs 持久化。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 19:52:43 +08:00
吕柏青 5cf015de17 docs(readme): 实际效果示例换成客户回邮件用例(中英双语)
把「一个实际例子 / A concrete example」从未充分测试的 SQL→ChatGPT 演示
换成经过测试的客户回邮件用例:口语输入 → 结构化清理稿,更贴合
OpenLess「只清理你的话、不替你作答」的定位,宣传与实际相符。
收束句由「回车发送」改为「粘贴给 ChatGPT/Claude 续写」,标语保持不变。
2026-06-04 19:31:15 +08:00
吕柏青 ace7fe47ab docs(readme): 顶部加入 Discord 社区入口(badge + 社区行,中英双语) 2026-06-04 13:20:32 +08:00
吕柏青 bd435b9e5e chore(release): bump version to 1.3.6-2 (Beta) — 翻译会话参与对话感知润色(#583) + 快速录音提示音丢失修复(#584) + 正式发版自动更新 Homebrew cask(#586) 2026-06-04 13:17:17 +08:00
吕柏青 4c31448cb9 test(polish): 补 Case 4 单测——普通历史在翻译轮喂 final_text (@claude review) 2026-06-04 12:59:08 +08:00
吕柏青 18ffbb0e48 fix(ci): cask intel sed 锚定 sha256 行,避免误改 arch 指令 (@claude review)
裸 s/intel: "..."/ 会同时命中 cask 第2行 arch arm: "aarch64", intel: "x64",把 arch 的 intel 改成哈希 → Intel Mac 用户 brew 安装 404。改用 ^[[:space:]]*intel: 锚定 sha256 块行(捕获组保留缩进),并加防呆:arch 指令被破坏或 sha256 没写进去就让 job 失败。本地模拟新旧 sed 对比已验证。
2026-06-04 12:57:31 +08:00
吕柏青 6b28d42ea1 chore(cask): openless 补到当前正式版 1.3.5
cask 此前停在 1.3.4(自动更新一直没生效),Homebrew 用户拿不到 1.3.5。用 v1.3.5-tauri release 资产 sha256 一次性补齐。
2026-06-04 11:50:32 +08:00
吕柏青 0d35c811fe ci(release): 正式发版后在流水线内自动更新 Homebrew cask
原 update-cask.yml 监听 release: published,但 Release 由 release-tauri.yml 用默认 GITHUB_TOKEN 创建,不会触发该事件(GitHub 防递归)——所以 cask 从不自动更新,需手动;且其条件 endsWith('-tauri') 连 beta 也匹配。

改为在 release-tauri.yml 内新增 needs:build 的 update-homebrew-cask job:仅正式版 tag(v*-tauri 且非 -beta-tauri),构建+建 Release 后下载 DMG 算 sha256、sed 更新 Casks/openless.rb、提交回默认分支([skip ci])。删除失效且会错配 beta 的 update-cask.yml。
2026-06-04 11:50:32 +08:00
吕柏青 bff4bd727f feat(polish): 翻译会话参与对话感知润色(润色+翻译单次调用 + 按轮剔除译文)
接续上一条「按风格包隔离」,把「翻译会话直接排除出上下文」改成「保留但按需剔除」:翻译会话也参与对话连续性,又不让外语译文污染普通润色。

- 翻译改为单次 LLM 调用同时润色源文 + 翻译:polish_and_translate_or_passthrough 用合成系统提示词(翻译规则 + 追加输出润色后源文 + 两段哨兵)走现成 polish() 通道,复用对话上下文;解析失败 / 译文为空则退回专用 translate_text 拿干净译文(不丢字)。
- DictationSession 加 polish_source(serde default 兼容旧历史),存润色后的源语言文本。
- eligible_polish_context_turns 加 current_translation_active 分流:翻译历史在普通轮喂 polish_source(无则跳过=剔除译文),翻译轮喂 final_text;普通历史始终喂 final_text。
- 前后端类型同步(polishSource);补解析器 + 上下文分流单测,cargo test --lib 371 全过。
2026-06-04 11:36:35 +08:00
吕柏青 52e2e45bb2 fix(audioCue): 修复快速录音时提示音偶尔丢失
suspended 的 AudioContext 播放前需异步 resume。原实现用单个 playGeneration:快速点一下录音时 resume 还没完成、录音已结束(stopAudioCue 自增代号),挂起的播放被作废 → 提示音「有时没有」。

A 治本:新增 primeAudioCue(),胶囊挂载时预热 resume,让常见路径同步播放、绕开竞态。
B 兜底:拆 playSeq(新播放接管)/ stopSeq(发生过 stop)双序号 + 400ms 迟到阈值,抽出纯函数 shouldPlayDeferredCue 决策——被新播放接管→不播;停了且真迟到→不播;停了但 resume 很快(快速录音)→仍补响一声。

补 shouldPlayDeferredCue 单测(含边界);tsc --noEmit 通过。
2026-06-04 09:23:15 +08:00
吕柏青 8bc1583c98 fix(polish): 对话感知上下文按风格包隔离 + 排除翻译会话
对话感知 polish 回看最近历史作为 LLM 上下文时,之前复用所有成功会话,导致两类串味:

1. 切换风格包后,旧风格包的历史仍被带进新提示词,把当前风格带偏;
2. 翻译会话的译文/指令污染下一段普通润色(如中文输入被带成英文)。

给 DictationSession 增加 style_pack_id / translation_active 两字段(serde default 向后兼容旧历史),抽出 eligible_polish_context_turns 过滤:仅复用同一 active 风格包、且非翻译的成功会话;旧历史无 style_pack_id 时保守排除。前后端类型同步,补两条单测。
2026-06-04 09:12:33 +08:00
吕柏青 08ad6b7979 docs(readme): 突出风格市场 + 流式输出,状态更新到 v1.3.6 2026-06-01 18:00:22 +08:00
吕柏青 d1a5b75c2b chore(release): bump version to 1.3.6-1 (Beta) — MediaPlayPause 线控录音 + Toggle 冷却(#545) + 多屏胶囊跟随(#569) + 本地模型管理(#559) + Linux 磨砂回退(#553) 2026-06-01 15:08:50 +08:00
吕柏青 acac9f67d9 chore(release): bump version to 1.3.5-1 (Beta) — 录音开始合成提示音
本次只动版本号,功能改动已在 #560 落盘:
- 按下录音热键进入 recording 即播放 Web Audio 合成提示音,提醒「已开始录音」
- 新增 audioCueOnRecord 开关(默认开启),录音与输入设置可关闭 + 试听
2026-05-31 14:02:47 +08:00
吕柏青 2784e9764b fix(audioCue): resume 期间被叫停则作废本次播放(pr_agent 评审)
suspended 的 AudioContext 先 resume 再排期,若等待 resume 期间录音已结束
(stopAudioCue)或有新一轮播放,旧的 resume 回调不再排期,避免「录音已停、
提示音晚到」。引入 playGeneration 代号:stopAudioCue 自增并停节点;
scheduleCueVoices 只 stopVoices 不动代号;deferred 播放回调比对代号决定是否排期。
2026-05-31 09:44:25 +08:00
吕柏青 84a96a3975 feat(capsule): 录音开始播放合成提示音 + 录音与输入设置开关
按下录音热键进入 recording 状态时,capsule 窗口用 Web Audio API 即时合成
一段上升双音提示音,提醒「已开始录音」;离开 recording 即停。提示音独立于
showCapsule —— 胶囊隐藏时仍会响。

- 新增 audioCueOnRecord 偏好(默认开启),可在「录音与输入」设置里开关,
  并提供「试听」按钮即时听效果。
- 后端 UserPreferences / UserPreferencesWire 持久化该字段并默认 true,
  老配置缺字段按开启;补两条单测(缺字段默认开启 + 显式 false 往返保真)。
- 提示音合成抽成 lib/audioCue.ts:playRecordStartCue / stopAudioCue 两个
  操作 + 纯函数音符规划(recordStartCueTones / cueTotalDurationMs),附
  .test.ts。suspended 的 AudioContext 先 resume 再排期,无 Web Audio 时静默降级。
- i18n 五语(en / zh-CN / zh-TW / ja / ko)补齐 label / desc / preview。
2026-05-31 09:39:29 +08:00
吕柏青 9746bb2ae8 chore(homebrew): bump cask to 1.3.4
Update OpenLess Homebrew cask from 1.2.23 to 1.3.4 (latest stable
release v1.3.4-tauri, published 2026-05-22). SHA256 checksums for
both aarch64 and x64 DMGs verified by local download.
2026-05-28 21:31:42 +08:00
吕柏青 b7c6217519 chore(release): bump version to 1.3.4-14 (Beta) — Linux fcitx5 状态弹窗 + Sherpa 本地 ASR 状态同步 + DashScope 重复修复
本次只动版本号,逻辑改动已在以下 PR 落盘:
- #520 Linux fcitx5 候选框听写状态提示 + AppImage 插件自动安装 + 热键启动容错
- #523 Sherpa 本地 ASR 状态同步修复(active settings 顺序 + Qwen3 tokenizer 缓存校验)
- #527 Sherpa 本地 ASR asset 与 state handling 修复
- DashScope (bailian) ASR 部分语句重复输出修复
- pr-agent.yml CI 配置调整
2026-05-25 20:55:51 +08:00