Chris233
fc38edd1a3
refactor: consolidate Core 2.0 and restore cross-platform behavior ( #1019 )
...
* refactor: split Linux egui host from shared backend
* fix: keep CI contracts portable across runners
* fix(ci): preserve CRLF while removing qwen dependency
* fix(ci): regenerate target lockfile and install ripgrep
* fix(ci): gate desktop-only selection voice module
* ci: limit native build parallelism on cross-platform runners
* fix(host): expose blocking task seam to coordinator
* fix(settings): avoid macOS default hotkey collision
* docs: record cross-platform CI evidence
* ci: expose Linux validation package artifact
* ci: grant reusable Linux package permissions
* refactor: remove legacy coordinator dictation facade
* ci: skip unsupported MSI prerelease builds
* ci: install minisign only for signed Linux releases
* fix: keep test-only coordinator session helper
* refactor: move style preview diagnostics into core
* fix: import shared preferences type from core
* refactor: centralize enabled vocabulary projection
* ci: install ripgrep for Linux package gates
* test: make source contract line-ending agnostic
* fix: keep Linux desktop entry standards compliant
* docs: expose core style diagnostics contract
* ci: resolve Linux package version in app directory
* refactor: centralize ASR vocabulary priority
* fix: use shared vocabulary note constant
* refactor: keep vocabulary priority inside core
* test: retain starting-stop queue coverage
* ci: scope Linux updater URL to the current repository
* docs: close implementation and artifact evidence
* docs: record final CI artifact evidence
* fix(android): align updater plugin versions
* fix(android): pin updater crate exactly
* refactor(core): remove unused type anchor
* docs: record final artifact runs
* fix(remote-input): preserve beta status fields
* fix(tauri): finish beta host merge
* fix(asr): pass scoped operation to native qwen adapter
* fix(macos): bundle mlx metallib as a resource
* test(remote-input): update status wire contract
* feat: share Less Computer voice flow with Linux
* refactor(core): centralize host-independent runtime policy
* refactor(core): complete 2.0 backend consolidation
* fix(ci): prepare Sherpa after cache restore
* fix(macos): route native model paths through Core store
* fix(macos): use ModelStore catalog API
* fix(ci): serialize macOS native test build
* test(ci): preserve macOS cargo diagnostics
* fix(macos): release input source lock before await
* test(core): replace legacy hotkey injection gate
* test(core): move Codex sandbox contract into Core
* fix(core): close final runtime policy gaps
* fix(windows): terminate coding agent process trees
* fix(core): complete local model migration
* fix(credentials): align Core LLM model account
* fix(core): restore 1.x parity across shared backend
* fix(android): complete shared backend stubs
* fix: close cross-platform migration review gaps
* fix: complete migration lifecycles and platform compatibility
* docs: define desktop 2.0 acceptance and Linux handoff
* fix: close desktop parity and session lifecycle gaps
* fix: finish voice lifecycle and desktop feedback parity
* fix: close desktop cancellation and feedback race gaps
* fix: honor proxy settings and reject stale session effects
* fix: dispatch native cleanup on host runtimes and order events
* fix: keep optional recording effects best effort
* fix: close desktop agent model and QA lifecycle gaps
* fix: preserve desktop raw voice and QA turn ownership
* fix: finalize desktop dictation and QA feedback
2026-09-06 18:39:46 +08:00
bigsong
aa0acf8827
chore(dsh): 跟上游插件改名 dsh-events,并删掉一条会随机变红的联机验收 ( #980 )
...
* rename: 上游插件改名 dsh-jsonl -> dsh-events,同步 vendor 与常量
插件仓库改了名。`jsonl` 说的是输出格式,不是它给你什么——目标用户是想把 dsh 接进
GUI / 编辑器 / CI 的人,他们脑子里的词是「事件」,不是「JSON Lines」。而且别家都不这么
叫(Claude Code 是 --output-format stream-json,Codex 是 --json,OpenCode 是
--format json),有人来找这个能力时搜的是 "dsh json output"。
趁 npm 还没发布、还没对外宣传时改,代价只有改几个文件。旧 URL 有 GitHub 重定向兜底,
但留着迟早会成为断链。
跟着改的:vendor 文件名、同步脚本名、两个常量、环境变量名、模块文档与注释里的链接。
vendor 文件是用改名后的脚本重新生成的,不是手改——review 时加进来的来源与 MIT 许可头
原样保留。
* test(dsh): 删掉一条红绿取决于模型心情的联机验收
`tmp_is_writable_by_design_and_we_cannot_narrow_it` 想把「$TMPDIR 可写且收不掉」这个
dsh 既有行为钉成契约。但验证它必然要让模型去写文件,而 dsh 有**两道松紧不同的围栏**:
bash(seatbelt 子进程沙箱) 工作目录 + /tmp + $TMPDIR
write / edit(进程内 fs 围栏) 只有工作目录
prompt 里写了「用 bash 执行」,模型照做就绿,改用 write 就红。实测两种都出现过:一次
文件真落下去(bash),一次模型回 DONE 但文件根本没建(write)。这条测试的红绿反映的是
模型当次的选择,不是被测系统的行为——正是它上面那条注释批评过的毛病,我自己又犯了一遍。
那张「两道围栏」的表挪进模块文档,并写明为什么不做成测试。留下的
`sandbox_blocks_writes_outside_the_workspace` 打的是家目录,两道围栏都挡,不受工具选择
影响,那条才作数。
2026-08-22 02:11:25 +08:00
b7ac91f0e6
feat(coding-agent): Less Computer 接入 dsh(DeepSeek Harness),自带事件插件补齐流式与工具展示 ( #975 )
...
* fix(coding-agent): 版本号解析认预发布号
dsh 的真实输出是 `0.1.0-rc.6`,而解析器要求严格的三段全数字:它被切成
["0","1","0-rc","6"] 四段,判定失败。用这个判「装没装」的地方就会说它没装。
改成认 `-rc.6` / `+build.7` 这类预发布与构建后缀,并原样保留(用户看到的版本号才是
真的)。同时把排版噪声挡在外面:`(1.2.3)` 取到的是 1.2.3,不是 `1.2.3)`。
单测里的样例串是手抄的,抄错了测试照样绿。所以另加一条 #[ignore] 的 live 测试,直接问
本机四个 CLI 要 --version 输出再喂给解析器——那才是「设置页会不会误报没装」的可信证据。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
* fix(coding-agent): 「装没装」改看命令能否跑通,不再依赖版本号解析
上一条修的是解析器,那只是补了一个洞。根子在于判据选错了:
installed: version.is_some() // 解析不出版本号 = 判定没装
版本字符串是这些 CLI 里最容易变的东西——改排版、加后缀、换前缀,对它们都不算 breaking
change,但对「拿版本号当判据」的我们就是。
改成:命令跑通(进程起得来且 exit 0)= 装了;版本号只用来显示,解析不出来最多让界面少
显示一个号码,不会把能用的后端说成没装。三个后端共用一个 probe_cli,顺手删掉了三份
几乎一样的 detect_* 。
单测拿 `echo` 当替身——`echo --version` 会原样打印并 exit 0,正是「跑得通但读不出版本号」
这个形状,且不依赖本机装了哪些 agent CLI。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
* feat(dsh): 无头 dsh 后端,自带事件 tap 插件
dsh(DeepSeek Harness)的 headless 只在结束时往 stdout 打一行最终文本——没有 JSON、
没有逐字流、没有工具事件。但这不是它缺数据:它内部的会话事件总线上 assistant/chunk、
tool/call、turn/end 一应俱全,只是 headless 汇总完就丢了。
dsh 是插件栈架构,`--patch` 是官方留给外部叠自己那层的入口。这里挂上一个订阅
session/event 的小插件,把需要的事件规范化成 NDJSON 打到 stderr,stdout 的「只打最终
文本」契约原样不动。挂载不侵入用户的 profile,插件随运行落到临时目录、跑完删掉。
上游耦合被刻意压缩在那个 30 行的 JS 里:它把 dsh 的内部事件翻译成我们自己定义的稳定
形状,上游改字段只需要改 JS,Rust 的解析和单测都不用动。tap 是纯增益——挂不上也只是
少了逐字流和工具展示,最终结果照样从 stdout 拿得到。
prompt 不进 argv:dsh 的启动器不认 `--` 作为选项终止符(实测 `dsh --profile headless
"--version"` 会真的去执行 --version),所以以 `-` 开头的转写会被劫持成 flag。既然本来
就要写 patch 文件,索性用它直接覆盖 runner 的 task 配置,prompt 连进程列表都不会出现
——这点比另外三家都严。
护栏走 DSH_PERMISSION_MODE 三档沙箱,沙箱根 = 子进程工作目录。和 Codex 一样没有逐命令
deny 清单,所以审批卡对它同样不生效,撞墙时如实报错。
模型解析永远返回 None:headless profile 没有 --model,模型由 dsh 自己的 profile 决定,
返回 Some 只会让调用方以为选得动。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
* feat(settings): Less Computer 后端下拉加入 dsh
第四项。它的能力面和另外三家不一样,设置页如实反映,不给假开关:
- 模型那一行**整行不显示**,换成一句说明:dsh 的 headless 没有模型开关,模型由它自己的
profile 决定,在这里改不了。给个选不动的下拉比不给更糟。
- 复用 Codex 那条沙箱护栏说明(两家都只有粗粒度档位、都不弹审批卡)。
- 装没装当场检测;可执行文件 placeholder 跟着后端走。
文案 5 语言齐全。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
* refactor(dsh): tap 插件抽成独立项目 dsh-jsonl,OpenLess 改消费它的公开 schema
原来这个插件是内嵌在 dsh.rs 里的一段 Rust 字符串,只发我们自己要的 4 类事件、用我们
自己的私有形状。它要开源出去(dsh 缺结构化输出,而上游不收 PR,第三方插件是这个能力
唯一可能存在的形式),于是有了「两份会各自跑偏的代码」这个问题。
解法是保证世界上只有一份这个 JS:
- 源头在 github.com/bigsongeth/dsh-jsonl,发完整的、有文档、有版本的事件词表。
- OpenLess 用 include_str! 内嵌一份逐字副本(vendor/dsh-jsonl.js),带版本标记。
内嵌而不是依赖 npm 包:用户装机即用,不需要网络、不需要 npm i,也不碰用户自己的
dsh profile 配置。
- Rust 侧改成解析那个公开 schema(text.delta / tool.call / turn.end),不再有私有形状。
用不上的事件按 schema 的兼容性约定忽略——上游加新事件不会把我们打挂。
**跨仓库没有任何自动同步**,这点没被消除,只是变便宜了:
- scripts/vendor-dsh-jsonl.sh 把同步变成一条命令
- 单测盯着 vendor 文件的版本标记与 VENDORED_DSH_JSONL_VERSION 一致,防止有人直接手改副本
- 插件报的 schema 大版本与我们支持的不一致时留警告日志(不中止:最终结果始终从 stdout
拿得到,流式展示只是锦上添花)
- 真的对不上时,live::tap_plugin_delivers_streaming_and_tool_events 会红
顺带把「禁止任何 import」的断言放宽成「只准 node: 内置」——公开插件要写文件,用了
node:fs,而 node: 内置不需要 node_modules,不影响从临时目录绝对路径加载。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
* test(dsh): 沙箱验收的靶子选错了,它一直是靠运气过的
那条「沙箱挡得住工作目录之外的写入」把靶子放在 $TMPDIR。但 dsh 的 workspace-write 把
可写根写死成 [工作目录, "/tmp", $TMPDIR](见 @deepseek-ai/dsh-sandbox 的 writableRoots)
——往 $TMPDIR 写**本来就是允许的**。
它之前能过,只是因为模型碰巧挑了受限更严的 `write` 工具;这次挑了 `bash`,文件就真的
落下去了。也就是说这条测试的成败取决于模型当次的选择,不取决于沙箱是否失效。
改成打真正的边界(家目录),实测会被拒:
bash: /Users/…: Operation not permitted [sandbox: file access denied under workspace-write mode]
另加一条把 dsh 的既有行为钉成契约:$TMPDIR 可写,且**没有配置项能收掉**——Codex 那边
有 -c sandbox_workspace_write.exclude_* 可以收紧,dsh 没有对应物。哪天 dsh 收紧了这条
会红,那是好消息,届时删掉它并更新文档。
所以对用户的措辞只能是「撞到限制会如实报错」,不能说成「写入限制在工作目录内」。
模块文档里把这个边界写清楚了。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
* fix(dsh): 收紧运行协议与权限边界
* fix(dsh): 修复权限与连续对话审查项
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com >
Co-authored-by: Chris233 <h-chris233@outlook.com >
2026-08-22 00:14:17 +08:00
aeoform and Chris233
33467a36ea
feat(linux): 选区读取集成进 fcitx5 插件(clipboard addon),删除外部工具依赖 ( #961 )
...
* feat(linux): 选区读取集成进 fcitx5 插件(clipboard addon),删除外部工具依赖
- openless 插件新增 GetSelectionText DBus 方法:直读 fcitx clipboard addon
的 PRIMARY 选区缓存(X11 XFIXES / Wayland data-control 统一维护),与
fcitx 剪贴板模块来源一致,不触碰用户剪贴板
- CMake 增加 Fcitx5::Module::Clipboard 依赖
- Rust 侧 selection 改走插件 GetSelectionText,删除 wl-paste/xclip/xsel
外部工具路径及 linux_selection_tools_missing 提醒机制(前端
selection_warning 状态机、i18n、测试一并删除)
- acceptQaSessionEvent 的 idle 分支不再依赖 selection_warning 字段,
一律视为新会话 token(事件保序,语义等价)
- 打字提问路径(QA 面板 / less-computer)不再 emit Polishing capsule:
回答在面板内可见,避免 Linux 输入法 auxDown 闪「✨ 润色中...」
(cherry picked from commit 384f8876a490cb3e8e992af05cdad33f2af6a8ec)
* fix(linux): bundle and sync fcitx5 selection plugin
(cherry picked from commit 7e8bf825c08f5e935c7a25746c121136d92b7926)
* fix(linux): use DBus selection reader during validation
(cherry picked from commit d13403343b08fe1b4070e0f0d2f219a0a69d363e)
* refactor(linux): 收尾 PR 961 审查发现的小毛病
- capsule 反馈参数从裸 bool 改为具名枚举 CapsuleFeedback(Show/Hide):
语音/听写路径 Show、打字路径 Hide,调用点自文档化,避免将来误传
- AppImage 插件子路径提取为 APPIMAGE_PLUGIN_SUBDIR 常量并注明与 CI
bundle.resources 的契约,避免三处硬编码静默失配
- selection.rs 移除文件级 Linux 大赦(lib.rs 的 crate 级兜底仍在),
Duration import 按平台限定,消除 Linux unused 隐患
---------
Co-authored-by: Chris233 <h-chris233@outlook.com >
2026-08-19 20:34:10 +08:00
TRIP and sim
b68a5901c3
feat(selection-polish): Linux 支持——fcitx5 插件热键路由 + PRIMARY 选区校验 ( #927 )
...
* feat(selection-polish): Linux 支持——fcitx5 插件热键路由 + PRIMARY 选区校验
- linux_fcitx.rs:新增 SetSelectionPolishHotkeyRaw DBus 方法 + sync_selection_polish_binding;
start_dictation_signal_listener 增加 selection_polish_trigger 参数,监听
SelectionPolishEvent 信号 → HotkeyEvent::SelectionPolishShortcutPressed,
初始同步与 fcitx5 重启重同步均含选区润色触发键
- hotkey.rs Linux adapter:update_modifier_shortcuts 转发 selection_polish_trigger
(移除 disabled on Linux 注释)
- hotkey_loops.rs:Linux 启动 fcitx5 监听时传入 selection_polish_trigger
- selection.rs Linux 分支:is_captured = PRIMARY 读到选区即可;validate 重读
PRIMARY 与捕获文本比较(用户改选/清空即拒绝粘贴);selection_text_matches
cfg 覆盖 linux
- fcitx5 插件 openless.cpp:新增 SelectionPolishEvent 信号 +
SetSelectionPolishHotkeyRaw 方法(INI 持久化 SelectionPolishRawSym/States),
按键分发仿 QA 模式;需重装插件(scripts/inject-fcitx5-plugin.sh)生效
- 注:Linux 代码由 Linux CI 验证编译;fcitx 插件 .so 需 Linux 环境重编译安装
* fix(selection-polish): 补传 selection_polish_trigger 到 fcitx 监听器(第二个调用点)
coordinator.rs 的 hotkey 安装路径调 start_dictation_signal_listener 时漏传
selection_polish_trigger(签名 7 参数只传 6 个),Linux CI E0061。hotkey_loops.rs
已传,此处遗漏导致 Linux 编译失败。
---------
Co-authored-by: sim <sim@local>
2026-08-06 19:32:39 +08:00
leaion and leaion
0063497c6e
fix(linux): fcitx5 plugin crash on empty triggerKeyList with raw hotkey ( #883 )
...
When the raw hotkey path (SetHotkeyRaw) matches and triggerRawStates==0,
the code unconditionally indexes triggerKeyList_[0]. With an empty
KeyList (common in raw mode), this is undefined behavior and segfaults
fcitx5 on every hotkey press. Only access the list when the KeyList
path matched; use raw values for the raw path.
Co-authored-by: leaion <leaion@sohu.com >
2026-08-03 14:54:30 +08:00
93c6f6f448
fix(hotkey): 修饰键热键被当组合键用时不再误唤起听写(150ms 仲裁 + 即时撤销,胶囊不再赖着) ( #858 )
...
* fix(hotkey): 修饰键热键被当组合键用时不再唤起听写
modifier-only 触发键(如 Option)按住期间只要按下普通键,就说明用户在打
Option+任意字母/数字键这类组合键、不是想说话。此前 tap/hook 只看修饰键自身的边
沿:按下即 Pressed → 开录音,Auto 模式下快速松手还会被判成「短按锁存」,录音一直
开着停不下来。
- hotkey.rs:macOS CGEventTap / Windows WH_KEYBOARD_LL 在触发键按住期间收到普通
键 KEY_DOWN 时发一次新的 TriggerCombined 边沿(每次按住只发一次;修饰键叠加不
算,Shift 翻译修饰键行为不变),之后仍照常发 Released。
- coordinator:TriggerCombined 清掉按住态(随后的 Released 被 was_held 吞掉,不
再走 Hold 松手 / Auto 短按锁存),并只取消「这一次按下开出来的」会话 ——
toggle 停止 / 被冷却拦下 / 路由给 QA 的按下什么都不动,绝不误杀正在转写的上一
条。同时清掉冷却与防抖时间戳,紧接着那次真想说话的按下不会被静默吞掉。
- Less Computer 的 modifier 触发键走同一条撤销路径(顺带熄灭整屏描边)。
* feat(hotkey): 修饰键热键加 150ms 组合键仲裁窗口——组合键不再闪一下胶囊
上一提交是「事后撤销」:Option+任意字母/数字键仍会真的开麦、弹胶囊、烧一次 ASR
建连,然后被取消——功能对了,观感上闪一下。
改为按下后先等 150ms 再开会话,期间监听器若已报告叠加了普通键,这次按下整条作废:
麦克风不开、胶囊不弹。等待只加在 modifier-only 触发键的「开录」分支上:
- 自定义组合键(Cmd+Shift+D)本身没有歧义,不等;
- toggle 停止 / QA 路由 / 防抖与冷却判定都在窗口之前,时序不变(尤其不挤占
is_queued_chain_press 的 120ms 排队接力窗口);
- 窗口没盖住的慢速组合键(按住 Option 半秒再按 Tab)仍由 TriggerCombined 事后撤销
兜底,两条路径互补。
150ms 覆盖绝大多数组合键的「修饰键→普通键」间隔,又低于人从按键到开口的反应时间,
不吃首字。
* fix(hotkey): 补上移动端 hotkey stub 的 TriggerCombined / trigger_combined_since_press
Android CI(cargo check --target android)红:`mobile_stubs/hotkey.rs` 是移动端的
HotkeyEvent / HotkeyMonitor 替身,桌面端新增的枚举变体与方法必须同步补上,否则
coordinator 里那两条 match 臂和仲裁窗口的调用在移动端找不到符号。
移动端没有键盘监听器,`trigger_combined_since_press` 恒为 false(仲裁窗口等价于
直接放行)。
* fix(hotkey): 组合键撤销改走独立通道——按下第二个键的那一帧胶囊就消失
150ms 仲裁窗口只盖住了「修饰键→普通键」间隔够快的组合键;慢一点的(实测日志里
press→combined 中位数 428ms)仍然走事后撤销兜底,本该只是「闪一下」,实际却晚了
几百毫秒才收——观感上像录音真的起来了。
原因不是手速,是排队:TriggerCombined 和 Pressed/Released 挤同一条 channel,而
bridge 为修 #468/#475 的 latch 竞态改成了串行 block_on —— 这次按下自己的
begin_session(开麦 + ASR 握手)正卡在 bridge 线程上,撤销只能在队列里等它跑完。
日志里那个 428ms 因此是上界,真实键间隔被 begin_session 的耗时撑大了。
修法同 #853(Esc 取消):把撤销从 HotkeyEvent 枚举拆出,tap/hook 回调改发独立的
Sender<()>,由专用 combo-abort-bridge 线程消费。macOS CGEventTap 与 Windows
WH_KEYBOARD_LL 同步修改;Less Computer 的修饰键 monitor 走同一条通道,只是 handler
换成 cancel_less_computer_press。
并发面两处:
- 撤销与 Released 之间不再有先后保证。改为只看 hotkey_press_began_session
(每个 Pressed 边沿都会重置)判断要不要撤销,不再看 hotkey_trigger_held ——
否则 Released 抢先跑完清了按住态,撤销会认不出这条会话是自己开的、放着不管,
Auto 模式下就是一条停不下来的录音。清 hotkey_trigger_held 只为吞掉后面的
Released,与撤销与否无关。加了回归测试覆盖这个倒序。
- 撤销落在 begin_session 还在 await 的中途,由既有的
startup_race_status_for_starting / CancelRaced 检查点接住(audit HIGH #1),
与 Esc 取消同一条路径。
至此两条路径分工:仲裁窗口内的快组合键胶囊根本不出现;窗口没盖住的慢组合键胶囊
冒个头,按下第二个键即刻消失。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
* fix(hotkey): 取消先收胶囊再拆麦克风 + watchdog 碎觉——撤销不再拖着胶囊一秒
上一提交把撤销决策做到了「按下第二个键的那一帧」(tap → handle_trigger_combined
实测 0.14ms),但真机日志显示胶囊仍然晚 0.8~1 秒才消失。11 次组合键测试分成干净的
两组:这次按下已经把 recorder 开起来的 5 次是 769/900/931/965/986ms,还没开起来的
6 次是 0ms。
链路:cancel_session 先 stop_recorder_for_session 才 emit_capsule。
Recorder::stop() 要 join 音频线程,音频线程退出前要 join liveness watchdog,
watchdog 睡在 1000ms 的检查间隔里 —— 所以停采要等它当前那一觉睡完,胶囊排在后面。
按 Esc 取消走同一个函数,同样拖一秒。
两处一起改(缺一不可,见下):
- coordinator/dictation.rs:cancel_session 把 UI 收尾(finish_cancel_session_state
→ emit_capsule → schedule_capsule_idle → 熄描边)整块提到三个拆资源调用之前。
emit_capsule 仍排在 finish_cancel_session_state 之后 —— 它要读 state.voice_agent /
phase 拼 payload,再提前会发出「还在进行中」的那一帧。拆资源都按 session_id 取,
不依赖 phase,挪到后面语义不变。
- recorder.rs:watchdog 的一个检查间隔切成 50ms 的碎觉来睡,每觉醒来重看 stop_flag。
停采的等待从最坏 1000ms 降到 50ms。判据(回调静默 3s / 首帧 5s)用的是真实时间差
而非醒来次数,灵敏度不变;代价是录音期间线程多醒几次,醒来只读一个时间戳。
为什么必须成对:UI 先收之后,胶囊消失到麦克风真正释放之间有一段窗口,其间旧
recorder 还占着设备。`Recorder` 没有 Drop 停采,recorder 槽被下一条会话覆盖后旧音频
线程会继续跑、抓着麦克风不放。只做第一处会把这段窗口留在 ~1 秒,紧接着那次真想说话
的按下(最快也要 250ms 防抖 + 150ms 仲裁)就可能撞上;watchdog 切碎后窗口降到几十
毫秒,够不着。
副作用:正常说完话松手结束(end_session 也走 Recorder::stop())同样少等将近一秒。
可观察的代价:胶囊消失后系统菜单栏的录音小圆点会多亮几十毫秒。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
* fix(hotkey): resolve modifier combo abort after beta merge
* test(hotkey): initialize mac companion press id
* fix(hotkey): clone Linux fcitx combo channel
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com >
Co-authored-by: Chris233 <h-chris233@outlook.com >
2026-08-01 18:01:11 +08:00
aeoform and Claude Opus 4.7
7e21123bad
fix(linux): fcitx5 hotkey intercept + remove backdrop-filter + clipboard cleanup
...
- fcitx5 plugin: fix Alt+a hotkey leaking 'a' character by normalizing
case comparison (Alt produces uppercase sym, Key::check expects lowercase)
- fcitx5 plugin: use PreInputMethod + filterAndAccept to block engine
before commit happens (same strategy as clipboard addon)
- Linux: remove backdrop-filter (unreliable on WebKitGTK, #553 ),
replace with opaque background
- Linux: skip clipboard writes after streaming insert (fcitx5 commits
directly, clipboard fallback corrupts user data)
- SettingsModal: render via React Portal to document.body (#580 , fixes
scrollbars overlapping modal)
- Remove is_no_compositing_mode command and CSS fallback (no longer needed)
- Remove StartupShell loading screen (app renders FloatingShell immediately)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-06-05 21:59:12 +08:00
aeoform and Claude Opus 4.7
eb05bc1ca1
fix(linux): fcitx5 auxDown persistent + hotkey mode save + hide clipboard/capsule settings
...
- coordinator.rs: terminal state (Done/Error/Cancelled) auto-clears after 3s;
fix RETRY_GEN comparison (fetch_add returns old value, use latest_gen > gen+1);
remove broken 300ms retry
- HotkeySettingsContext.tsx: fix queueSetSettings never calling setSettings()
(latestPrefsRef was already updated to the new object before microtask ran)
- openless.cpp: add PostInputMethod event hook to restore auxDown after
fcitx5 processes key events
- RecordingInputSection.tsx: hide clipboard/paste/capsule settings on Linux;
streaming insert shown as standalone card instead of inside clipboard group
- ShortcutsSection.tsx: hide "capsule confirm" shortcut hint on Linux
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-29 20:53:32 +08:00
aeoform and Claude Opus 4.7
28ef33267c
fix(inject): fail loudly when RPM injection cannot complete
...
exit 0 on rpmrebuild failure produced a broken RPM silently.
Use exit 1 and print to stderr so the CI pipeline catches it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-28 23:59:53 +08:00
aeoform and Claude Opus 4.7
f5bf1ff153
fix(inject): remove AppImage support, detect Debian multiarch dynamically
...
- AppImage cannot work for fcitx5 plugin injection: fcitx5 runs on the
host system and loads addons from host paths (/usr/lib/.../fcitx5).
Files placed inside the AppImage mount are invisible to fcitx5.
- For Debian, use dpkg-architecture to detect the multiarch triplet
(x86_64-linux-gnu, aarch64-linux-gnu, etc.) instead of hardcoding.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-28 23:53:53 +08:00
aeoform and Claude Opus 4.7
0e5f90bcf2
fix(inject): use correct RPM lib path, remove broken AppImage file mode
...
- RPM: use /usr/lib64/fcitx5/ (RPM 64-bit standard) instead of
Debian multiarch path
- AppImage: only support AppDir directory input, remove .AppImage file
extraction which couldn't repack and would lose changes
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-28 23:32:10 +08:00
aeoform and Claude Opus 4.7
19ab7429cd
chore(linux): fcitx5 插件打包注入 + 清理 rdev 残留 + 声明系统依赖
...
- fcitx5 插件改为打包时注入系统路径(inject-fcitx5-plugin.sh),不再运行时复制
- ensure_plugin_installed 改为纯版本检查,不做文件 I/O
- 清理 hotkey/permissions/unicode_keystroke 中 rdev 相关注释和日志
- tauri.linux.conf.json 声明 deb/rpm 运行时依赖及 fcitx5 推荐
- prebuild 脚本自动复制插件文件供 Tauri resources 打包
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-28 23:08:00 +08:00
aeoform and Claude Opus 4.7
7403597be8
fix(review): 修复 PR review 指出的 Stale Status 和 Retry Race
...
1. clearAuxDown 找不到 IC 时也会清掉 lastAuxText_,避免 FocusIn 时
重放旧状态(如"已插入"→切窗口→突然显示"已插入")
2. 300ms 延迟重试前检查 LAST_AUX,状态已变则跳过,避免旧文字覆盖
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-22 20:58:38 +08:00
aeoform and Claude Opus 4.7
57a4919712
feat(linux): fcitx5 候选框听写状态提示 + AppImage 插件自动安装 + 热键启动容错
...
- fcitx5 插件新增 SetAuxDown/ClearAuxDown DBus 接口,在候选词列表下方
显示听写状态文字(收音中→识别中→润色中→已插入),切窗口自动跟随焦点
- 优先使用当前焦点 IC 展示 auxDown,失焦 IC 降级兜底,避免面板不渲染
- flushUI 排空旧事件后再设 auxDown,防止按键事件竞态覆盖状态文字
- start_dictation_signal_listener 启动时等待 fcitx5 最多 30s,监听
NameOwnerChanged 在 fcitx5 重启后自动重新同步快捷键绑定
- AppImage 打包 libopenless.so 为资源,启动时检测缺失自动安装到
~/.local/lib/fcitx5/ 和 ~/.local/share/fcitx5/addon/
- Linux 胶囊窗口不在 Wayland/X11 显示,状态完全走 fcitx5 输入面板
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-22 20:40:40 +08:00
aeoform and Claude Opus 4.7
9cd8b9a8fc
fix(linux): clear previous hotkey trigger when switching between custom combo and preset modifier
...
setHotkey/setHotkeyRaw now clear hasCustomDictationKey_ to prevent stale custom
combo from persisting after switching to a preset modifier. setCustomDictationTrigger
now persists TriggerRawSym=TriggerRawStates=0 to raw config so the old raw
trigger doesn't reload after fcitx5 restart.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com >
2026-05-16 09:53:25 +08:00
aeoform and Claude Opus 4.7
60250c839d
fix(linux): support custom combo hotkeys and surface fcitx5 errors
...
- Add SetCustomDictationTrigger(s) to plugin for custom key combos
- Add fcitx5 availability check before hotkey install, surface error
- Sync custom dictation combo to fcitx5 plugin from coordinator
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com >
2026-05-16 09:32:12 +08:00
aeoform and Claude Opus 4.7
323dbed401
fix(linux): add QA and translation modifier hotkey support to fcitx5 plugin
...
Extend the fcitx5 plugin with SetQaHotkeyRaw/SetTranslationHotkeyRaw
DBus methods and corresponding signals, so that QA panel toggle and
translation modifier hotkeys work on Linux (not just the main dictation
hotkey). Built-in Shift key translation modifier also handled by the
plugin. Document DBus security model in plugin header.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com >
2026-05-16 09:21:38 +08:00
aeoform and Claude Opus 4.7
b349a4dcba
fix(linux): remove dead rdev code and leftover Wayland references
...
Remove 290 lines of unused rdev listener code from the Linux hotkey
platform module, drop the rdev dependency from Cargo.toml, and clean up
capsule_window_visible init leftover. Update doc comments and frontend
i18n to reflect the unified fcitx5-only path.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com >
2026-05-16 09:01:56 +08:00
aeoform and Claude Opus 4.7
dbae3b7a5f
fix(linux): correct fcitx5 InputContext::destroyed signal connection syntax
...
ic->connect(ic->destroyed, ...) is invalid — InputContext has no two-arg
connect overload. Use ic->destroyed.connect(callback) directly, which
calls Signal<void()>::connect. Also simplified ScopedConnection storage
from unique_ptr to direct member (ScopedConnection is move-assignable).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com >
2026-05-16 08:53:06 +08:00
aeoform and Claude Opus 4.7
07132c2087
fix(linux): track fcitx5 InputContext lifetime via destroyed signal
...
Prevents savedIc_ from becoming a dangling pointer by connecting to the
InputContext::destroyed signal when the IC is saved. On destruction the
pointer is cleared automatically, and commitText falls through to
foreachFocused for the current focused IC.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com >
2026-05-16 08:53:06 +08:00
aeoform and Claude Opus 4.7
2912aefed5
feat(linux): replace enigo with fcitx5 plugin for Wayland input
...
- fcitx5 C++ plugin (scripts/linux-fcitx5-plugin/) with DBus interface:
CommitText, SetHotkey/SetHotkeyRaw, DictationKeyEvent signal
- linux_fcitx.rs: DBus client to call plugin from Rust
- coordinator/dictation.rs: Wayland insertion via fcitx5 commit_text,
streaming insert enabled on Wayland
- insertion.rs: fcitx5 commit_text on Linux with clipboard fallback
- unicode_keystroke.rs: Linux path uses fcitx5 commit_text
- Capsule window show-once on Linux to avoid stealing focus
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com >
2026-05-16 08:53:06 +08:00