Commit Graph
1323 Commits
Author SHA1 Message Date
7de0c04ba5 docs: 用协调沉降定律重构 README 能力/增强叙事,凸显开屏即授权完基础设施 (#765)
- 新增「从工具到基础设施 / From tool to infrastructure」专章:把 OpenLess 定位为
  消灭「想法 → 干净文字」之间每一次协调的基础设施——语气沉降成风格包、专有名词
  沉降成词典热词、文字落点沉降成插入协议、模型与凭据沉降进系统保管库,开屏授权
  一次即为默认协议。
- 改写「更新亮点(增强)」与「当前状态(能力)」两节开场,从功能罗列改为「你授权
  一次之后就不必再操心的基础设施层」;其余条目与版本号(v1.3.6)保留不动。
- 中英双 README 同步(README.md + README.zh.md),纯文档改动,不涉及代码/版本门禁。

Co-authored-by: sim <sim@local>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 21:28:36 +08:00
f77bc8fccd feat(asr): enable speaker diarization to filter non-primary speakers (#760)
Open volcengine streaming ASR's `enable_speaker_info` flag so each
returned utterance carries a speaker label. Then in the result
assembly, group utterances by speaker, pick the one with the longest
total speaking time as the "primary speaker", and only concatenate
their text — dropping utterances from other speakers (e.g. a
colleague interjecting while you dictate).

This is a heuristic quick-win for the "only transcribe my voice"
request; it does not yet do enrolled voiceprint verification. If no
speaker labels are present, behavior is unchanged (all utterances
concatenated).

Co-authored-by: Shicongcoding <Shicongcoding@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@owtffssent.com>
2026-07-05 11:26:23 +08:00
TRIPandsim 3ca6883d3a fix(deps): patch tauri-plugin-shell CVE (GHSA-c9pr-q8gx-3mgp) (#763)
@tauri-apps/plugin-shell (npm) 与 tauri-plugin-shell (crate) 的声明下限
——npm `^2.0.1`、crate `"2"`——都落在 GHSA-c9pr-q8gx-3mgp 影响范围
(`< 2.2.1`,CRITICAL:`open` 端点作用域校验绕过,可致 RCE)。本项目
src/lib/ipc/utils.ts 正是通过该 `open` 端点打开外链,属受影响用法。

两个 lockfile 实际已解析到 2.3.5,但清单声明的下限仍是漏洞版本,供应链
扫描(SkillSpector SC4)因此读取到 2.0.1 报警。此处仅把两侧声明下限抬到
最新安全版 2.3.5,使清单与锁定版本一致、扫描不再命中;解析版本不变,无
运行时行为变化。

Closes #668

Co-authored-by: sim <sim@local>
2026-07-05 00:40:23 +08:00
351e0211bd 集成 1.3.14 → beta(主窗 UI 大改 + 一批 fix/feat) (#761)
* fix(insertion): macOS 让「恢复剪贴板」开关真正生效,修复被回退的 #525

#525 的 macOS 剪贴板恢复(commit 4b4d219)被 Android 移植 commit 1ea467a
连同 cfg 门控一起回退:整套恢复机制被重新 #[cfg] 排除出 macOS,macOS insert()
退回「只写剪贴板 + Cmd+V」,于是设置里的「恢复剪贴板」开关在 macOS 上完全无效
——无论开关开还是关,剪贴板都被留成转写文字。

- 把共享的剪贴板恢复机制(ClipboardRestorePlan / schedule_clipboard_restore /
  restore_clipboard_after_delay / should_restore_clipboard 等)的 cfg 从
  not(any(macos,android,ios)) 放宽为 not(any(android,ios)),重新纳入 macOS
  (移动端仍排除;Windows/Linux 不变)。
- macOS insert() 改为:保存原剪贴板 → 写转写文字 → Cmd+V → 仅当「恢复剪贴板」
  开关开启且粘贴成功时按 CLIPBOARD_RESTORE_DELAY 延迟恢复;粘贴失败则保留转写
  文字供手动粘贴、不恢复。开 = 恢复,关 = 保持现状,开关说了算。
- 删除随之失效的 macos_insert_status_after_paste;恢复相关单测在 macOS 也运行。

Windows/Linux 专用项(insert_with_clipboard_restore / paste_keys /
simulate_paste(shortcut) / 其 insertion_success_status 变体)的 macOS 排除保持不变。

* fix(less-computer): 护栏 sudo/dd/chmod/chown 等设为不可批准,修批准注入畸形规则 (#679)

旧审批流对所有命中 pattern 一律 format!("Bash({p}:*)") 生成 allow 规则:
- 对带空格/参数的子串("sudo " → "Bash(sudo :*)","dd if=" → "Bash(dd if=:*)",
  "chmod -r 777 /"、"chown -r")生成的串既不等于 deny "Bash(sudo:*)" 等(故 deny
  没被移除、批准静默失效),又把畸形规则注入了 allowed_tools。

改为:新增 guard::deny_rule_for_pattern(pattern),把「可批准」命令(rm -rf / git
push --force / git reset --hard / git clean 等)映射到其在 default_deny_rules 里的
**精确** deny 规则;提权/毁盘/系统级命令(sudo / dd / mkfs / chmod / chown / shutdown
/ reboot / 管道执行 / fork 炸弹)返回 None = 不可批准,即使被批准也保持拦截
(fail-closed),不再注入畸形 allow。审批流用 filter_map 取精确规则,移除的 deny 与
加入的 allow 严格一致。

附单测:每个可批准 pattern 的映射规则确实存在于 default_deny_rules;危险系统命令一律
不可批准。

* fix(ui): unify macOS shell top inset with sidebar/background surface

The 28px top inset that clears the macOS overlay traffic lights sat on the
content wrapper with no background, so it exposed the lighter WindowChrome
glass (--ol-window-bg) as a full-width band above the sidebar and panel —
reading as an extra top row that the Windows build (no inset) doesn't have.

Paint that wrapper with the same --ol-app-shell-bg the sidebar/background
already use, so the inset blends into one continuous surface with the panel
card floating on top. One property; token carries its own light + dark
values; no layout, token, or other UI changes.

* fix(capsule): 胶囊跟随鼠标光标所在屏,修复多屏/多 Space 只在第一块屏显示

多屏 + 多桌面下胶囊只出现在第一块屏、其他屏闪一下就消失。根因:定位
(position_capsule_bottom_center) 用 AX caret 选屏,多数 App 取不到 caret 矩形就
跌回胶囊自己的 current_monitor(第一块屏);而去重缓存 capsule_layout_snapshot 也
用 current_monitor,与定位看的不是同一块屏 → 胶囊一落第一块屏即被判“无变化”跳过
重定位,被锁死。两条路都没跟踪鼠标。

- 新增 mouse_cursor_point():CoreGraphics CGEventGetLocation 取光标,坐标系与 AX
  一致、零权限、永远可用
- focused_input_target_monitor → capsule_target_monitor,选屏改“光标优先”(caret
  仅兜底),选屏循环抽成 monitor_for_anchor_point
- 定位与去重快照统一走 capsule_target_monitor,彻底消除不一致
- 保留 CAN_JOIN_ALL_SPACES(Spaces 契约强制且本就正确:帧落在光标屏→只那块屏显示)
- 扩展 macos-capsule-spaces 契约测试,grep 守护“光标优先 + 两路共用同一函数”

Windows 路径未动(本就跟随输入 App 屏且快照一致)。
验证:cargo check + lib 纯函数单测 + check:macos-capsule-spaces 全过。

* feat(less-computer): 边缘发光改用 EdgeGlow 式四层模糊堆叠 + iridescent 配色

把 Less Computer 全屏彩虹边缘发光从原来「两条都带模糊的软光带」改成
EdgeGlow(github.com/vector4wang/EdgeGlow,MIT)的实现方式,让彩虹「实线」
更清晰、发光更有层次。

- 4 层模糊堆叠:最内一条 0.5px 锐利彩虹实线(对应 EdgeGlow blur=0 那层,
  定义边缘),外面叠 3 层逐渐变宽、变虚的光晕(blur 3/8/15px),得到
  霓虹灯管式发光。原生用 CAShapeLayer + 高斯模糊,这里用 CSS「边框遮罩环
  + filter:blur」等价复刻。
- 配色移植 EdgeGlow 默认的 iridescent(Apple Intelligence 高饱和霓虹:
  紫→蓝→青→绿→金→橙→红→粉),按 12 段均匀铺满 360°。
- 流光整圈 8s/圈通过 --lcg-angle 旋转(只动渐变角度不动元素形状,GPU 友好),
  光晕层各带不同周期呼吸;实线用 normal 保色准,光晕用 screen 叠加增亮。

保留 issue #470 的隐藏即卸载发光层(零 GPU)逻辑、pointer-events:none、
prefers-reduced-motion 降级与 --lcg-radius 圆角变量。纯视觉、仅 macOS、单文件。

* fix(vault): Windows 凭据读取瞬时失败重试,修概率性「火山引擎未配置」

Windows Credential Manager(CredReadW)在登录后/并发下读 manifest+各 chunk 条目
时可能瞬时失败。任一条目读失败会让整组凭据看起来为空 → load_keyring_credentials
返回 Err → load_credentials 回退到空默认值 → 概览页显示「火山引擎未配置」,但下
一次听写重读成功,所以是「概率性」且「实际可正常使用」。凭据 chunk 越多,单次
load 读取次数越多,命中瞬时失败的概率越高。

修法:仅在 Windows 上对非 NoEntry 的读错误做几次短退避重试(4 次 / 60·n ms)。
NoEntry 是确定的「未存储」,立即返回不付重试延迟。macOS/Linux 保持原单次行为
——它们的读错误是 ACL 拒绝,重试无益,且未缓存的错误路径下次调用本就会重读,
加 sleep 只会拖慢 macOS 首次 Keychain 授权流程。纯后端、cfg 隔离,零 PC-macOS 影响。

* fix(asr): 火山引擎连接增加超时与重试,修弱网下无法语音输入

connect_async 此前既无超时也无重试:弱网下 TLS/WebSocket 握手可能挂到 OS 级
TCP 超时(几十秒),期间用户卡在「Starting」;而协调器全局超时只覆盖
await_final_result,不覆盖 open_session,所以握手挂起完全不受约束。单次网络
抖动(连接被重置/瞬时 DNS 失败)也会直接让整次听写失败。

修法:每次握手包 5s 超时,最多 3 次尝试、250·n ms 退避。AuthRejected(凭据被拒)
短路不重试——重试不会变好只会拖慢报错。把请求构造抽成 build_connect_request
(connect_async 消费 request 且 http::Request 非 Clone,需每次重建)。

桌面与安卓共享此路径,改动对两端等效;最坏耗时从「无界(~75s)」降到有界(~15s)
并新增抖动可恢复,对 PC 端只增不减。

* feat(coding-agent): OpenCode CLI 适配器,语音 Agent 可选 OpenCode 后端 (issue #579)

把 OpenCode 当作与 Claude Code 同类的 coding agent CLI 接入,照既有 coding_agent
适配模式(不另造架构),复用同一套 CodingAgentRequest / CodingAgentEvent /
CodingAgentError / 审批护栏链路。

后端:
- coding_agent/opencode.rs(新):detect_opencode(opencode --version)、
  build_opencode_args(opencode run --format json --model --dir
  [--dangerously-skip-permissions],prompt 作为 argv)、parse_opencode_json_line
  (NDJSON:text/tool_use/error;text 块累计,EOF 合成 Completed)、
  run_opencode_agent(取消/超时 kill,护栏经 OPENCODE_CONFIG_CONTENT 注入)。
- coding_agent/args.rs:CodingAgentProvider 枚举(claude-code-cli/opencode-cli,
  未知回落 Claude)+ from_pref/default_exe。
- coding_agent/guard.rs:build_opencode_guard_config —— 把高风险 bash 前缀翻译成
  OpenCode permission.bash deny glob + webfetch deny,审批放行的前缀显式 allow。
- coordinator/dictation_voice_agent: run_less_computer_once 按 coding_agent_provider
  分派 Claude / OpenCode 运行器;两路都 fail-closed(护栏生成失败即中止,不裸跑)。
  审批拦截探测/重跑放行链路 provider 无关,自动复用。
- commands: coding_agent_detect_opencode 命令(已注册 lib.rs)。

前端:
- CodingAgentSection: OpenCode 选项从「即将支持」改为可用 + 选中后探测安装状态并提示。
- ipc: codingAgentDetectOpencode + OpenCodeDetection。
- i18n: opencodeReady / opencodeMissing(5 语言,替换 providerOpenCodeSoon)。

验证:cargo check + cargo test(492 通过)+ npm run build 均通过。

Refs #579
(#579 还含「语音润色模型配置」:prefs 字段已存在但全链路未实现/未接 UI,
转写直接进 agent 未经语音润色模型整理 —— 故用 Refs 不用 Closes。)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(coding-agent): OpenCode 防参数注入 + 可配置 Agent 可执行文件路径 (#638)

注入风险(@H-Chris233 评审):`opencode run` 的 prompt 作为最后位置参数,但之前缺
end-of-options 分隔。以 `-` / `--` 开头的 prompt(语音转写或被 prompt 注入诱导)会被
OpenCode 当作 flag 解析,可绕过护栏(混入 --dangerously-skip-permissions、--dir 改写
工作目录等)。修复:build_opencode_args 末尾追加 `--`,运行器把 prompt 接在其后;新增单测。

可配置路径(@PR-Agent 指出之前 hardcode "opencode"):新增 prefs.coding_agent_exe,
语音 Agent 路径据此取 exe(留空回退默认 claude / opencode),检测命令亦用配置路径;
设置「高级 → Less Computer」新增「可执行文件路径」输入;补 5 语言 i18n。

* fix(updater): 安装失败可见化 + 手动下载兜底 + 错误写日志 (#706)

下载完成后若 install() 抛错,旧代码把 status 置成 'error',而 UpdateDialog 不渲染 error 态 → 弹框消失、错误只 console.error,用户表现为「下载后无反应、重启仍旧版」(#706)。

- 新增独立状态 installError:仅下载/安装失败用它;'error' 仍表示「检查失败」,行为不变(CheckUpdateButton 按钮内轻提示、后台自动检查都不弹框,零回归)。
- 失败弹框留在原地显示真实错误 + 「手动下载」按钮直达 GitHub Releases 兜底。
- 新增 Rust IPC log_client_error,把前端更新错误写进 openless.log(webview console 不入文件日志),便于用户「导出日志」后定位真因。
- 5 个 i18n 文件补 updateDialog.installError / manualDownload。

注意:这是兜底(失败可见 + 可诊断),不解决 ad-hoc/quarantine 机器自动更新真正成功的问题——那需要 Apple Developer ID 签名 + 公证(本次暂缓)。

* 手动补:dictation/ipc 拆分后的 OpenCode + updater 改动迁移

* fix: AutoUpdate 兼容性修复(补回 CheckUpdateOptions + Android 参数类型)

* fix(audio-cue): 重建/唤醒退化的 AudioContext,修「用久了提示音消失」

长时间使用后「开始录音」提示音与设置页「试听」双双静音,且重启才恢复。

根因在 audioCue.ts 的 AudioContext 单例 sharedCtx 从不检查/恢复运行期状态:
- WKWebView/WebView2 在频繁录音抢占音频会话后,ctx 可能转入 WebKit 非标准的
  `interrupted` 态。旧代码只判断 `suspended`,interrupted 被当作可直接排期 →
  排到非 running 的 ctx 上,无声且不尝试 resume → 永久静默(macOS 最可能根因)。
- ctx 也可能被系统 `closed`。closed 无法复活(resume 必拒、createOscillator 必抛、
  被 catch 静默吞掉),而 getContext 从不重建 → 永久静默。
- 胶囊窗口与设置窗口各持一份 ctx,都会这样退化 → 两处提示音同时失效。

修复:
- 新增纯函数 audioContextActionForState(state):closed→recreate / running→ready /
  其余(suspended/interrupted/未知非运行态)→resume,单一真相、可单测。
- getContext 在 ctx 为 closed 时丢弃重建(并清空挂在旧 ctx 上的 activeVoices)。
- playRecordStartCue / primeAudioCue 改用该分类器,把 interrupted 一并按 resume 唤醒。

公共 API 签名不变,仅 AudioCue.tsx / RecordingInputSection.tsx 复用,行为向后兼容。
tsc --noEmit 通过;audioCue.test.ts 新增 5 条状态分类断言,全部通过。

* fix(macos): onboarding Accessibility authorization page stuck (#703 follow-up)

Root cause: permissions::request_accessibility() was called in setup(),
which runs BEFORE the AppKit event loop is live. On some macOS versions,
AXIsProcessTrustedWithOptions depends on the run loop for XPC to tccd.
When the event loop isn't ready, TCC may not show the dialog but still
consumes the one-time prompt. Later calls from the onboarding page's
'grant accessibility' button silently return Denied, trapping users on
the onboarding page. Recovery requires System Settings grant + app
restart, but users had no clear restart action — only a text hint.

Changes:

- lib.rs: Remove setup() TCC prompt. The prompt is now exclusively
  triggered by user action on the onboarding page, aligning with
  Apple HIG (explain why → then prompt).

- Onboarding.tsx:
  * onGrantAccessibility now checks the TCC result: skip
    openSystemSettings when already granted (previously always opened)
  * New tccPromptShown state distinguishes 'never attempted' vs
    'already denied' for correct button labels
  * Denied state now shows a prominent 'Restart OpenLess' button
    calling restartApp()

- PermissionsSection.tsx: Same fix for settings page accessibility
  row — check TCC result, guide to System Settings when denied,
  show 'Restart App' button

- i18n: Add onboarding.actionRestart + settings.permissions.restart
  for zh-CN / zh-TW / en / ja / ko

Closes: macos-onboarding-accessibility-stuck

* chore(release): publish 1.3.13

* fix(audio-cue): recover when resume() is rejected or wakes non-running

Long sessions park the shared AudioContext in 'suspended' / WebKit
'interrupted' after the system audio session is repeatedly preempted by
ASR and recording. The earlier fix only recreated a 'closed' context: a
rejected resume() was swallowed and a resume() that resolved without
reaching 'running' scheduled voices on a frozen currentTime, both of
which leave the start cue permanently silent until the app restarts.

Treat both "won't wake" states as recoverable — discard the dead context
and retry once on a fresh one, bounded to avoid infinite recreation. The
decision is extracted into the pure, unit-tested cueActionAfterResume.

* chore(release): integrate 1.3.14

* fix(ipc): 去除 coding-agent.ts/index.ts 中 OpenCodeDetection 和 codingAgentDetectOpencode 的重复声明

integrate/cloud-sync-20260621 合并后 IPC barrel 出现重复 export(beta 已含 PR #638 的
OpenCode 适配器,integrate 分支又加了一份)。合并去重,npm build 通过。

* style(less-computer): 气泡/审批/发送按钮改用 accent 主题 token

把 LessComputerPanel 里三处硬编码渐变(#3d6bff→#7a55f2)替换为
--ol-accent-solid-bg / --ol-accent-solid-ink,跟随浅/深主题。

* feat(capsule/asr/ui): 胶囊即时显示与预测展开、Apple 语音修复、弹窗全局遮罩

胶囊入场(延迟出现 / 无入场动画 / 亮起→卡→展开):
- 按下热键即「乐观显示」胶囊并播入场动画,不等麦克风就绪;level_handler 首帧 PCM
  到达后从预备态翻正式态、光条点亮(新增 warming 标志贯穿 emit_capsule payload)
- 修入场动画被吞:入场帧把发给前端的事件挪到 window.show 之后再 emit
- 预测式展开:光条按历史平均加载时长(EMA + localStorage 跨会话学习)从按下就平滑
  展开,就绪早则加速补满、未就绪则慢爬至 90%,消除「亮起→卡→突然展开」

Apple 语音(说中文识别成英文 / 偶发不可用):
- initWithLocale 跟随用户工作语言主语言,修 init 无参落系统首选语言(英文)导致
  中文被英文引擎误识别
- on-device 优先(requiresOnDeviceRecognition):离线可用 + 隐私 + 不受网络波动/限流
- isAvailable 就绪轮询等待,修刚 init 时瞬时不可用就报错的竞态

弹窗遮罩:Modal 用 createPortal 到 body,修被 WindowChrome/FloatingShell 常驻
will-change: transform 祖先困住 fixed 遮罩(登录弹窗浮在亮着的设置页上、无全局压暗)

服务页 Apple 语音文案优化(zh-CN/zh-TW)

---------

Co-authored-by: 吕柏青 <lubaiqing9@gmail.com>
Co-authored-by: sim <sim@local>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 23:41:41 +08:00
Duyi-Wang 28a52e949b feat(llm): add custom provider extra headers
Add support for custom LLM provider extra HTTP headers stored in credentials, with validation that prevents overriding protocol-critical headers.
2026-06-27 17:06:13 +08:00
katanumahotoriandClaude Opus 4.8 bafddd4a91 fix(history): fall back to raw transcript when polished text is empty + copy button on raw panel (#752)
When polishing returns an empty finalText (errorCode stays null), the History
"Copy" button copied an empty string, so the raw transcript that is still
visible in the UI could not be recovered. Re-transcribe does not apply here
(ASR succeeded; polish is what failed).

- onCopy: fall back to rawTranscript when finalText is blank
- raw panel: add a dedicated copy button (shown only when rawTranscript exists)
- reuse existing common.copy / common.copied, no new i18n; frontend-only

Complements #653 (polish identical to raw); independent of the in-progress
history re-polish work (#666/#694). No backend/IPC changes.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 14:08:44 +08:00
jmr 1df2838604 NO AI SLOP PLEASE
AI SLOP 会导致项目产生大量垃圾内容 且在此workflow被跳过的情况下无法正常merge pr
2026-06-27 14:05:38 +08:00
sim de8b6e3bd8 fix(release): skip beta Windows MSI 2026-06-25 15:32:28 +08:00
sim 8d74b3e0eb chore(release): prepare 1.3.13-Beta.1 2026-06-25 14:59:31 +08:00
浩彬(^-^) ee0f746324 feat(windows): add SendInput insertion and keyboard list controls
Merge PR #740 after review and full CI. This supersedes PR #734 by including its SendInput insertion mode work plus the keyboard-list visibility preference for #738.
2026-06-25 13:51:11 +08:00
浩彬(^-^) fa09eaf605 feat(hotkey): support side-specific modifier combos
Add side-aware combo detection for dictation on macOS/Windows, shortcut recording support for side-specific modifiers, overlap validation, and tests.
2026-06-25 13:41:28 +08:00
NarwhalChen 346942e1bb style(ui): improve dark theme polish
Rework dark theme tokens, replace hardcoded dark-mode colors with theme variables, and remove the redundant overview metrics wrapper border.
2026-06-25 13:41:06 +08:00
Yu Peng Zheng c10ef381cb fix(ui): clean up late Tauri listener subscriptions
Immediately unsubscribe Tauri listeners if their async listen() call resolves after component unmount, preventing leaked handlers in AudioCue and Linux window chrome.
2026-06-25 13:39:58 +08:00
TRIP b4636f9620 fix(audio-cue): recover degraded AudioContext
Recreate closed AudioContext instances and resume interrupted/suspended contexts before scheduling generated record-start cues. Includes state classification tests.
2026-06-25 13:39:10 +08:00
TRIP 124a49ca05 fix(macos): recover stuck Accessibility onboarding
Fix macOS Accessibility onboarding getting stuck after TCC prompt/identity drift. Request permission only from onboarding, add targeted Accessibility TCC reset + restart recovery, and mirror the flow in settings.
2026-06-25 13:38:52 +08:00
sim 8563e707ba [cask] openless 1.3.12 (auto from release) [skip ci] 2026-06-24 13:56:50 +08:00
sim a4df800d66 chore(release): publish 1.3.12
- add OpenCode CLI provider support for voice agent workflows

- improve updater fallback/error visibility and compatibility

- bump app, Tauri, and Cargo versions to 1.3.12
2026-06-24 12:42:25 +08:00
e1c1be2942 feat(coding-agent): OpenCode CLI 适配器,语音 Agent 可选 OpenCode 后端 (Refs #579) (#638)
* feat(coding-agent): OpenCode CLI 适配器,语音 Agent 可选 OpenCode 后端 (issue #579)

把 OpenCode 当作与 Claude Code 同类的 coding agent CLI 接入,照既有 coding_agent
适配模式(不另造架构),复用同一套 CodingAgentRequest / CodingAgentEvent /
CodingAgentError / 审批护栏链路。

后端:
- coding_agent/opencode.rs(新):detect_opencode(opencode --version)、
  build_opencode_args(opencode run --format json --model --dir
  [--dangerously-skip-permissions],prompt 作为 argv)、parse_opencode_json_line
  (NDJSON:text/tool_use/error;text 块累计,EOF 合成 Completed)、
  run_opencode_agent(取消/超时 kill,护栏经 OPENCODE_CONFIG_CONTENT 注入)。
- coding_agent/args.rs:CodingAgentProvider 枚举(claude-code-cli/opencode-cli,
  未知回落 Claude)+ from_pref/default_exe。
- coding_agent/guard.rs:build_opencode_guard_config —— 把高风险 bash 前缀翻译成
  OpenCode permission.bash deny glob + webfetch deny,审批放行的前缀显式 allow。
- coordinator/dictation_voice_agent: run_less_computer_once 按 coding_agent_provider
  分派 Claude / OpenCode 运行器;两路都 fail-closed(护栏生成失败即中止,不裸跑)。
  审批拦截探测/重跑放行链路 provider 无关,自动复用。
- commands: coding_agent_detect_opencode 命令(已注册 lib.rs)。

前端:
- CodingAgentSection: OpenCode 选项从「即将支持」改为可用 + 选中后探测安装状态并提示。
- ipc: codingAgentDetectOpencode + OpenCodeDetection。
- i18n: opencodeReady / opencodeMissing(5 语言,替换 providerOpenCodeSoon)。

验证:cargo check + cargo test(492 通过)+ npm run build 均通过。

Refs #579
(#579 还含「语音润色模型配置」:prefs 字段已存在但全链路未实现/未接 UI,
转写直接进 agent 未经语音润色模型整理 —— 故用 Refs 不用 Closes。)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(coding-agent): OpenCode 防参数注入 + 可配置 Agent 可执行文件路径 (#638)

注入风险(@H-Chris233 评审):`opencode run` 的 prompt 作为最后位置参数,但之前缺
end-of-options 分隔。以 `-` / `--` 开头的 prompt(语音转写或被 prompt 注入诱导)会被
OpenCode 当作 flag 解析,可绕过护栏(混入 --dangerously-skip-permissions、--dir 改写
工作目录等)。修复:build_opencode_args 末尾追加 `--`,运行器把 prompt 接在其后;新增单测。

可配置路径(@PR-Agent 指出之前 hardcode "opencode"):新增 prefs.coding_agent_exe,
语音 Agent 路径据此取 exe(留空回退默认 claude / opencode),检测命令亦用配置路径;
设置「高级 → Less Computer」新增「可执行文件路径」输入;补 5 语言 i18n。

---------

Co-authored-by: 吕柏青 <lubaiqing9@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Chris233 <h-chris233@outlook.com>
2026-06-24 12:30:12 +08:00
Haoqian 0881338e2e fix(asr): route Apple Speech dictation locally (#741) 2026-06-24 12:29:54 +08:00
jmr 95920a6b41 Merge pull request #729 from Open-Less/fix/issue-643-traditional-chinese-output
fix(ime): 繁中用户语音输入始终输出繁体 / enforce 简繁 script on output (#643)
2026-06-21 20:57:47 +08:00
Andy Jiang 48e190be20 fix(ime): always enforce 简/繁 script preference on dictation output (#643)
Traditional-Chinese users got Simplified output. finalize_polished_text only ran
the deterministic OpenCC conversion for Raw mode / polish failures, leaving
successful polish modes to the LLM — which defaults to Simplified for Chinese.
The streaming-insert path bypassed the conversion entirely.

- finalize_polished_text: for non-translation dictation, always apply
  apply_chinese_script_preference. It is a no-op for ChineseScriptPreference::Auto
  (the default), so zero impact on users who didn't pick a script; for Simplified/
  Traditional it now guarantees the output script instead of trusting the LLM.
- streaming_insert_eligible: gains the script preference and disables streaming
  insert when it is non-Auto, so the one-shot path runs (streaming types live and
  has no finished text to post-process).

Tests: polish_output_honors_chinese_script_preference (S2t actually converts;
Auto is a no-op) + streaming_disabled_for_non_auto_script_so_opencc_runs. 470 lib
tests pass.

Closes #643
2026-06-21 19:22:46 +08:00
github-actions[bot] e55ac03fa2 [cask] openless 1.3.11 (auto from release) [skip ci] 2026-06-21 06:49:44 +00:00
Andy Jiang 950ad58006 chore(release): 1.3.11 2026-06-21 13:55:15 +08:00
Andy Jiang 65361606f7 feat(dictation): failure-resilience, record-next queue, manual re-transcribe
- preserve audio on ASR failure + auto silent retry: always-archive recordings
  (delete on success unless debug), route all engine failure arms through a single
  fail_dictation -> recoverable transcribeFailed history; auto-retry re-feeds the
  archived WAV via retranscribe_pcm; fix empty-transcript id mismatch (#613)
- hotkey: queue-chain -- a press buffered during processing auto-starts the next
  recording (grace window distinguishes it from the #545 post-session cooldown)
- history: wire the manual re-transcribe button for transcribeFailed/emptyTranscript
  entries with audio

Rebased onto beta 1.3.10; my capsule multi-monitor fix dropped (superseded by #714).
2026-06-21 13:53:56 +08:00
Chris233 b07f78bc1d Merge pull request #723 from H-Chris233/fix/whisper-dynamic-timeout-721
fix(asr): Whisper/OpenRouter 动态超时 + 全局兜底 15→30s
2026-06-21 13:26:07 +08:00
Chris233 e1f24b61a4 test: 修复 local_qwen_timeout 测试 + 新增 whisper_transcribe_timeout 测试
- local_qwen_timeout_ceils_partial_seconds: 10.1s 音频原期望 17s,全局常量
  提升到 30s 后改为期望 COORDINATOR_GLOBAL_TIMEOUT_SECS
- 更新相关注释中的 15→30
- 新增 3 个 whisper_transcribe_timeout 测试:短音频兜底、长音频缩放、ceil
2026-06-21 12:50:41 +08:00
Chris233 3ec8c5ef0c fix(asr): Whisper/OpenRouter 动态超时 + 全局兜底 15→30s
- COORDINATOR_GLOBAL_TIMEOUT_SECS 从 15 改为 30,为云端 batch ASR 留网络预算
- 新增 whisper_transcribe_timeout(audio_secs):max(30, ceil(audio×0.5)+20)
- WhisperBatchASR 新增 buffer_duration_ms() 供 coordinator 计算动态超时
- dictation.rs Whisper 路径从固定 15s 切到动态超时,超时日志包含音频时长

Closes #721
2026-06-21 20:48:38 +08:00
TRIPandsim 323f44d450 feat(less-computer): 边缘发光改用 EdgeGlow 式四层模糊堆叠 + iridescent 配色 (#720)
把 Less Computer 全屏彩虹边缘发光从原来「两条都带模糊的软光带」改成
EdgeGlow(github.com/vector4wang/EdgeGlow,MIT)的实现方式,让彩虹「实线」
更清晰、发光更有层次。

- 4 层模糊堆叠:最内一条 0.5px 锐利彩虹实线(对应 EdgeGlow blur=0 那层,
  定义边缘),外面叠 3 层逐渐变宽、变虚的光晕(blur 3/8/15px),得到
  霓虹灯管式发光。原生用 CAShapeLayer + 高斯模糊,这里用 CSS「边框遮罩环
  + filter:blur」等价复刻。
- 配色移植 EdgeGlow 默认的 iridescent(Apple Intelligence 高饱和霓虹:
  紫→蓝→青→绿→金→橙→红→粉),按 12 段均匀铺满 360°。
- 流光整圈 8s/圈通过 --lcg-angle 旋转(只动渐变角度不动元素形状,GPU 友好),
  光晕层各带不同周期呼吸;实线用 normal 保色准,光晕用 screen 叠加增亮。

保留 issue #470 的隐藏即卸载发光层(零 GPU)逻辑、pointer-events:none、
prefers-reduced-motion 降级与 --lcg-radius 圆角变量。纯视觉、仅 macOS、单文件。

Co-authored-by: sim <sim@local>
2026-06-21 10:04:35 +08:00
GanAO da9bf15795 fix(asr): add MiMo provider to credential gate validation (#713)
ensure_asr_credentials() 的 if-else 链未处理 MiMo provider,fall through 到末尾被火山引擎凭证检查误拦。本 PR 把 is_mimo_provider(&active_asr) 加入 API Key 验证分支(+4 行),与 build_qa_asr_start / active_asr_provider_kind 的处理保持一致。

Claude 审核:修复正确且最小化,无副作用,可合并。四平台 CI 全绿。
2026-06-21 00:27:47 +08:00
TRIP 50274d1acd fix(insertion): macOS 让「恢复剪贴板」开关真正生效,修复被回退的 #525 (#683)
#525 的 macOS 剪贴板恢复被 Android 移植 commit 1ea467a 用 cfg(not(any(macos,android,ios))) 误伤排除。本 PR 把 40+ 处 cfg 放宽为 not(any(android,ios)),macOS 重新纳入;insert() 改为「保存原剪贴板 → 写转写 → Cmd+V → 按开关决定是否恢复」。

已解决与 beta(#712 安卓集成)在 insert() 签名上的冲突,cargo check 通过,四平台 CI 全绿。
Claude 审核:cfg 边界清晰、三态逻辑正确,无阻塞性问题。Closes #525
2026-06-21 00:27:39 +08:00
TRIP cfb09b14f8 fix(capsule): 胶囊跟随鼠标光标所在屏,修复多屏/多 Space 只在第一块屏显示 (#714)
新增 CGEventGetLocation 取鼠标光标位置(零权限),focused_input_target_monitor → capsule_target_monitor,定位与去重快照统一走光标优先选屏,消除「定位用 AX caret、去重用 current_monitor」两套选屏不一致导致的锁屏。

Claude 审核:5 个核心确认点(坐标系一致/选屏逻辑/快照统一/零权限/平台隔离)全部通过,可合并。
2026-06-21 00:05:45 +08:00
TRIP 7a33db0605 fix(less-computer): 护栏高危系统命令设为不可批准,修批准注入畸形规则 (#688)
deny_rule_for_pattern 只对可安全前缀化的命令(rm -rf / git push --force 等)返回精确 deny 规则;sudo/dd/mkfs/chmod/shutdown 等返回 None,即使被点批准也保持拦截、不注入 allow。修复旧代码 format!("Bash({p}:*)") 对 "sudo " 生成 Bash(sudo :*) 与 deny 规则 Bash(sudo:*) 不匹配导致批准静默失效的问题。

Closes #679

Claude 审核:方向正确,核心修复逻辑无误,无阻塞性问题。
2026-06-21 00:03:56 +08:00
TRIP fbe40ab583 feat(android): 集成 #704 安卓自动更新 + 同步 PC updater 修复(#705/#708)到安卓 (#712)
合并 #704(安卓应用内自动更新 + JNI 修复)并把 PC updater 修复同步到安卓:①安卓公钥回退 6F4B5994→1DEAA803(同步#705)②installError 安装失败兜底+去 throw(同步#708)③还原被自动合并误删的桌面 logClientError。桌面机制零变更。CI 全绿 + @claude 审核通过。取代 #704 及 #694 安卓部分。
2026-06-19 13:55:53 +08:00
github-actions[bot] 6c34e0e579 [cask] openless 1.3.10 (auto from release) [skip ci] 2026-06-18 03:50:17 +00:00
github-actions[bot] a5d1b0b579 [cask] openless 1.3.10 (auto from release) [skip ci] 2026-06-17 16:59:48 +00:00
sim dbdc73f278 chore(release): 1.3.10 (stable / 正式版) 2026-06-18 00:33:00 +08:00
sim 412dc9968a chore(release): 1.3.10-4 (beta) 2026-06-17 23:30:58 +08:00
TRIPandsim 6c99ddf1f7 fix(updater): revert signing pubkey to 1DEAA803 to repair auto-update (#705)
Auto-update was broken on every platform: builds since aaf4ccc embed pubkey
6F4B5994, but shipped 1.3.8 stable users trust 1DEAA803 and CI now signs with
1DEAA803 again. A key the installed app doesn't trust => download succeeds,
install() signature verification fails => "downloads but never updates".

Revert the desktop updater pubkey to 1DEAA803 so it matches both the CI
signing key and android/updater.rs (which already hardcodes 1DEAA803). This
also repairs Android auto-update, which was signing-vs-verifying mismatched.
One line, no logic change.

Co-authored-by: sim <sim@local>
2026-06-17 23:30:01 +08:00
sim f94f9be07a chore(release): 1.3.10-3 (beta)
Re-cut of the 1.3.10-2 beta from the synced cloud beta branch (supersedes the
-2 pre-release, which was deleted). Identical code to 1.3.10-2 — version-number
bump only. Bundles #701 persistence split, #702 top-bar unification, #703
onboarding accessibility gate, on top of 1.3.10-1 audit fixes.
2026-06-17 18:57:05 +08:00
sim aa643b972d chore(release): 1.3.10-2 (beta) — macOS top-bar unification + onboarding accessibility gate
Bundles the two macOS UX fixes already merged into beta:
- #702 unify macOS shell top inset with sidebar/background surface
- #703 require macOS Accessibility grant before first-run onboarding completes

Version synced across package.json, package-lock.json, tauri.conf.json,
Cargo.toml, Cargo.lock. Local release build verified (1.3.10-2 installs and
runs; top bar seamless; first-run requires Accessibility).
2026-06-17 17:54:09 +08:00
TRIPandsim f3467ae5f9 fix(onboarding): require macOS Accessibility grant before first-run completes (#703)
On macOS the hotkey needs Accessibility, but first-run onboarding could finish
after Microphone only and drop users into the main UI with a dead hotkey
("nothing happens when I press the key").

Cause: desktop onboarding gated completion on the hotkey *capability* flag
(requiresAccessibilityPermission). That flag is null while the capability IPC
loads, so requiresAccessibility was briefly false — if Mic got granted in that
window, refresh() called onComplete() before Accessibility was ever requested.
App.tsx's startup gate, by contrast, always requires Accessibility, so the two
disagreed.

Fix: gate completion on the real permission status, mirroring App.tsx — macOS
returns granted/denied, every other platform returns notApplicable, so this is
timing-independent and cross-platform safe. Also surface the Accessibility step
whenever it is denied (macOS-only state) and drop the capability dependency from
its grant button, so the step always appears and is actionable even before the
capability finishes loading.

No change on Windows/Linux/Android (Accessibility = notApplicable there).

Co-authored-by: sim <sim@local>
2026-06-17 17:39:16 +08:00
TRIPandsim 4903f4442e fix(ui): unify macOS shell top inset with sidebar/background surface (#702)
The 28px top inset that clears the macOS overlay traffic lights sat on the
content wrapper with no background, so it exposed the lighter WindowChrome
glass (--ol-window-bg) as a full-width band above the sidebar and panel —
reading as an extra top row that the Windows build (no inset) doesn't have.

Paint that wrapper with the same --ol-app-shell-bg the sidebar/background
already use, so the inset blends into one continuous surface with the panel
card floating on top. One property; token carries its own light + dark
values; no layout, token, or other UI changes.

Co-authored-by: sim <sim@local>
2026-06-17 17:27:15 +08:00
TRIPandsim a94ea3d05a refactor(persistence): split persistence.rs into domain modules behind a barrel (#701)
Behavior-unchanged decomposition of the 2757-line `persistence.rs` god-file
into a `persistence/` module, one file per concern. Everything is re-exported
via glob (`pub use <submodule>::*`) so the `crate::persistence::*` public
surface is byte-for-byte identical and no call site changes.

Layout:
- mod.rs         shared fs/io helpers (data_dir, ensure_dir, atomic_write,
                 read_or_default) + the two cross-cutting consts + re-exports
- paths.rs       models / recordings / Foundry roots + migration helpers
- credentials.rs OS credential vault (CredsRoot model, keyring/android/legacy,
                 CredentialAccount, CredentialsSnapshot, CredentialsVault)
- history.rs     HistoryStore
- preferences.rs read_preferences + PreferencesStore
- style_pack.rs  StylePackStore + ZIP import/export + prefs reconciliation
- dictionary.rs  DictionaryStore + vocab presets
- correction.rs  CorrectionRuleStore

Pure move + re-export: no logic, signatures, public API, or UI changed. Unit
tests moved next to the code they now cover. Shared helpers stay private in
`mod.rs` (parent items are visible to child modules), and glob re-exports keep
cfg-gated items (Android/Windows) resolution-safe across every target —
avoiding the explicit-`pub use` breakage seen in the earlier types.rs split.

Verified on macOS: cargo fmt/check/clippy clean (no new persistence warnings),
446 lib tests green. Android/Windows cfg paths verified by CI.

Part of #697.

Co-authored-by: sim <sim@local>
2026-06-17 16:08:26 +08:00
TRIP b98e353a2b refactor(ipc): split lib/ipc.ts into domain modules behind a barrel (#700)
Behavior-preserving: 1765-line ipc.ts → 22 domain modules + barrel index.ts; all ~118 exports preserved, 0 consumer changes, tsc/build/all-platform CI green. Part of #697.
2026-06-17 13:38:34 +08:00
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
TRIP 5aed1e46ed fix: audit batch 2 — correctness bugs + Android robustness (#696)
7 medium findings: corrupt-timestamp context truncation, voice_agent flag timing, Volcengine/Bailian ASR cancel-on-error, graceful store-init fallback (no startup panic), Android OVERLAY_VISIBLE desync (Rust+Kotlin onDestroy), JNI null-check, accessibility clipboard restore. All-platform CI green; 446 tests pass.
2026-06-17 11:39:55 +08:00
TRIP 121bb11447 fix: audit batch 1 — critical Android updater + security hardening + bug fixes (#695)
14 confirmed audit findings: critical Android auto-update fix (SUPPORTED_ABIS), security hardening (exe allowlist, window-scoped approval, update-URL validation, remote PIN rate-limit/entropy, mirror order, prompt sanitization), correctness bugs (prefs race, hotkey spawn race, voice-agent focus clear), build-hygiene, and Android robustness. All-platform CI green; 446 tests pass.
2026-06-17 11:13:27 +08:00
TRIP 0eb69fc0f4 chore: codebase cleanup + behavior-preserving refactor (#693)
Audit-driven cleanup: 6 issue fixes (#689/#686/#680/#691/#678/#612), Aura skin removal (visually-neutral), dead-code cleanup (98→87 warnings), behavior-preserving modularization (coordinator.rs 6961→3083 + LocalAsr/ + polish/ submodules), and RELEASING.md (admin-only release policy). Gates: cargo test --lib 446 passed, tsc, all-platform build + contract checks green. Supersedes #685/#687/#690/#692/#682/#681.
2026-06-16 23:22:21 +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
TRIPand吕柏青 ee42d1a2e7 perf(onboarding,settings): 权限/热键状态纯事件驱动,去掉高频轮询 (#673)
* perf(onboarding,settings): 权限/热键状态纯事件驱动,去掉所有高频轮询

issue #470:引导页桌面(1s)/Android(3s)、设置页热键(1s) 的 setInterval 高频权限/热键轮询
全部改为 window focus / visibilitychange 事件驱动,连低频兜底也去掉(纯事件驱动)。
覆盖论证:麦克风前台原生弹窗授予由申请结果回调 + 800ms 兜底覆盖;辅助功能授予必经系统
设置 App、切回 OpenLess 必触发 focus/visibilitychange。并给 onGrantAccessibility 补对称的
void refresh() + 800ms 兜底(与 onRequestMicrophone 对齐)。PermissionsSection 的
refreshPermissions(10s)/refreshNetwork(30s) 保留不动。

* fix(onboarding): DesktopOnboarding effect 内 refresh() 统一加 void

@claude 审查 #673 指出:DesktopOnboarding 的 useEffect 里 refresh()/onFocus/onVisibility
三处 refresh() 是 floating promise(与上方 Android 段的 void refresh() 不一致,IPC 失败会
静默)。统一加 void 明确 fire-and-forget。issue #470。

---------

Co-authored-by: 吕柏青 <lubaiqing9@gmail.com>
2026-06-16 11:10:53 +08:00