diff --git a/docs/2.0-ux-review.md b/docs/2.0-ux-review.md deleted file mode 100644 index 7ac6eaf6..00000000 --- a/docs/2.0-ux-review.md +++ /dev/null @@ -1,85 +0,0 @@ -# Core 2.0 体验梳理与页面改进 - -状态:draft;阶段:implementation-backed;更新:2026-09-07。 - -## 基线与结论 - -实时抓取 `Open-Less/openless` 后确认,#1019 将 Core 2.0 合并到默认主线 `beta`,提交 `fc38edd1a3da3a4f2fa56f433be84278c3456ed1`。稳定 `main` 是 1.3.18 的 `08d70601`。本次按用户授权删除应用仓库旧的未提交修改,用前者替换工作区,再建立 `feat/core-2-ux-linux`。 - -项目名为 OpenLess,迁移名为 **Core 2.0**;应用显示版本 `2.0.0-Beta.1` 与后端公开合同 `2.0.0` 是不同概念。范围仍以 [2.0 需求](2.0-requirements.md)为准。 - -2.0 的重要收益是业务状态、渠道、迁移与会话取消进入共享 Core。主要体验问题是新架构已就位,界面仍让用户自己推断操作顺序:主页以统计为主,设置混放大量任务,Linux 原有领域界面顺序堆叠。本次优先整理现有功能的入口与显示,不增加另一套业务规则。 - -## 迁移领域与页面影响 - -本次按 #1019 的变更清单、公开合同、领域出口、平台交接与实际页面梳理;下表是迁移面索引和体验判断,不是逐行安全审计或全部设备验收。 - -| 领域 / 桌面验收项 | 2.0 相关实现 | 体验判断与本次处理 | -| --- | --- | --- | -| 启动、生命周期 D01 | `App.tsx`、IPC `shared.ts`、Core `api.rs` | 保留所有窗口的合同检查;补可读启动状态、重查按钮与折叠错误详情 | -| 听写、音频、风格/语言 D02/D03/D07 | Core `dictation_engine.rs`、`audio.rs`、`silence_auto_stop.rs`、`prompt_compose.rs` | 主页解释下一步;录音、外观语言拆分类;保留现有首帧、热键和停止语义 | -| 云服务、渠道与凭据 D04/D05 | Core `provider_rules.rs`、`provider_service.rs`、`credentials.rs`;`ChannelList` | 主页按有效 traditional/Omni 状态展示服务,直接进入 AI 设置;渠道规则继续用 descriptor | -| 本地模型 D06 | Core `model_store.rs`、`local_asr_service.rs`;React LocalAsr / Linux models | 仍放 AI 服务范围,保留原子激活与下载取消;Linux 管理深度缺口留在 L08 | -| 快捷键与插入 D08/D09 | Core `hotkey_interpreter.rs`、`streaming_insert.rs`;各 Host 原生 Adapter | 单独的快捷键与选区分类;权限诊断可直达;不更改原生目标和回退策略 | -| QA、选区、Agent D10–D12 | Core `qa_service.rs`、`selection*_service.rs`、`less_computer.rs` | 页头解释划词追问用途;Linux 分页仍保留全局待办、审批和取消 | -| 历史、词典与纠错 D13/D14 | Core `history.rs`、`activity.rs`、`vocabulary.rs`、`correction.rs` | 统计置于主页次级;历史在窄窗口使用列表/详情;词汇页面沿用列表与编辑布局 | -| 风格包与市场 D15/D16 | Core `style_pack_store.rs`、`style_packs.rs`、`marketplace.rs` | 保留主导航的风格分组;共同页头操作区允许换行,降低多语言窄窗拥挤 | -| 手机输入 D17 | Core `remote_input_service.rs`、各 Host TLS/H5 | React 保留录音与输入内入口;Linux 独立手机页、连接状态与陈旧地址处理 | -| 设置与维护 D18 | Core `settings.rs`、`preferences.rs`、React settings / Linux settings | 分类、搜索、自动保存提示、直接可见说明、焦点与滚动恢复 | -| 旧数据 D19 | Core credentials/model/preferences 等迁移;Host vault/data roots | 本次不改变迁移;真实升级与回滚继续按桌面/Linux 清单验收 | -| 合同与其他平台 | `backend-2.0.json`、事件 reducer、JNI 合同、构建入口 | 保留 Core/Host 边界;Android 不展示空的桌面快捷键分类;不改发布工作流 | - -## 已落实的界面设计 - -- **主页**:下一步与状态刷新前置;未读取/读取失败/未配置分别处理;本地 ASR、云 ASR 和 Omni 依据已有真实状态呈现;服务卡有配置/管理入口。活动热力图置于近期结果之后,底部卡片自然延展而不被短窗口压扁。 -- **设置**:录音与输入、快捷键与选区、AI 服务与模型、外观与语言、权限与数据、实验与扩展、关于与更新。稳定的旧 section ID 保留;分类搜索支持当前语言的用途和关键词,不搜索或暴露凭据值。 -- **AI 分区**:语言模型、语音识别、本地模型、连接与扩展各自呈现,页签随滚动保留;Omni 模式使用对应入口,避免传统模型页变空白。设置实底,不透出背后的页面文字。 -- **操作细节**:搜索不卸载当前分类草稿;选分类回到页首;Escape 先清空查询、再关闭;打开时移动焦点、关闭时返回入口;已有下拉和快捷键录制优先处理自己的按键。 -- **页面排布**:设置关键解释直接可读;共享页头不再被内容压缩,操作按钮可换行;历史窗口小于1000像素时用列表/详情切换,缩窗保留正在查看的重润色结果;翻译和划词追问补上已有用途说明。 -- **入口修复**:空本地模型目录在布局阶段反复写入相同选择值,浏览器可复现最大更新深度错误。增加变化判断后,同一服务入口恢复正常。下拉选项补可访问关联,快捷键小菜单优先消费 Escape。 -- **渠道与表单**:列表改用统一分隔行,显示名称、模型、当前使用/停用、验证结果与时间;添加放在分区头部,验证/启停/编辑操作有标签。表单按服务连接、模型设置、连接验证组织,名称作为可选信息,删除仍需确认;顶部关闭、Tab循环、子菜单Escape与焦点返回已补齐。 -- **模型与下载**:管理页减少嵌套卡片,已下载/下载中模型与所选详情各有清楚位置;空态提供下载与重新读取。下载目录有模型大小、引擎、状态、仓库和简介;仅多引擎时显示筛选,窄屏按目录/详情切换,底部操作独立滚动。保留Windows原引擎操作与取消/激活合同。 -- **Linux**:实际界面行为统一记录在 [04 页面与领域操作](linux-egui-handoff/04-ui-domains.md),剩余事项统一记录在 [02 缺口登记](linux-egui-handoff/02-gap-register.md)。 - -## 下一轮建议与优先级 - -| 优先级 | 建议 | 依据与验收 | -| --- | --- | --- | -| P1 | 将单项配置检查连接成首次听写向导 | 渠道列表现已区分当前使用与上次验证;下一步串联麦克风、服务和目标输入检查,让新用户完成一条真实听写 | -| P1 | 优先跑首装与升级后的第一条听写闭环 | 2.0 改动大量 Host/Core 边界。分别验证权限拒绝、设备断开、快捷键冲突、取消与最终落字,保留清楚的恢复入口 | -| P1 | Linux 先完成输入环境和后台工作可达,再补领域功能 | L01–L03 涉及热键、上下文、录音,直接影响后续历史/纠错。真机覆盖 X11/Wayland 和常用目标应用;不以页面数替代功能闭环 | -| P2 | 将主快捷键的编辑统一到一个明确目的地 | 当前录音页和快捷键页都有入口,属于既有行为;下一轮可保留录音页摘要并跳到快捷键编辑,需同步首次引导与迁移提示 | -| P2 | 本地/云选择围绕用户需求表达 | 用离线、速度、语言和设备需求组织模型选择;模型实际可用、下载中、加载中、失败分别反馈,不默认“已下载”等于可识别 | -| P2 | Linux 跟进模型、历史、词汇/风格/市场完整操作 | 具体范围已在 L04–L09;已有 Core API 可复用,优先交付有完整成功/失败/取消路径的一个领域 | -| P2 | 页面能力来自 Host,并给不支持项明确原因 | Linux 能力快照中的 overlay/AppImage flag 不代表 UI/更新器已接好;后续能力合同可携带不可用原因,避免两端界面各自猜测 | -| P3 | 按领域拆解持续增长的门面和原生 UI 文件 | `Core api.rs` 与 Linux `main.rs` 较大,增加定位和回归成本;在领域功能实际扩展时拆分,避免无业务目的的全面重写 | - -## 浏览器预览目录 - -普通浏览器此前的 `listProviderDescriptors` 返回空数组,2.0 渠道编辑只能停留在加载态。现在浏览器分支懒加载 Core 生成的公开目录;原生分支仍执行同一个受启动合同保护的 IPC。该文件只有公开服务元数据,没有用户凭据或认证状态,Core 继续是规则的唯一来源。 - -生成文件:`openless-all/app/src/lib/ipc/provider-descriptors.generated.json`(generated,不手工维护)。在应用目录执行以下命令重新生成: - -```sh -cargo run --locked -p openless-core --example export_provider_descriptors -``` - -生成器只调用 `openless_core::provider_rules::provider_descriptors`,没有启动应用、打开密钥环或请求外部服务。对应浏览器入口测试覆盖 ASR、LLM、Omni 目录可加载及协议字段完整性。 - -## 验证结果(2026-09-07 收口) - -| 验证 | 本次结果与范围 | -| --- | --- | -| `npm test` | 构建与72个前端/合同测试模块通过,包含实际Core快捷键回归;最后运行退出码0 | -| 既有渠道/提供商/本地模型/轮询/布局合同 | 通过;渠道重排后恢复保守布局的纵向操作区,没有通过改测试掩盖回归 | -| 五语言 | 导航与搜索路径、服务分区、渠道和本地模型文案验证通过;中英切换经浏览器检查 | -| Core目录导出 | 直接编译运行生成器通过;生成文件供预览按需加载;浏览器目录测试通过 | -| 浏览器桌面 | 主页直达、分类搜索与空结果、服务页签、完整LLM与ASR表单、字段排布、菜单Escape、Tab循环与返回焦点通过 | -| 浏览器窄屏 | 390×844下完整LLM表单和模型目录/详情;选择1.7B、返回目录保留选择、关闭返回下载入口通过。900×620历史无横向溢出;1280缩至990时重润色结果保留 | -| Linux Host | 50个单元测试与4个Host合同测试通过;本次实际运行在macOS | -| egui界面 | 临时macOS验证副本9项通过;追加Omni有效模式2项定向测试覆盖16种配置组合通过,先复现失败再修复 | -| 差异与格式 | 最终差异空白检查通过;新增Core导出示例和Linux改动的定向格式检查通过 | - -Linux目标交叉检查缺少对应`core/std`,Docker服务未运行,因此没有Linux原生编译通过证据。真机输入、音频、密钥环、窗口、真实模型下载/取消/推理与安装升级按[Linux验收](linux-egui-handoff/07-acceptance.md)及[桌面验收](2.0-desktop-acceptance.md)继续完成。浏览器使用预览数据,UI通过不代表真实服务、设备或分发验收通过。 - -本次确定的页面回归(历史缩窗状态、空目录布局更新、菜单与弹窗焦点、Omni状态)已修复;没有留下已知未处理的本轮问题。完整原生验收属于上述后续清单。代码保留在 `feat/core-2-ux-linux` 工作区,未提交或推送。 diff --git a/docs/core-platform-boundary-audit.md b/docs/core-platform-boundary-audit.md deleted file mode 100644 index 6d267f97..00000000 --- a/docs/core-platform-boundary-audit.md +++ /dev/null @@ -1,48 +0,0 @@ -# Core / Host 平台边界审计 - -> 2026-09-06范围更新:[当前2.0需求](./2.0-requirements.md)要求Windows/macOS完整保留各自1.x功能;Linux剩余Host/UI和产品验收移交egui团队,见[拆分交接目录](./linux-egui-handoff/README.md)。本审计保留代码边界证据,不表示Linux产品完整;Linux应用待办不单独阻塞本批桌面2.0。 - -审计基线:OpenLess 2.0.0-Beta.1(2026-09-04 工作树)。范围覆盖 `openless-core`、Tauri、Linux egui、React/TypeScript、C++ 插件及构建脚本;vendor、生成物和纯视觉实现排除。 - -2026-09-05 更新:下文为边界迁移索引;最新生产行为缺口、修复和证据在 -[`pr1019-2.0-final-review.md`](./pr1019-2.0-final-review.md)。有接口和通过 fixture -不能证明对应 Host 的启用、取消、资源释放或实际输入行为正确。 - -## 已迁移项 - -- Provider descriptor、Credential channel mutation/active/order、ASR/LLM/Omni、设置事务与事件位于 Core;React/egui 只渲染 descriptor 和本地化标签。 -- `LessComputerVoiceSession` 在 Core 持有 session lease、ASR snapshot、PCM 校验、TranscriptDelta、静音/fault 决策和 Agent submit。 -- 主听写与 Selection Voice 的 Hold/Toggle/Auto/Combined、press generation、250ms debounce、450ms terminal cooldown 均由 Core 解释。 -- 录音 plan、`SilenceAutoStop`、silent retry、correction 顺序/归因、流式 final reconciliation、edit observation generation 与历史重转 mutation 均位于 Core。 -- `ModelStore` 统一 1.x/custom-root 迁移与 Local ASR 原子激活;Qwen/Whisper 用 target generation lease,Linux Qwen 使用打包 runtime 和进程组 cancel/timeout。 - -## 本批修复项 - -- Shared realtime ASR 将 Qwen、StepFun、Bailian、Volcengine、讯飞 interim 回调接入 Unicode replace-from `TranscriptDelta`;React/Linux 使用同一 reducer 语义。 -- Linux production factory 注入 QA、Remote Input、Selection preview/revert、Provider/Model、Less Computer 与打包 Qwen runtime;AppImage 在 hotkey listener 前处理 fcitx5 插件。 -- Tauri 只保留窗口、原生录音/native ASR、秘密存储、插入目标、进程和协议 transport effect;Credential/QA/Selection/stream/history 的旧 Host policy 已删除并由 source gate 固定。 -- Linux/Tauri manifest、打包脚本和 AppStream 使用 `AGPL-3.0-only`;2.0.0-Beta.1 为许可证生效边界,1.x 发布物仍为 MIT。 - -## 有意保留的 Host 项 - -- Tauri 窗口、胶囊、原生录音/native ASR、系统凭据、插入和生命周期。 -- Linux cpal、fcitx5、Secret Service、资源布局和单实例。 -- Windows Foundry/Sherpa、macOS Apple Speech/MLX/Whisper 等单平台 runtime。 - -## Deferred 候选 - -- 不再为当前迁移拆独立 process/transport crate;现有 Core `AgentCommand` + Host `ProcessAdapter` 已满足边界,新增 crate 只会制造中间层。 -- Ubuntu 实际焦点输入、音频设备、Secret Service、QA/Remote/Selection、Qwen 性能与 deb/rpm/AppImage 安装升级回滚证据。 -- Windows 原目标恢复/PasteSent/TSF unknown/mute/fault/Foundry/Sherpa,macOS newline/context/edit/login-shell,以及 Android readiness/overlay/IME 的真实设备 smoke。 - -## Standards - -依赖方向保持 Host → Core;Core 不引用 Tauri/egui,凭据只经 `CredentialStore`,未注入 runtime 明确返回 `Unsupported`。共享 session 通过一个 mutable lease 和 `BackendEvent.session_id` 关联。 - -## Spec - -2.0 目标的 Core voice session、实时增量、三档热键解释和 Linux typed event 已有公共入口;平台原生录音/本地模型仍由 Adapter 提供,不能以 headless fixture 代替设备证据。 - -## 剩余风险和证据边界 - -本审计证明源码边界和可运行 contract,不证明云服务凭据、真实音频设备、CLI 安装、签名、设备运行或发布产物。Backend contract 版本为 `2.0.0`,不随应用版本升级。 diff --git a/docs/images/pr-711/dark-overview.png b/docs/images/pr-711/dark-overview.png deleted file mode 100644 index a1a12525..00000000 Binary files a/docs/images/pr-711/dark-overview.png and /dev/null differ diff --git a/docs/images/pr-711/light-overview.png b/docs/images/pr-711/light-overview.png deleted file mode 100644 index ee9cae61..00000000 Binary files a/docs/images/pr-711/light-overview.png and /dev/null differ diff --git a/docs/knowledge/2026-09-06-linux-setup.md b/docs/knowledge/2026-09-06-linux-setup.md deleted file mode 100644 index adc9d47b..00000000 --- a/docs/knowledge/2026-09-06-linux-setup.md +++ /dev/null @@ -1,11 +0,0 @@ -# Linux 环境引导:官方资料 - -状态:canonical;阶段:implementation-backed;来源类型:外部知识。 - - -获取日期:2026-09-06;新鲜度:normal;复核期限:2026-09-16;置信度:高(官方文档/源码),具体桌面可用性仍依设备验证。项目使用egui/eframe 0.31.1;以下Fcitx 5与Secret Service资料是当日上游页面,不声明为本机安装版本。 - -- [Fcitx 5设置](https://fcitx-im.org/wiki/Setup_Fcitx_5):桌面启动、输入法框架与会话配置。 -- [Fcitx 5 on Wayland](https://fcitx-im.org/wiki/Using_Fcitx_5_on_Wayland):配置依桌面与GTK/Qt等工具包而异。 -- [fcitx5-remote官方源码](https://github.com/fcitx/fcitx5/blob/master/src/tools/remote.cpp):`-r`调用`ReloadConfig`,不是插件安装/进程重启。 -- [Secret Service官方规范](https://specifications.freedesktop.org/secret-service/latest/):会话、集合锁定及解锁语义;不能从已有渠道配置推断当前vault可访问。 diff --git a/docs/linux-egui-shared-backend-plan.md b/docs/linux-egui-shared-backend-plan.md deleted file mode 100644 index 7f53a34b..00000000 --- a/docs/linux-egui-shared-backend-plan.md +++ /dev/null @@ -1,2233 +0,0 @@ -# OpenLess Linux egui 与共享后端 lib 拆分实施计划 - -> **2026-09-06 范围更新**:当前需求以[2.0交付范围](./2.0-requirements.md)、[Windows/macOS验收](./2.0-desktop-acceptance.md)及[Linux拆分交接目录](./linux-egui-handoff/README.md)为准。Windows/macOS须完整保留各自Tauri 1.x功能;Linux本批交付可接入Core,剩余Linux Host/UI和产品验收交egui团队,不再作为本批桌面2.0完成条件。下方旧阶段记录不覆盖这一新范围。 - -> 历史说明:本文保留 2026-08-31 至 2026-09-01 的分阶段设计与证据,正文中的“当前”、旧 HEAD、 -> 测试计数和 UI stub 状态不代表 PR #1019 最新实现。当前 2.0.0 接口以 -> `linux-egui-backend-contract.md`、代码中的 canonical fixtures 和 PR #1019 body 为准。 - -> 文档状态:实施与复审记录。2026-09-05 完整复核发现此前 F01–F24 的接口/fixture 通过仍遗漏生产 Adapter、取消、输入目标与 UI 状态序列错误;问题、修复及最新验证以 [`pr1019-2.0-final-review.md`](./pr1019-2.0-final-review.md) 为准。不能继续用下面的历史收口矩阵单独证明迁移完成。 -> 更新日期:2026-09-05;真实设备、签名、安装、升级和回滚仍需单独证据,最终远端 run/head 以 PR #1019 body 和本轮复审记录为准。 -> 范围:抽取无 Tauri 依赖的共享 Rust 应用核心;保留 macOS / Windows 的 Tauri 前端;为 Linux egui 前端准备公共接口、事件契约、测试适配器和构建契约 -> egui 责任(2026-09-06调整):由另一组负责剩余Linux Host原生实现/接线、egui/eframe界面、交互、视觉、设备与Linux发布验收;本轮交付稳定Core与细化缺口文档 -> 最终审查基线:推送后重新读取 PR #1019 的真实 `baseRefOid/headRefOid`;不得继续使用历史 SHA 或把 `MERGEABLE` 等同发布就绪 - -## 0. F01–F24 收口矩阵(2026-09-04) - -下表保留上一轮生产调用者与自动证据索引;本轮新发现与修复见上方链接。“设备待补”始终不等于真实平台或发布就绪。 - -| 项 | Core 生产调用链 / 已删除的 Host policy | 自动证据 | 真实平台证据 | -| --- | --- | --- | --- | -| F01 | Linux egui → Core Credential/Provider/LocalAsr activation;UI 不再持有 endpoint/model/auth 默认值 | provider、credential、Linux factory/UI contract | Ubuntu 首次配置待补 | -| F02 | Linux factory → Qa/Remote/Selection Adapter;AppImage installer 先于 listener | Linux lib/host contract、WSL 条件编译 | Ubuntu QA/Remote/preview/fcitx5 待补 | -| F03 | Tauri/Linux → `start_less_computer_voice` +统一 voice lease/cancel;Host 不再因 `take()` 丢取消路由 | Less Computer capture/cancel/approval tests | Windows/macOS 冷启动语音待补 | -| F04 | `CredentialDirectory` 持有 channel ID/type/order/enable/active;Tauri mutation policy 已删除 | provider channel facade、CredentialDirectory contract、source gate | Keychain/Secret Service A/B channel 待补 | -| F05 | ProviderDescriptor auth requirement → validate 与真实 builder;空 key 不发 Authorization | local fake HTTP no-auth ASR/LLM tests | 自建无鉴权服务待补 | -| F06 | ProviderDescriptor validation probe/static model policy → ProviderService | StepFun/DashScope/static-list/timeout/cancel/redirect tests | 各云厂商错误 key 待补 | -| F07 | 共享 `SilenceAutoStop` + typed RecordingPlan/Event/Control;Host 仅 recorder effect | 六个 silence tests、QA/Selection/Less wiring、recording fault test | 三平台 mute/fault/设备切换待补 | -| F08 | `TextInserter::begin` 固定 opaque target;Core HostContext/EditObservation generation | cursor-off零文档读取(前台应用仍冻结)、edit stale、target restore/source gate | Windows 切焦点、macOS AX/privacy 待补 | -| F09 | Pipeline correction → actual ASR/LLM labels/timing/history;Host 归因逻辑已删除 | corrected-polisher/history/provider attribution tests | Foundry GPU→CPU notice 待补 | -| F10 | `HotkeyInterpreter` 持有 press generation、grace/debounce/cooldown;Host 只发 edge | Core 29、Tauri 39、Linux hotkey contract | 三平台物理三连按待补 | -| F11 | Pipeline 从同一 archive/冻结 context 最多重试两次 | retry success/exhaust/cancel/terminal contract | 冷云 ASR 失败恢复待补 | -| F12 | H5 bounded pre-ACK queue → Core `RemoteFrameCodec`/session guard | Rust remote contract、浏览器 queue contract | 手机冷启动首词待补 | -| F13 | Core `MacosNewlineMode` 与 frozen front app;Host 只执行 LF/Return effect | serde、Terminal/非 Terminal、Unicode streaming tests | macOS Terminal/聊天框待补 | -| F14 | 唯一 `effective_pipeline_mode` 供 Dictation/QA/Selection/Auxiliary/status 使用 | flag/mode 与 credential zero-read tests | 运行中切换待补 | -| F15 | Core `PasteSent` 与 `OutcomeUnknown` 分离;不自动重试未知结果 | Core/Tauri mapping 与 contract fixture | Windows Paste/TSF 待补 | -| F16 | Core `ActiveTextInsertion` 独占 final reconciliation;两 Host 副本已删除 | streamed/tail/Unicode/diverge/fallback tests、source gate | Tauri/Linux 实际落字待补 | -| F17 | Tauri presenter 按 generation 延迟终态,fallback 卡持有窗口;主窗仍收 cue | capsule visibility/ownership/timing/message tests | Windows/macOS 可见时序待补 | -| F18 | Unicode replace-from TranscriptDelta + session/sequence reducer/replay | Rust canonical fixture、TS reducer、Linux lag replay tests | provider interim 修订待补 | -| F19 | startup 同时迁移默认与 custom ModelStore root | Qwen/Q5/Sherpa/conflict/idempotence tests | 1.x 用户目录升级待补 | -| F20 | Core LocalAsr activation transaction + target/generation lease | activation rollback/switch/release、Linux process-group tests | macOS/Windows runtime 切换待补 | -| F21 | ModelStore partial/ready/card 与 Linux 打包 Qwen runtime | model-store contract、package script/workflow checks | deb/rpm/AppImage 安装与性能待补 | -| F22 | Core AgentCommand/materialization/PATH/parser;Host 只做 I/O/spawn/kill | coding-agent parser/guard/PATH/process tests | 四 CLI 桌面启动待补 | -| F23 | 真实 StartupSnapshot/BackendEvent DTO;所有 React webview 与 Android fail-closed | Rust canonical fixture、TS gate、Android snapshot tests | Android overlay/IME 待补 | -| F24 | Credential/QA/Selection/stream/provider/history 旧 Host policy 同阶段删除 | `shared-backend-wire-contract.test.mjs` residual allowlist | 不适用;随源码复审 | - -## 1. 执行摘要 - -当前仓库已经有 Cargo `lib` crate,但这个 `lib` 仍然是 Tauri 应用本身:应用入口、Tauri builder、插件、窗口、托盘、IPC 命令注册和部分核心协调逻辑都在同一层。目标不是把现有文件整体搬家,而是建立一个真正不依赖 Tauri 或 egui 的 `openless-core`,再由两个宿主适配器使用它。 - -架构结论:原方案方向正确,但“整个后端包装进一个 lib”应理解为“把跨平台业务和状态机 -收敛到一个深的 core Module”,而不是把所有 OS 能力塞进同一个 crate。麦克风、文本插入、 -热键、凭据存储、窗口、托盘、单实例和更新器仍通过 core 拥有的最小 Interface 由平台 -Adapter 实现;否则只是把 Tauri 耦合从应用入口搬进 library,Linux 仍无法真正独立。 - -目标结构: - -```text -React UI ── Tauri 适配器 ──┐ - ├── openless-core -egui UI ── Linux 适配器 ──┘ -``` - -核心规则: - -1. `openless-core` 只承载应用业务、会话状态机、provider 调度、持久化、类型化结果和语义事件。 -2. Tauri 只承载 IPC 转换、Tauri 插件、窗口/托盘生命周期和 WebView 事件桥接。 -3. Linux适配器只承载Linux平台能力和非UI宿主接线;依2026-09-06范围,其剩余工作与egui/eframe主循环、窗口及交互一并交egui团队实现。 -4. 核心接口不能出现 `AppHandle`、`tauri::State`、WebView 窗口 label、`emit_to` 或 egui 类型。 -5. egui 团队只接收稳定的 Rust 接口和事件契约,不需要了解核心内部模块。 -6. Android 暂时继续作为 Tauri mobile 适配器;本计划不把 Android UI 改成 egui,也不改变现有 Android 语义。 - -### 1.0.1 责任矩阵与移交门 - -| 责任方 | 本计划内的交付 | 明确不负责 | -| --- | --- | --- | -| 共享后端/架构组 | `openless-core`真实领域实现、facade、状态机、平台Interface、事件/错误/能力合同、fixture/headless、兼容测试与Linux拆分交接资料;修复接入所需Core缺口 | Linux剩余Host/UI及Linux完整产品验收 | -| Tauri组 | Windows/macOS完整保留各自1.x功能,React IPC/event接线、原生Adapter、自动与设备/安装验收;保留现有Android合同不回退 | 在command中重新实现Core业务规则;本次不新增Android首批完整支持承诺 | -| Linux Host工作(移交egui团队) | 复用已有Secret Service、fcitx5、cpal、单实例/runtime;补原生效果、全局热键、上下文/归档、系统集成与设备/发布验收 | 重写Core领域规则或将OS细节塞进Core | -| egui组 | 承接上一行Host工作,并基于2.0.0合同补齐页面、交互与Linux产品验收;缺口逐项见交接目录 | 读取Core私有模块、include Tauri源码、复制业务规则,或在UI绕过LinuxHost事务/读取秘密 | - -**当前移交门**:按[2.0需求第4节](./2.0-requirements.md#4-core-对-linux-的交付门)提供真实Core实现、可运行合同/示例与[拆分交接资料](./linux-egui-handoff/README.md)。保留已有Linux实现;未接入的原生效果明确表达不可用并移交,不等待Linux全部设备/UI验收才交付Windows/macOS。后文M7为历史阶段记录。 - -完成的定义: - -- macOS / Windows Tauri 功能仍通过原有 React IPC 契约工作。 -- Linux egui 程序可以直接依赖 `openless-core`,不编译 Tauri、WebKitGTK 或 Tauri plugin。 -- 两个宿主都能使用同一套听写、润色、设置、历史、词典、风格包和 provider 业务规则。 -- 核心可以在无窗口、无 WebView、无真实麦克风的测试环境中通过 fake adapter 验证。 -- egui 团队获得版本化的公共接口、事件语义、能力矩阵、错误码、示例 host 和 headless 测试夹具。 - -### 1.1 当前实施状态(2026-08-31) - -此表只描述当前工作树中的可验证状态,不替代后面的最终验收清单: - -状态统一使用以下口径:`已完成` 表示该里程碑的退出条件和当前相关门禁均已通过; -`已完成(Interface)` 只表示 egui 团队可以依赖的 Interface 已稳定,不表示真实 Linux 原生能力或 -正式产物已经验收;`进行中` 表示仍有源码、兼容、原生或发布证据缺口。任何会影响既有证据的 -后续改动都会使对应门禁重新变为待验证,历史测试数字只保留作参考,不能继续标为“最新通过”。 - -| 阶段 | 状态 | 当前证据 | 仍需完成 | -| --- | --- | --- | --- | -| M0 | 已完成 | 196 command / 30 legacy event / 29 core event kind 的机器基线、drift check 和平台/调用/版本决策已冻结 | 只有破坏性 Interface 变更才重新打开决策 | -| M1 | 已完成 | 根 workspace 仅含 core/Linux;Tauri 与 backend compatibility tests 使用独立 manifest/lockfile;依赖门禁通过 | 对应原生 target 仍由 CI 证明 | -| M2 | 已完成(Interface) | 共享 `types.rs` 已由 core 类型重导出;听写、设置、历史、词典、风格包、凭据及复杂领域 DTO/错误/serde fixture 已建立;快捷键语法、冲突规则、完整设置 DTO 和 Linux validated settings 公共 contract 已进入稳定 Interface;style-pack prompt 诊断和 ASR 热词排序也已收敛到 Core facade | 破坏性 Interface 变更才重新打开版本迁移 | -| M3 | 已完成(Interface) | lifecycle、`DictationEngine`、`AudioRecorder`、`TranscriptionEngine`、`TextPolisher`、progress sink、host/inserter/credential/resource Interfaces、`BackendServices` 与 fake/unsupported Adapter 已有 | 新 seam 仍须满足“两个真实 Adapter,或一个真实 Adapter 加一个测试替身”的建立条件 | -| M4 | 进行中 | core 已有完整 Pipeline、录音 level、sequence/session/lagged/late-result/cancel race tests;成功/失败 history、实测 ASR/润色耗时、失败录音保留、成功录音隐私清理和最终纠正规则已由 core 统一;backend 实例拥有 2048 条有界事件 replay,Less Computer 不再使用进程级静态 backlog;Android stop-time translation、remote external PCM、桌面普通听写的 Core Pressed/Released/Combined 和 Esc 取消已使用冻结 session context 与共享 Core 状态机;QA 的 phase/message/cancel/conversation 真相也已归 `QaService`,窗口可见性由 Coordinator/QA Adapter 共享的 `TauriQaHostContext` 持有,不再存在第二份 `QaHostState`;30 个 legacy event 已完成归类,原 12 个待迁移事件已获得 typed core event;`Coordinator::Inner` 与 `capsule_focus` 已恢复 module 私有,`bind_app(AppHandle)` 已删除,Coordinator/capsule 子模块中的 `AppHandle`、`WebviewWindow` 和直接 Tauri emit 已清零;capsule 的原生窗口操作、布局/穿透/style/fallback cache 与 deferred payload 均归 `TauriCoordinatorHost`,`TauriCapsuleWindow::apply_capsule_payload` 只接收窄值 | Core `LessComputerVoiceSession` 现统一 capture lease、ASR 取消、PCM 校验、TranscriptDelta final 和 Agent submit;Linux/Tauri edge 仅做宿主生命周期适配,pending stop 与静音策略继续由各自 Host 触发 Core;旧 compatibility `Coordinator` 仍持有显式 Tauri Host,并承担部分热键仲裁、native runtime 生命周期和兼容编排;继续完成宿主边界审计并取得 Android/macOS/Windows/Ubuntu 原生验证 | -| M5 | 进行中 | preferences/history/activity/vocabulary/correction/style-pack/ZIP/output-cleaning/credentials、完整 prompt compose、实时云 ASR/LLM/Omni 协议、provider 默认值/凭据路由/取消与 QA answer 已迁入 core;设置事务现由 Core 统一 legacy 同步、strict/reconcile、style 保留、typed effect plan、单写入 gate、乐观 revision、一次持久化/事件及 receipt 补偿;Selection Voice 的 transcript correction、instruction polish、自动意图模型/fallback、输出模式、EditPlan、translation 与 QA preview 迭代现由 Core 高层 use-case 统一;Tauri/Linux Adapter 只消费显式 action/target;Coding Agent、Local ASR、Marketplace、Selection、Selection Voice、QA、Remote Input 及 Provider 管理面(`ProviderService::validate/list_models`)均已有 Core Implementation | native/local ASR、平台录音、socket、窗口、授权和系统 effect 继续留在 Adapter;Selection Voice/QA/Remote Input、Provider 真实网络和设置原生 effect 仍需完整平台证明 | -| M6 | 进行中 | Tauri 已管理 `Arc`;生产云 ASR/LLM/Omni/Auxiliary/QA/Provider 管理面的运行时均调用 core 共享 Implementation,Tauri 只注入 `SystemCredentialStore`、平台录音、native/local ASR、窗口/插入与系统 runtime;Selection Voice Adapter 只提交原始 transcript、执行 Core `SelectionVoiceEditAction`、保存 opaque insertion target 并回报 apply outcome,源码门禁禁止业务规则回流;React command、CLI、Android JNI、remote WebSocket PCM、桌面普通听写热键及复杂领域的业务调用均调用 core Interface;Core `LessComputerVoiceSession` 已可供非 Tauri Host 使用并固定 provider/model、审批、continuation、stream 和终态,旧 Coordinator 语音 recorder/ASR 兼容编排仍待替换;Host 只注入 recorder/native runtime 与热键边沿;legacy provider/Selection Voice 业务副本及仅供历史测试使用的 coordinator runner/approval helper 已删除 | Provider command 已收窄为参数/错误转换,Linux factory 已接入同一 Core service、Generic/Qwen ASR CLI 和 Coding Agent runtime;继续收窄旧 Coordinator 兼容 host state,并补齐 Android/macOS/Ubuntu 原生证据 | -| M7 | 已完成(Interface) | `BackendServices` 全领域 Interface、完整 headless/unsupported 示例、Linux host contract、能力 fixture 和 unsupported 语义已交付;`LinuxHost::save_settings`/`update_settings_strict` 强制 snapshot revision;4 项公共 host contract 已覆盖设置事务以及 Selection/Selection Voice 的 preview、confirm、cancel、stale、outcome-unknown 与 Linux preview/revert `Unsupported`;Provider 管理面已有 Core/Tauri/Linux 接线和源码契约;当前公共面门禁和 headless 示例运行通过 | egui/eframe UI、交互、视觉与 UI 验收由另一组负责,不属于本交付 | -| M8 | 进行中 | Linux Secret Service、资源布局、fcitx5 插入、cpal 录音、DBus 热键 listener、HostActions、能力矩阵、单实例与统一 `LinuxNativeRuntime` 已实现;`LinuxBackendBuilder::from_shared_providers(config)` 无需 UI 注入 provider factory,即可组装共享云 ASR/LLM/Omni/Auxiliary、ProviderService、Marketplace、传统 Pipeline、凭据和 settings runtime;`LinuxHost` 暴露同一 ProviderApi;`LinuxHost::download_marketplace_archive` 提供不覆盖已有文件的 filesystem sink;`LinuxSettingsRuntime` 按 receipt 恢复显式 effect;WSL Ubuntu 已通过真实 Secret Service adapter 的 set/read/remove + secret 边界 contract、fcitx5 插件加载/DBus method/listener/press-release-combined-translation signal contract、cpal 无输入设备的稳定错误分类,以及 desktop/AppStream metadata 校验 | 仍需真实焦点输入上下文中的按键/translation 顺序、存在音频设备时 cpal start/stop、settings effect/单实例退出的桌面流程,以及正式 Ubuntu runner 的安装/签名证据;合成 DBus signal 和 WSL contract 不能替代这些证明 | -| M9 | 进行中(CI runner 门禁已通过;原生安装/设备证据待完成) | fork CI run 33408317390(head `06e85f7b`)四个平台 job 全部成功:Linux `openless-core` 596 unit + 79 contract、Linux crate 30 + 4 host contract(3 个真实 Linux native contract 明确 ignored);macOS Tauri 737 tests(730 passed、7 ignored)、Windows Tauri/Core checks、Android `aarch64`/`x86_64` mobile compile/Gradle/JVM/instrumentation/Keystore contract;frontend/contract 58、196/30/29 基线、依赖/秘密/隔离/runtime/public-surface/source/headless 等门禁均通过 | 仍缺 Android 签名安装/设备运行、macOS/Windows 安装升级 smoke、Ubuntu 真实桌面输入/音频/设置流程及正式 runner 的签名安装证据;Linux UI stub 仍不属于产品验收 | -| M10 | 进行中(验证产物已可生成;正式发布待外部门) | Tauri/Linux release workflow 已拆分;Linux deb/rpm/AppImage/fcitx5/updater 契约和 README/RELEASING 已加入;CI run 33408317390 的 Linux artifact job 成功上传并校验 `openless-linux-egui-x86_64`(artifact ID 9764249814),手动 Tauri/Android workflow 也已分别生成桌面和四 ABI debug artifact | UI stub 未替换,故 Linux workflow 不监听 tag;正式签名密钥、真实 Ubuntu runner 安装/运行 proof、正式 macOS/Windows/Android 签名安装仍缺 | - -### 1.1.1 当前 Coordinator 收口边界 - -本节是对上表中 M4/M6 状态的代码级澄清,防止把“Core 已提供接口”误读成“所有生产入口都已迁移”。 - -- Less Computer 的文字入口,以及宿主完成录音/ASR 后的 - `run_voice_agent_transcript -> submit_less_computer_with_session`,已经使用 Core 的 - provider、prompt、护栏、审批、continuation、stream 和终态规则。 -- Less Computer 的热键按下会先调用 - `OpenLessBackend::begin_less_computer_capture` 预留 Core capture lease,再由 - [`coordinator/hotkey_loops.rs`](../openless-all/app/src-tauri/src/coordinator/hotkey_loops.rs#L779-L849) - 以同一个 session id 启动兼容 Coordinator 的 recorder/ASR;松开、Starting pending stop、 - 静音自动停止最终仍复用 `end_session`,但转录提交、Agent 运行、审批、取消和终态全部由 - Core 负责。启动失败、空转写和取消会释放未提升的 capture lease。 -- Coordinator 的 `state`、`voice_agent`、`pending_stop` 仍是 Tauri 兼容层的录音/热键状态, - 不是 Linux egui 可见的业务真相;Linux host 应直接使用 Core facade 的 active session、 - cancellation 和 typed events,不读取这些字段。普通听写的 Pressed/Released/Combined/Esc - 继续使用 Core dictation 状态机。 -- QA 编辑预览需要把平台的 opaque selection target 绑定到 Core preview;该动作现在由构造阶段 - 注入 `TauriQaHostContext` 的弱引用回调完成。QA Adapter 不再通过 `AppHandle.try_state` 反查 - `Coordinator`,并由 source contract 和 focused test 守护这一边界。 -- 本批迁移的完成判据是:Less Computer 的跨宿主身份使用同一个 Core session id,任何 Host - 只负责捕获资源和生命周期边沿;Provider、prompt、approval、continuation、stream、终态 - 和 cancellation 语义只能由 Core 产生,并由 headless/compatibility contract 覆盖。 - -### 1.2 本轮原生验证记录(2026-08-29) - -以下 Linux 证据来自 WSL2 Ubuntu(不是 Windows 交叉编译),Android Rust cross-target 证据来自 -当前 Windows 主机;临时 minisign key 只位于 WSL `/tmp`,未写入仓库: - -- `dbus-run-session` + `gnome-keyring-daemon --unlock --components=secrets` 下,`secret_service_contract` 以 `--ignored` 显式通过 1 项:adapter 实际写入、读取、删除 Secret Service 项,metadata 文件不包含 secret。 -- 系统安装并加载仓库构建的 `libopenless.so` 后,`fcitx5` 真实 DBus 对象注册成功;`fcitx5_contract` 以 `--ignored` 通过 1 项,覆盖 `Fcitx5HotkeyListener` 启停、DBus method、press/release/combined/translation signal 顺序映射和 no-focused-input 的安全返回。`CommitText(s: text) -> b` 的 `false` 被 Rust Adapter 转为明确的平台/插入失败,不能当作成功;测试后 fcitx5 进程仍存活。真实桌面输入上下文的物理按键顺序仍需 runner 证明。 -- `cpal_contract` 以 `--ignored` 通过 1 项;WSL 当前无 ALSA 输入设备,adapter 返回 `Platform`/`PermissionDenied`/`Unsupported` 中的明确错误而不是 panic 或假成功。存在真实设备时的 stream start/stop 仍需 runner 证明。 -- Android Tauri mobile 初始化、脚手架复制和 manifest 合并脚本均已在当前工作树执行成功;随后使用 - `cargo ndk -t arm64-v8a check --manifest-path "src-tauri/Cargo.toml"` 与 - `cargo ndk -t x86_64 check --manifest-path "src-tauri/Cargo.toml"` 通过,证明两个 Android - Rust target 的源码/依赖可编译。该证据不包含 JNI 在设备上的运行、Gradle/JVM 编译、APK/AAB - 组装、instrumentation 或签名安装;本机 Gradle cache 缺少 - `com.android.tools.build:gradle:8.11.0`,在线解析未稳定完成,因此这些项目必须由具备完整 - Android/Gradle cache 的 CI runner 证明。临时生成目录中的 AGP 版本尝试已恢复为仓库声明版本, - 未修改 CI 版本契约。 -- `desktop-file-validate` 与 `appstreamcli validate --no-net` 通过;AppStream metadata 已补齐 description/homepage。 -- 重新生成的 Linux 产物位于 `openless-all/app/target/linux-egui-packages/`:deb、rpm、AppImage;release binary/plugin 的 `ldd` 无 `not found`,且无 Tauri/Wry/WebKit 依赖;deb/rpm/AppImage 内容均含 binary、desktop/AppStream metadata 和 fcitx5 plugin。使用临时 minisign key 对 AppImage 的签名/验签已通过;独立 updater manifest 由 release workflow 生成,正式发布必须注入正式 secret,当前不把临时签名当作可发布凭据。 - -### 1.2.1 历史 Windows 本地重验(2026-08-30) - -本轮只记录当前工作树可在 Windows 主机复现的证据;它不能替代 Android、macOS 或真实 -Ubuntu 桌面 runner 的原生证明: - -- `npm.cmd test`(包含 pretest build)退出码为 0,发现并执行 58 项前端/契约测试; -- `cargo test --locked --manifest-path "src-tauri/Cargo.toml" --lib` 运行 752 个单元测试, - 结果为 745 passed、0 failed、7 ignored;Provider 旧 command 测试旁路已删除,解析与模型 - 响应测试归入 Core `ProviderService`。 -- `cargo test --locked -p openless-core` 运行 594 个 unit tests,领域 integration contract - 另有 79 项,全部通过; -- `cargo test --locked -p openless-linux-egui --all-targets` 运行 29 个 Linux crate tests 和 - 4 个 host contract tests;Secret Service/fcitx5/cpal 原生 contract 在 Windows 以 0 tests - 保持 ignored,不被误报为 Linux 原生成功; -- Core/Linux clippy(`-D warnings`)、workspace fmt、command/event baseline(196/30/29)、 - core/Linux 依赖、secret surface、test isolation、runtime seam、Linux public surface、 - source contract、headless example 和 `git diff --check` 均通过; -- 修正 `release-linux-egui.yml` 的版本解析路径:该步骤的 - `working-directory: openless-all/app` 现在读取 `src-tauri/Cargo.toml`,不会再拼出重复的 - `openless-all/app/openless-all/app` 路径;本地已用同一工作目录解析出 - `1.3.18-Beta.7`。 - -以上结果证明共享 core、Linux Interface 和 Tauri compatibility 在当前工作树可构建并通过 -本地契约;不证明真实音频设备、焦点输入、fcitx5 物理按键顺序、安装/签名、Android APK/JNI -或 macOS/Windows 安装包行为。 - -### 1.2.2 跨平台 CI runner 验收(2026-08-31) - -提交 `06e85f7b8b9e93db7df276952a18825e245e7c37` 在 fork 的 [CI run 33408317390](https://github.com/H-Chris233/openless/actions/runs/33408317390) 上四个平台及 Linux artifact job 全部成功: - -- Linux core and egui host:Core 596 unit + 79 contract、Linux crate 30 tests + 4 host contract、严格 clippy 和依赖/秘密/隔离/runtime/public-surface 门禁通过。 -- Android cargo check:`aarch64`/`x86_64` Tauri Rust check、Gradle scaffolding、JVM unit/instrumentation tests 和 Android Keystore instrumentation 通过。 -- Windows checks:前端/契约 58 项、Tauri check、Windows backend test compile、Rust-only backend tests、Core contract、Rust 1.88 MSRV 和五处版本同步通过。 -- macOS checks:前端/契约 58 项、Qwen3/Tauri check、737 个 Tauri Rust unit tests(730 passed、7 ignored)、Rust 1.88 MSRV、backend test compile 和版本同步通过。 -- Linux egui validation artifact:无 Tauri 的 deb/rpm/AppImage、fcitx5 plugin、ELF/包内容/desktop/AppStream 和 updater manifest SHA-256 校验通过。 - -该 run 证明当前提交在声明的原生 runner 上可编译并通过已配置的契约;它不等同于正式 release workflow 的安装包、签名、设备输入/音频或 egui UI 视觉验收。Linux egui UI 仍由 egui 组实现,正式发布仍按 M10 的 release gate 执行。 - -### 1.2.3 远端验证 artifact(2026-08-31) - -- Linux artifact job [run 33408317390](https://github.com/H-Chris233/openless/actions/runs/33408317390) 成功上传 artifact `openless-linux-egui-x86_64`(ID `9764249814`),包含 1 个 deb、1 个 rpm、1 个 AppImage 和 `latest-linux-egui-x86_64.json`;下载后的 AppImage SHA-256 `f9e061c7b27ba26eff886a68b1acaaf561a67389447f485b8593a02e341a9307` 与 manifest 一致,manifest URL 指向 `H-Chris233/openless`。 -- Tauri 手动构建 [run 33405500864](https://github.com/H-Chris233/openless/actions/runs/33405500864)(commit `80be78c2`)三个 job 全部成功,上传 macOS arm64/x86_64 DMG(artifact IDs `9757421887`、`9757475524`)和 Windows x64 NSIS 安装包(ID `9757290871`);Windows runner 的 NSIS 安装/卸载与 IME smoke 通过,非数字 Beta 版本明确跳过 MSI。 -- Android 手动构建 [run 33405500972](https://github.com/H-Chris233/openless/actions/runs/33405500972)(commit `80be78c2`)成功上传四个 ABI debug APK(artifact IDs `9757082911`、`9757086428`、`9757090328`、`9757094381`);`Collect split APKs` 已校验每个 APK 只包含一个预期 ABI,artifact 均未过期。 - -以上是 CI 验证 artifact,不是正式签名发布:当前 Linux UI 仍是 stub,Tauri/Android 手动构建未注入正式签名密钥;真实设备安装、升级/回滚和 Ubuntu 桌面输入/音频仍由 12.4 未勾选门禁负责。 - -### 1.3 外部依据与本项目决策映射 - -本节只记录用于验证分层方向的公开一手资料;具体接口、兼容字段和完成状态仍以仓库代码、contract tests -和对应平台 runner 为准(资料核对日期:2026-08-29)。 - -| 依据 | 可采用的事实 | 对本项目的决策 | -| --- | --- | --- | -| [Tauri Architecture](https://v2.tauri.app/concept/architecture/) | Tauri 的 Rust 应用层、WebView 前端以及 command/event 通道属于宿主运行时边界 | `src-tauri` 只做 IPC、WebView、窗口、托盘、插件和移动端宿主;业务状态不能反向依赖 Tauri 类型 | -| [Cargo Workspaces](https://doc.rust-lang.org/cargo/reference/workspaces.html) | workspace 成员共享解析/构建上下文;`exclude` 和独立 manifest 可隔离不应参与某一构建的 package | Linux workspace 只解析 `openless-core` 与 Linux adapter;Tauri 保持独立 manifest/lockfile,避免 Linux 构建解析 Tauri/native path dependency | -| [eframe API](https://docs.rs/eframe/latest/eframe/) / [egui API](https://docs.rs/egui/latest/egui/) | eframe/egui 提供 native/web GUI application loop、绘制和 UI 状态承载 | `egui`/`eframe` 只进入 Linux UI crate;core 只交付同步快照、非阻塞事件订阅、异步 use-case 和 host action,不规定布局或视觉 | - -由上述事实得到的项目结论是:用户提出的“后端包装进 lib、Tauri 薄包装、Linux 用 egui”方向正确, -但 lib 必须是“业务核心 + 由 core 所有的最小平台 Interface”,不能把窗口、热键、录音、凭据、 -文本插入等 OS 实现继续塞进同一个跨平台 crate。否则 Linux 虽然不直接编译 Tauri,仍会被错误的 -宿主耦合或不可替换的系统实现卡住。 - -## 2. 问题定义与现状证据 - -| 事实 | 位置 | 影响 | -| --- | --- | --- | -| Cargo 已声明 `openless_lib`,但主依赖包含 Tauri | [`src-tauri/Cargo.toml:9-23`](../openless-all/app/src-tauri/Cargo.toml#L9-L23) | 现有 `lib` 不是框架无关核心 | -| `run()` 直接分派到 `run_desktop()`,并在其中创建 Tauri builder | [`src-tauri/src/lib.rs:144-153`](../openless-all/app/src-tauri/src/lib.rs#L144-L153)、[`lib.rs:494-519`](../openless-all/app/src-tauri/src/lib.rs#L494-L519) | 启动和业务模块无法被 egui 直接复用 | -| 原 `Coordinator::Inner` 直接保存 `AppHandle`;当前已替换为显式 `TauriCoordinatorHost`,`Inner`/`capsule_focus` 已恢复私有,`bind_app(AppHandle)` 已删除,Coordinator/capsule 子模块中的 `AppHandle`、`WebviewWindow`、直接 emit 和直接 `tauri::async_runtime` 调用均已清零;原生 capsule window code/cache 已移入 Host | [`coordinator.rs`](../openless-all/app/src-tauri/src/coordinator.rs)、[`tauri_coordinator_host.rs`](../openless-all/app/src-tauri/src/tauri_coordinator_host.rs) | 窗口、事件和运行时 seam 已显式隔离;compatibility Coordinator 仍承担部分 Tauri-only 热键仲裁、native runtime 生命周期和兼容编排,尚未达到删除兼容层的终态 | -| 命令层以 `State`、`AppHandle`、`Window` 作为参数 | [`commands/mod.rs:11-24`](../openless-all/app/src-tauri/src/commands/mod.rs#L11-L24)、[`commands/mod.rs:130-148`](../openless-all/app/src-tauri/src/commands/mod.rs#L130-L148) | Tauri command 不是可移植的公共接口 | -| 兼容面包含 30 个旧 Tauri event,现已全部分类;原 12 个待迁移事件已集中映射 | [`linux-egui-command-event-baseline.json`](./linux-egui-command-event-baseline.json)、[`tauri_events.rs`](../openless-all/app/src-tauri/src/tauri_events.rs) | 后续新增业务事件必须先定义 core 语义事件,再由各宿主映射;纯窗口事件继续只归 Tauri host | -| Linux 入口包含 WebKitGTK compositing workaround | [`src-tauri/src/main.rs:4-20`](../openless-all/app/src-tauri/src/main.rs#L4-L20)、[`lib.rs:694-729`](../openless-all/app/src-tauri/src/lib.rs#L694-L729) | 分离 egui 的主要动机是降低 Linux WebView 风险,但 Wayland 仍需单独验证 | -| 原 Linux fcitx5 资源安装从 Tauri 取路径;当前 `openless-linux-egui` 已用 `LinuxResourceLayout`/`FcitxPluginInstallPlan` 独立实现 | [`linux-egui/src/resources.rs`](../openless-all/app/linux-egui/src/resources.rs)、[`linux-egui/src/fcitx5.rs`](../openless-all/app/linux-egui/src/fcitx5.rs) | Linux package 不再依赖 Tauri;真实安装顺序仍需 Ubuntu proof | -| 旧 Rust-only backend test 曾通过 path include 和 Tauri stub 绕开完整应用;当前该旁路已删除 | [`backend-tests/Cargo.toml`](../openless-all/app/src-tauri/backend-tests/Cargo.toml)、[`core_contract.rs`](../openless-all/app/src-tauri/backend-tests/tests/core_contract.rs) | compatibility package 现在只验证公开 core contract;原 Tauri 单测必须在 Tauri crate 自身运行,不能再复制源码 | -| React IPC 已按领域拆成多个模块 | [`src/lib/ipc/index.ts:1-20`](../openless-all/app/src/lib/ipc/index.ts#L1-L20) | 可保留现有 command 名称,降低 Tauri 迁移风险 | - -历史发布工作流曾把 Linux 放在 Tauri 矩阵中并安装 WebKitGTK。当前工作树已经把 Linux 从 -[`release-tauri.yml`](../.github/workflows/release-tauri.yml) 移出,并建立独立的 -[`release-linux-egui.yml`](../.github/workflows/release-linux-egui.yml)。在真实 egui 入口替换 -stub 前,Linux workflow 只允许手动或复用调用,不能由 release tag 自动发布。 - -## 3. 目标与非目标 - -### 3.1 目标 - -- 建立不依赖 Tauri / egui 的 `openless-core` Rust library。 -- 把 Coordinator、ASR/LLM pipeline、持久化和业务类型放到 core 的清晰模块中。 -- 通过类型化接口提供同步查询、异步命令、取消、快照和事件订阅。 -- 用宿主适配器承载窗口、托盘、权限、更新、开机自启、单实例和系统集成。 -- 保留现有 React IPC command 名称及其 JSON 字段兼容性,作为 Tauri 适配器的兼容层。 -- 为 Linux egui 团队提供可独立开发的接口包、示例、mock、事件映射和 headless 验证。 -- 把现有 Rust backend tests 迁移为 core 的单元测试和 adapter integration tests。 -- 在 CI 中证明 Linux egui package 的依赖树没有 Tauri/WebKitGTK。 - -### 3.2 非目标 - -- 本计划不实现 egui 页面、视觉设计、控件、布局、动画或 UI 自动化。 -- 不要求 React 与 egui 像素级一致;只要求业务语义和能力契约一致。 -- 不把所有代码强行合并为一个几千行的 `lib.rs`;一个 library 可以内部由多个深模块组成。 -- 不在第一阶段将后端拆成大量独立远程进程或引入 JSON/RPC;同进程 Rust 调用应保持类型化。 -- 不改变 Android 当前 Tauri mobile 适配器的产品行为。 -- 不顺手修改 provider 协议、ASR 模型、提示词、发布版本号或无关 UI 行为。 - -## 4. 目标包结构 - -### 4.1 推荐目录 - -在 `openless-all/app` 建立只包含 core/Linux 的 Cargo workspace;现有 `src-tauri` 和 -`src-tauri/backend-tests` 各自保留独立 manifest/lockfile。这样执行 Linux package 命令时, -Cargo 不会为了加载 workspace 元数据而解析 Tauri 的 macOS-only path dependency: - -```text -openless-all/app/ - Cargo.toml # core/Linux workspace root 与独立 Cargo.lock - crates/ - openless-core/ - Cargo.toml # 不出现 tauri、egui、eframe - src/ - lib.rs - api.rs # 对外 facade 和 use-case 接口 - events.rs # 语义事件和事件订阅 - errors.rs # 稳定错误码 - config.rs # BackendConfig / 路径 / 能力 - types.rs # 跨宿主共享 DTO 和领域类型 - coordinator/ - asr/ - polish/ - persistence/ - providers/ - ... - src-tauri/ # 根 workspace 显式 exclude - Cargo.toml # 独立 Tauri Adapter + macOS/Windows/Android host - Cargo.lock - src/ - main.rs # Tauri desktop/mobile entry - lib.rs # Tauri setup、commands、window/tray lifecycle - commands/ # 薄 command adapter - tauri_events.rs # core event -> WebView event bridge - host/ # Tauri-specific host actions - linux-egui/ - Cargo.toml # 当前为非 UI Linux Adapter;egui 团队在此接入 eframe - src/ - main.rs # 由 egui 团队实现 - host.rs # Linux host adapter;本计划提供接口 - src-tauri/backend-tests/ # 独立 compatibility-test package - Cargo.toml - Cargo.lock -``` - -当前已建立隔离后的 workspace 骨架:[`openless-all/app/Cargo.toml`](../openless-all/app/Cargo.toml)、 -[`openless-core`](../openless-all/app/crates/openless-core/)、 -[`openless-linux-egui`](../openless-all/app/linux-egui/)。Tauri 通过 path dependency 使用 -core,但不会成为 Linux workspace 的解析依赖。该结构不代表 Coordinator 和复杂 provider -实现已经完成迁移;实际状态以 1.1 表为准。 - -### 4.2 依赖方向 - -```text -openless-core - ├── serde / tokio / reqwest / persistence dependencies - ├── platform Interfaces (traits owned by core) - └── no Tauri, no egui, no WebView type - -openless-tauri(现有 Cargo package 名称可继续为 openless) ──> openless-core - ├── Tauri commands / plugins / windows / tray - └── React IPC event names - -openless-linux-egui ──> openless-core - ├── 当前:Linux credentials / fcitx5 / resources / capabilities / host actions - └── 后续由 UI 团队加入 eframe / egui 与窗口/托盘交互 -``` - -禁止以下反向依赖: - -- core `use tauri::*` -- core `use egui::*` 或 `use eframe::*` -- core 读取窗口 label(`main`、`capsule`、`qa`、`less-computer`) -- core 直接 `emit_to`、创建 WebView、调用 Tauri plugin -- Linux egui crate 通过 path include 复用 `src-tauri/src/*.rs` -- Tauri adapter 把业务判断重新实现一遍,导致第二份真相 - -## 5. 共享核心接口设计 - -以下 Interface 已在 M0/M1 冻结;后续只能按 contract version 规则演进,不能由某个宿主单方面改名或改变语义。 - -### 5.1 Backend facade - -核心对宿主提供构造、生命周期、快照、事件和领域 use-case 的 facade;复杂领域通过 -`BackendServices` 暴露稳定 Interface: - -```rust -pub struct OpenLessBackend { /* private state and adapters */ } - -impl OpenLessBackend { - pub fn new(config: BackendConfig, deps: BackendDependencies) - -> Result; - - pub async fn start(&self) -> Result; - pub async fn shutdown(&self) -> Result<(), BackendError>; - - pub fn snapshot(&self) -> BackendSnapshot; - pub fn subscribe(&self) -> EventSubscription; - pub fn services(&self) -> &BackendServices; - - pub async fn start_dictation(&self) -> Result; - pub async fn stop_dictation(&self) -> Result; - pub async fn cancel_dictation(&self, session: Option) - -> Result<(), BackendError>; - - pub async fn submit_less_computer(&self, transcript: String) - -> Result; - pub fn begin_less_computer_capture(&self, session: SessionId) - -> Result<(), BackendError>; - pub fn less_computer_active_session(&self) -> Option; - pub fn less_computer_capture_cancelled(&self, session: SessionId) -> bool; - pub fn abort_less_computer_capture(&self, session: SessionId) - -> Result<(), BackendError>; - pub async fn submit_less_computer_with_session( - &self, - session: SessionId, - transcript: String, - ) -> Result; - pub async fn cancel_less_computer(&self, session: Option) - -> Result<(), BackendError>; -} -``` - -以上 facade 已在 `openless-core` 中落地。`BackendServices` 当前包含 `ProviderApi`、 -`LocalAsrApi`、`SelectionApi`、`QaApi`、`RemoteInputApi`、`MarketplaceApi`、 -`CodingAgentApi`、`PlatformApi` 和 `AuxiliaryApi`。宿主尚未注入的领域统一返回稳定的 `Unsupported`, -不得伪造成功;这让 egui 团队可以先针对 Interface 编写 view-model tests,而不会误判运行能力。 - -约束: - -- `OpenLessBackend` 必须 `Send + Sync`,可以被 Tauri `State>` 或 Linux host 持有。 -- `new()` 不应隐式创建窗口、弹权限或启动全局热键;启动副作用由 `start()` 和 host lifecycle 明确触发。 -- `shutdown()` 必须幂等,重复调用不能 panic,也不能遗留录音、热键、下载或 provider task。 -- `snapshot()` 返回可安全克隆的 owned 数据;不暴露内部锁、引用或生命周期参数。 -- 所有长任务必须异步执行;宿主 UI 线程不能等待网络、模型加载、录音或磁盘 I/O。 - -### 5.2 领域接口分组 - -不要把现有 196 个 Tauri command 原封不动暴露为 196 个核心方法。按当前 IPC 领域模块形成稳定的 use-case Interface: - -| 领域接口 | 负责内容 | 典型操作 | -| --- | --- | --- | -| `DictationApi` | 听写会话和插入结果 | start/stop/cancel、状态快照、会话结果 | -| `SettingsApi` | 用户偏好和默认模式 | get、patch、默认风格提示词 | -| `CredentialsApi` | 凭据状态与安全读写 | status、set、read;UI 默认只拿 status | -| `ProviderApi` | ASR/LLM/Omni provider | 列表、验证、模型列表、激活项;实现必须由 Core `ProviderService` 提供,宿主只注入 credential/transport | -| `HistoryApi` | 历史与活动统计 | list/delete/clear/stats、录音导出 | -| `VocabularyApi` | 词典、纠正规则和建议 | list/add/remove/enable/accept/reject | -| `StylePackApi` | 风格包生命周期 | list/create/save/preview/activate/import/export | -| `LocalAsrApi` | 本地模型下载与运行时 | models、download、prepare、release、status | -| `SelectionApi` | 选区润色/选区语音 | capture、preview、confirm、cancel、revert | -| `QaApi` | QA 会话、录音和回答 | submit、sync、approve、cancel | -| `LessComputerApi` | Coding Agent 连续对话 | submit、cancel、dismiss、approve | -| `RemoteInputApi` | 远程输入服务器 | status、PIN、locale、local IP | -| `MarketplaceApi` | 市场与 GitHub OAuth | list/detail/install/upload/like/auth | -| `CodingAgentApi` | Coding Agent 检测、模型、风险、测试与审批 | detect、list models、risk、run/cancel、approve | -| `PlatformApi` | 能力和权限状态 | capabilities、microphone、accessibility、IME | -| `AuxiliaryApi` | 对既有文本/PCM 执行单轮共享处理 | repolish、retranscribe PCM、实际 ASR 归因、取消 | - -每个接口只返回 core DTO 和 `BackendError`,不接受 Tauri 类型。Tauri command 和 egui 调用层分别把本宿主输入转换为这些 use-case 参数。 - -#### Less Computer 接口约定 - -`OpenLessBackend::submit_less_computer(transcript)` 是普通文本入口,Core 从同一份 -preferences snapshot 解析 provider、可执行文件、model、permission mode、workdir、prompt -和护栏策略,并生成 Core-owned `SessionId`。需要把热键录音生命周期与 Agent 运行严格关联的宿主 -先调用 `begin_less_computer_capture(session)` 预留实例级 capture lease,再使用 -`submit_less_computer_with_session(session, transcript)`;其中 `session` 只用于取消和事件关联, -不允许宿主借此覆盖 Core 的 provider 或安全策略。宿主启动 recorder/ASR 失败、空转写或取消而 -未进入 Agent run 时,必须调用 `abort_less_computer_capture(session)`;该方法对已提升为 run -的 session 是幂等 no-op。 - -提交前宿主可以显示自己的窗口或录音反馈;`less_computer_active_session()` 用于重连/诊断, -`less_computer_capture_cancelled(session)` 在宿主释放 capture lease 前报告取消。提交后只订阅 -`BackendEventKind::LessComputerEvent`: - -| 事件 | 语义 | UI 建议(egui 团队实现) | -| --- | --- | --- | -| `User { text, fresh }` | Core 已接受一轮输入;`fresh=true` 表示 dismiss 后的新会话 | 追加用户气泡并清理旧会话状态 | -| `Started` | provider 进程已开始 | 显示运行中 | -| `Delta { text }` | Agent 增量文本 | 追加到当前助手消息 | -| `Tool { name }` | provider 报告工具调用 | 显示工具活动,不执行工具 | -| `Compaction` | provider 压缩上下文 | 显示“整理上下文”状态(可选) | -| `Approval { token, command, reason }` | Core 等待一次高风险命令决定 | 仅展示 command/reason;通过 `CodingAgentApi::approve(token, bool)` 回传 | -| `Completed { text, cost_usd }` | 唯一成功终态 | 固化助手消息和费用(若有) | -| `Error { message }` | 唯一失败终态 | 显示可读错误和重试入口 | -| `Cancelled` | 唯一取消终态 | 清理运行态但保留已显示历史 | - -事件中的 `seq` 由 backend 实例统一分配;UI 重连时先建立订阅,再调用 replay/snapshot,按 -`seq` 去重。UI 不应自己维护 approval token、conversation flag、continuation history 或 -provider 进程状态;这些均由 Core/Runtime Adapter 持有。未注入 `LessComputerRuntimeAdapter` -时,submit 必须返回 `BackendErrorCode::Unsupported`,不能伪造 `Completed`。 - -### 5.3 快照与事件 - -事件是跨两个宿主的真实接缝。核心事件必须表达“发生了什么”,而不是“哪个窗口要怎么显示”。建议定义: - -```rust -pub struct BackendEvent { - pub sequence: u64, - pub session_id: Option, - pub kind: BackendEventKind, -} - -pub enum BackendEventKind { - BackendStarted, - BackendStopping, - DictationStateChanged(DictationStateSnapshot), - TranscriptDelta(TranscriptDelta), - PolishDelta(PolishDelta), - DictationCompleted(DictationResult), - SelectionStateChanged(SelectionSnapshot), - SelectionVoiceStateChanged(SelectionVoiceSnapshot), - InsertFallback(InsertFallbackPayload), - PreferencesChanged(PreferencesChange), - CredentialsChanged(CredentialsStatus), - HistoryChanged(HistoryChange), - VocabularyChanged(VocabularyChange), - StylePacksChanged(StylePackChange), - DownloadProgress(DownloadProgress), - PermissionChanged(PermissionSnapshot), - HotkeyStatusChanged(HotkeyStatus), - Notification(NotificationPayload), - CodingAgentTest(CodingAgentStreamEvent), - LessComputerEvent(LessComputerEvent), - LocalAsrPrepareProgress(LocalAsrPrepareProgress), - LocalAsrDownloadProgress(LocalAsrDownloadProgress), - LocalAsrEngineChanged(LocalAsrRuntimeStatus), - MicrophoneDevicesChanged, - QaLevel(QaRecordingLevel), - QaState(QaStateEvent), - RemoteInputStatusChanged(RemoteInputRuntimeEvent), - RemoteInputFailed(RemoteInputErrorEvent), - VocabularySuggestionsChanged(Vec), -} -``` - -事件约束: - -- 每个 backend 实例的 `sequence` 单调递增;同一 session 的事件顺序可验证。 -- 事件携带 `SessionId` 的地方必须由宿主丢弃过期 session,防止晚到结果污染新会话。 -- 事件流是增量通知,不是唯一真相;收到丢失/滞后通知后,宿主重新读取 `snapshot()`。 -- `subscribe()` 应使用可检测滞后的广播/订阅机制;订阅者落后时返回显式 `Lagged`,不能静默继续使用旧状态。 -- 最终事件只发布一次;取消、失败和成功都必须有明确终态。 -- 核心不发布 `capsule:state`、`chat-panel:shown` 等 Tauri 窗口事件名。Tauri adapter 可把 `DictationStateChanged` 映射为现有事件名,egui adapter 直接更新自己的 view model。 - -机器基线已把 30 个旧 Tauri event 全部分类为“core 语义事件映射”“纯 Tauri 窗口事件”或 -“删除前需版本迁移”。以下 12 个原 `migrationRequired` 事件现已全部获得 typed core event, -并从业务模块的直接 emit 迁移到集中桥接: - -1. `coding-agent:test` -2. `foundry-local-asr-prepare-progress` -3. `less-computer:event` -4. `local-asr:engine-changed` -5. `microphone:devices-changed` -6. `qa:level` -7. `qa:state` -8. `remote-input:error` -9. `remote-input:running` -10. `sherpa-onnx-asr-download-progress` -11. `sherpa-onnx-asr-prepare-progress` -12. `vocab:suggested` - -这 12 项已按固定步骤完成迁移:在 core 定义稳定 DTO 和 11 个 `BackendEventKind` variant -(Foundry/Sherpa prepare 共用一个语义事件);在 `tauri_events.rs` 集中映射为旧 React -payload;把领域实现的直接 emit 改为共享 `BackendEventPublisher`;纯窗口事件继续留在 -Tauri;baseline/contract、serde fixture、30/30 分类完整性、Tauri mapping 和 -secret-surface tests 同步更新。后续事件仍必须遵守同一流程。特别是 remote PIN、token 和 -provider credential 不得进入 core event 或兼容 payload。 - -### 5.4 错误接口 - -现有很多 command 返回 `Result<_, String>`。核心应改用稳定错误类型,Tauri adapter 再把它序列化为兼容 JSON: - -```rust -pub struct BackendError { - pub code: BackendErrorCode, - pub message: String, - pub retryable: bool, - pub details: Option, -} - -pub enum BackendErrorCode { - InvalidArgument, - InvalidState, - Busy, - Cancelled, - PermissionDenied, - Unsupported, - Provider, - Persistence, - Platform, - Internal, -} -``` - -约束: - -- `code` 是机器可判断字段,`message` 是用户可读信息,不能让 UI 解析英文字符串。 -- 凭据、token、PIN、Authorization header 和完整 provider 请求不能出现在 `message`、`details` 或日志中。 -- 超时后的异步插入/提交必须表达“结果未知”状态,不能仅凭 timeout 自动重试导致重复插入。 -- `Cancelled`、`Unsupported`、`PermissionDenied` 不能被 Tauri wrapper 统一转换为普通字符串失败。 - -### 5.5 BackendConfig 与依赖注入 - -`BackendConfig` 只包含配置值和路径,不包含窗口对象: - -```rust -pub struct BackendConfig { - pub data_dir: PathBuf, - pub cache_dir: PathBuf, - pub resource_dir: Option, - pub home_dir: Option, - pub platform: PlatformCapabilities, - pub locale: String, -} -``` - -`BackendDependencies` 通过 Interface 注入真正会变化或需要替身测试的依赖: - -| Interface | 生产 Adapter | 测试 Adapter | -| --- | --- | --- | -| `TaskSpawner` | Tauri host / Linux Tokio runtime | 单线程 deterministic executor | -| `Clock` | 系统时钟 | 固定时钟 | -| `CredentialStore` | macOS/Windows/Linux/Android 安全存储 | 内存 vault | -| `TextInserter` | AX / TSF / fcitx5 / clipboard | 记录调用的 fake inserter | -| `AudioRecorder` | cpal + 平台设备 | PCM fixture recorder | -| `HotkeyController` | macOS/Windows/global-hotkey/fcitx5 | 可控 fake hotkey | -| `HostActions` | Tauri 窗口/托盘/系统操作 | 记录 action 的 headless host | -| `ResourceResolver` | Tauri resource dir / Linux 安装目录 | 临时目录 | - -只在存在两个真实 Adapter 或一个真实 Adapter 加一个测试替身时建立 Interface;纯 Rust provider 和业务函数不为了形式而包一层。 - -### 5.6 HostActions - -核心需要请求宿主执行窗口或系统动作时,使用语义 action,不接受窗口 label: - -```rust -pub enum HostAction { - ShowMain, - FocusMain, - ShowDictationFeedback, - HideDictationFeedback, - ShowSelectionPreview, - ShowQa, - ShowLessComputer, - OpenExternalUrl(Url), - OpenSystemSettings(SystemSettingsPage), - RequestRestart, - Notify(NotificationPayload), -} -``` - -Tauri adapter 把这些 action 翻译为 WebView/window 操作,Linux adapter 翻译为 egui 状态、窗口操作或 desktop integration。核心不决定实现方式,也不假设存在多个 WebView。 - -## 6. 状态、并发和生命周期契约 - -### 6.1 状态所有权 - -核心拥有: - -- 当前 dictation / QA / selection session -- session phase、取消状态、插入结果和终态 -- provider 选择、凭据状态、模型准备状态 -- preferences、history、vocabulary、style pack 数据 -- 下载进度、热键能力和权限快照 - -宿主 UI 拥有: - -- 当前页面、tab、滚动位置和焦点 -- 对话框展开与输入草稿 -- 动画、过渡和 egui immediate-mode 临时状态 -- 窗口大小、位置、装饰和前端布局 - -### 6.2 线程规则 - -- `update()`、Tauri command 入口和 egui frame 都不能执行阻塞的网络、磁盘、录音或模型加载。 -- core 不调用 `tauri::async_runtime::spawn`;通过 `TaskSpawner` 或 core 统一的 Tokio runtime 执行后台任务。 -- egui host 在 frame 中只 drain 非阻塞事件;收到事件后调用 `request_repaint()`,不能在 frame 内 `.block_on()`。 -- Tauri event bridge 单独运行订阅任务,退出时先停止订阅,再关闭 backend。 -- 所有可取消操作必须绑定 `CancellationToken` 或等价 session token;取消后仍可能晚到的结果必须被 session guard 丢弃。 - -### 6.3 初始化与关闭顺序 - -启动: - -1. 宿主解析数据目录、资源目录、locale 和平台能力。 -2. 宿主创建 platform adapters 和 `BackendDependencies`。 -3. 构造 `OpenLessBackend`。 -4. 注册事件订阅和 host action sink。 -5. 调用 `backend.start()`,读取 `StartupSnapshot`。 -6. 成功后启动全局热键、托盘 watcher、下载 watcher 等宿主任务。 -7. UI 显示;未满足的能力通过 snapshot 明确降级。 - -关闭: - -1. 禁止新 command/session。 -2. 取消正在运行的会话和下载。 -3. 停止热键、录音、设备 watcher 和远程 server。 -4. flush 必须持久化的数据。 -5. 停止 event bridge / runtime。 -6. 调用 `backend.shutdown()` 并退出宿主。 - -## 7. 模块抽取与归属矩阵 - -这是迁移审计的初始归类;混合文件必须按函数拆分,不以文件名作为最终架构。 - -### 7.1 进入 `openless-core` - -| 当前位置 | 迁移动作 | -| --- | --- | -| `src-tauri/src/types.rs` | 把领域 DTO、快照、枚举、serde 名称和校验迁入 core;平台专属类型另放 adapter module | -| `coordinator_state.rs` | 直接迁移 session phase、generation、终态和状态转换测试 | -| `correction.rs` / `edit_plan.rs` | 迁入 core 纯业务模块 | -| `endpoint_security.rs` | URL 格式校验和 DNS resolution/pinning 逻辑迁入 core;宿主只提供网络 executor | -| `selection_voice_intent.rs` | 意图分类、关键词和 JSON 解析迁入 core;UI 选择弹窗留在 host | -| `cli.rs` | CLI 参数解析和 `CliIntent` 作为纯输入类型迁入 core;进程激活/窗口操作留在 host | -| `persistence/` | 保留 store 逻辑;凭据底层改为 `CredentialStore` Interface;路径解析改为 `BackendConfig` | -| `asr/` | provider 协议、请求、响应、重试和 ASR 业务流程迁入 core;平台模型 runtime 由 adapter 注入 | -| `asr/local/` | 模型 catalog、选择和生命周期接口进入 core;Qwen3 MLX、Foundry、Sherpa、Whisper 等 native runtime 按 target 放入 platform adapter,避免 macOS vendored path 被所有 target 解析 | -| `polish/` / `llm_gemini.rs` / `net.rs` / `omni.rs` | 迁入 core;不得引用 Tauri window/event | -| `coordinator/{dictation,polish_flow,asr_wiring,resources,silence_auto_stop}.rs` | 保留业务流程,改用 event bus、宿主 Interfaces 和 session guard | -| `coding_agent/` | provider/model/权限/预算/路径/风险/版本/MCP 等跨宿主规则进入 core;进程、Git、临时配置、宿主审批和事件桥接留在 Adapter | -| 现有类型测试与状态机测试 | 测试随模块迁移,测试入口改为 core interface | - -### 7.2 进入平台 adapter,但仍可被 core 注入 - -| 当前位置 | 目标职责 | -| --- | --- | -| `recorder.rs` | `AudioRecorder` 的桌面实现;设备枚举和 level monitor 的 host bridge 单独拆出 | -| `audio_mute.rs` | `AudioMuteGuard` 作为平台音频 Adapter;core 只依赖可选的 mute Interface | -| `hotkey.rs` / `global_hotkey_runtime.rs` | `HotkeyController` 的平台实现 | -| `qa_hotkey.rs` | QA 热键监听 adapter;QA session 状态和 use-case 逻辑进入 core | -| `combo_hotkey.rs` / `side_aware_combo.rs` / `shortcut_binding.rs` | 按平台保留实现,公共 binding 校验迁入 core | -| `insertion.rs` / `unicode_keystroke.rs` | `TextInserter` 实现;业务层只看 `InsertResult` | -| `linux_fcitx.rs` | DBus commit/hotkey 能力可作为 Linux adapter;资源目录检查和插件复制不能依赖 AppHandle | -| `windows_ime_ipc.rs` / `windows_ime_protocol.rs` / `windows_ime_session.rs` | Windows TSF IPC、协议和 session adapter;公共状态类型留在 core | -| `windows_ime_restore.rs` / `windows_ime_profile.rs` | Windows IME 恢复、键盘列表和注册表 adapter;公共设置 patch 留在 core | -| `host_document/` | macOS host document adapter;core 只接收 optional context | -| `permissions.rs` | capability/permission Interface;系统设置打开动作属于 host | -| `device_watch.rs` | OS 设备 watcher;通过 `BackendEvent` 或 host callback 回报 | -| `remote_server/` | 协议和业务可进 core;监听生命周期、端口、资源路径由 host 注入 | -| `external_url.rs` | URL scheme 校验可进 core;实际打开浏览器/Android Intent 必须由 host 实现 | - -### 7.3 只进入 Tauri adapter - -| 当前位置/内容 | 处理 | -| --- | --- | -| `lib.rs` 的 `tauri::Builder`、plugin 初始化、`generate_handler!` | 移入 Tauri host | -| `commands/` 中 `#[tauri::command]` 函数 | 保留为薄转换层,只做参数解析、core 调用和错误序列化 | -| `AppHandle` / `Window` / `WebviewWindow` 操作 | 移入 `tauri_events.rs` 和 `host/` | -| tray menu、窗口创建/显示/隐藏/定位、vibrancy/Mica、single-instance | 移入 Tauri host | -| Tauri updater/dialog/shell/autostart/fs plugin | 只在对应 host 使用 | -| `tauri.conf.json`、capabilities、Tauri resources | 只服务 Tauri desktop/mobile | -| `mobile_runtime.rs` / `android/` | 继续作为 Android Tauri host/JNI/overlay/IME adapter,不进入 Linux package | - -### 7.4 Android 处理 - -- 保留现有 `#[cfg(mobile)]` 分支和 Android Tauri host。 -- 将 Android 业务可复用部分依赖 core;JNI、overlay、IME、Keystore 留在 Android adapter。 -- 不让 Linux egui 为 Android 的 unavailable 能力增加条件分支。 -- `PlatformCapabilities` 由 core 定义结构,宿主提供真实值;Android 继续返回当前约定。 -- `build_target.rs` 只保留为构建目标/`cfg` 的测试辅助;不得成为运行时模块或 core 的宿主依赖。 - -## 8. 详细实施步骤 - -### 8.1 执行顺序、责任和阻塞关系 - -里程碑按下表推进。除 M7 的 Interface 移交外,egui 团队不阻塞共享后端迁移;他们可以在 M7 -交付后基于 fake/headless Adapter 并行开发 UI。任何阶段都不能以复制业务规则到宿主来绕过前置项。 - -| 里程碑 | 主责 | 依赖 | 退出后解锁 | -| --- | --- | --- | --- | -| M0 决策与基线 | 架构/后端负责人 | 无 | 固定平台范围、兼容基线和版本规则 | -| M1 package 骨架 | 构建负责人 + core 负责人 | M0 | core/Linux 可独立解析和编译 | -| M2 类型与错误 | core 负责人 | M1 | 两个 Adapter 可共享 DTO、错误和能力语义 | -| M3 依赖注入与生命周期 | core 负责人 + 平台负责人 | M2 | 可用 fake Adapter 做 headless 测试 | -| M4 Coordinator 与事件 | core 负责人 | M3 | 两个宿主可消费同一状态机和语义事件 | -| M5 领域迁移 | 各领域后端负责人 | M2–M4 | 业务规则只有 core 一份实现 | -| M6 Tauri Adapter | Tauri 负责人 | 对应 M5 领域逐项完成 | React IPC 保持兼容且不再承载业务规则 | -| M7 egui Interface 移交 | core/Linux host 负责人 | M2–M4 的稳定 Interface;允许以 Unsupported 标记未接线能力 | egui 团队可独立开发 view model/UI | -| M8 Linux 非 UI Adapter | Linux host 负责人 | M3、M4、对应 M5 领域 | Linux 宿主可调用真实共享主链路 | -| M9 测试与质量门禁 | 测试/构建负责人 | M4–M8 逐项接入 | 合并与发布候选具备可重复证据 | -| M10 打包与发布 | 发布负责人 | M8、M9;真实 egui 入口由 UI 团队交付 | Linux 原生产物可独立发布 | - -交付发生变化时,主责方必须同步更新 Interface contract、fixture、迁移说明和对应 Adapter -contract tests;仅更新实现代码不能视为完成。 - -### 8.2 当前工作树的剩余关键路径 - -以下顺序是从当前实现推进到最终验收的唯一关键路径。每一步完成后先过本步门禁,再进入 -下一步;不得通过在某个宿主复制业务判断来绕过未完成的 core 工作。 - -1. **已完成:冻结每会话配置快照。** core 已定义 `DictationContext`,在 - `start_dictation()` 时一次性固定麦克风、ASR/LLM/Omni channel、模型、语言、翻译目标、 - ASR prompt、风格包/润色 prompt、流式插入和 fallback 策略。会话开始后修改设置只能影响 - 下一会话,不能让正在运行的 provider 读取到一半新一半旧的偏好。 -2. **已完成:让 Pipeline 消费会话快照。** `DictationEngine`、`AudioRecorder`、 - `TranscriptionEngine` 和 `TextPolisher` 的最小参数,使 recorder 选择设备、provider 选择、 - ASR prompt 与 polish prompt 都来自同一个快照;补设置并发修改、取消和迟到结果测试。 -3. **已完成:修复 provider router 的会话占用语义。** `DictationEngineRouter::start()` 使用 - `HashMap::entry` 原子占位;回归测试证明第二次 start 返回 `Busy` 后,原 session 仍由 - 第一次选中的 Adapter 完成,不能被新 Adapter 接管。 -4. **已完成:共享 provider registry、生产 factory 与 Provider 管理面。** core 已提供按会话固定 Adapter 的 - `TranscriptionRouter`、`TextPolisherRouter` 和 `DictationEngineRouter`,并覆盖设置切换后 - 旧 session 不漂移、缺失 provider 显式 `Unsupported`、traditional/Omni 分流测试。云/实时 ASR、 - OpenAI-compatible/Gemini/Codex LLM、Omni、Auxiliary 和 QA provider Implementation 现由 core - 持有,包含 credential account、默认 endpoint/model、协议选择、取消、流式输出和 session - 占用语义。Tauri 注册共享实现并追加 native/local ASR;Linux 通过 - `LinuxBackendBuilder::from_shared_providers(config)` 注册同一批共享实现,不读取 Tauri Adapter。 - channel ID、协议类型和模型在会话开始时分别冻结;Omni 的 API key、endpoint、model、extra - headers 和 temperature 全部按冻结的 provider ID 读取,不依赖运行中的 active provider;重复 - session 不会覆盖原 cancellation route。`ProviderService::validate/list_models` 已迁入 Core,按 - channel-scoped credential 解析 provider/type/model;Tauri command 只做参数和旧错误转换,Linux - `from_shared_providers` 注入同一 service。静态清单、OpenAI/Gemini 响应解析、Omni channel 拒绝、 - 错误脱敏和 Linux 非 `Unsupported` factory contract 已覆盖。 -5. **已完成:迁移完整润色 prompt 语义。** 旧 `polish/prompt_compose.rs` 的 XML envelope、输入净化、 - prompt injection 防护、前台应用、光标上下文、历史 turns、翻译规则和 user prompt envelope - 已移入 core,并由 3 项固定 prompt contract 和 core/Tauri provider tests 覆盖。 -6. **已完成:为 Tauri 构造真实 core Pipeline。** 录音、凭据、host action,以及按 session 执行 - `prepare/insert/cancel` 的 Windows TSF/SendInput/Paste、Android strategy 与 macOS 插入 Adapter - 已接入;TSF 派发后的超时被保留为 outcome-unknown,禁止触发可能重复落字的 fallback。 - setup 构造唯一的 `Arc`,Tauri 与 compatibility Coordinator 共享同一组 - repository;生产 provider Adapter 也已接入该 Pipeline。 -7. **进行中:逐入口切换 Tauri 听写主链。** React dictation start/stop/cancel command、CLI - toggle/cancel、Android JNI、remote WebSocket,以及桌面普通听写热键的 - Pressed/Released/Combined 和 Esc 取消已进入同一个 facade。Android 通过 - `DictationStopOptions` 保留“stop 时决定 translation”的既有语义,同时只更新冻结快照中允许 - 变化的翻译开关;remote 使用 16 kHz、单声道、signed Int16LE external PCM seam,session ID - 严格关联,stop/cancel 后拒绝迟到帧。桌面宿主继续拥有 QA panel 优先分流、shortcut - recording、modifier-only combo arbitration、debounce/cooldown 和物理 listener/window - fallback;这些宿主机制不能重新拥有听写 session 状态。静音自动停止和 Starting pending stop - 仍调用 Coordinator 旧 `end_session`,必须与 Less Computer 语音生命周期一起迁移后,才能 - 把桌面热键主链标为全链路完成。托盘审计未发现听写 start/stop 入口。 - TLS/PIN/WebSocket 与 Android overlay/IME 继续留在 Tauri Adapter。其余 Coordinator 入口必须 - 按行为 contract 逐项切换,不能通过整体代理改变产品行为。 -8. **已完成:集中完整 legacy event mapping。** 机器基线中的 30 个 legacy event 已逐项标注为 - “core 语义事件映射”或“纯 Tauri 窗口事件”,并通过完整性检查(30/30、无重复/遗漏);原 - `migrationRequired` 分类已清空。12 个旧事件由 11 个 typed core event 覆盖,统一在 - `tauri_events.rs` 映射,业务模块不再直接发射这些事件。后续禁止新增业务直接 emit 点。 -9. **进行中:迁移复杂领域 Implementation。** Coding Agent 的跨宿主规则和 DTO 已进入 core, - Tauri command 已收敛为授权/兼容转换层,真实进程、Git、临时文件和事件转发由 - `TauriCodingAgentApi` 负责。Local ASR 的 catalog、设置事务、运行时生命周期 Interface、 - Core Implementation、engine-changed 事件语义与 Generic/Foundry/Sherpa Tauri command 薄包装 - 已经落地;`TauriLocalAsrRuntimeAdapter` 直接使用共享 preferences repository 与 Qwen/Whisper - cache,不再通过 `AppHandle` 回取 Coordinator;完整本地门禁已通过,剩余工作是各原生 runtime - 证据。Marketplace/GitHub OAuth 的 HTTP、 - 认证、归档、安装、upload、device-flow 状态机已经进入 core,Tauri command 已只保留参数、旧 - wire/error 转换与最终文件写入;该领域的 core contract 17 项和严格 clippy 已通过。Selection - Core 的 17 项 contract 与 Selection Voice 的 13 项 contract 已完成,生产构造已注入新的 Tauri - runtime,旧 Coordinator wrapper、正式热键/command 与安全 revert 路径已经收口;QA Core、 - `TauriQaRuntimeAdapter` 生产接线和 Remote Input Core 也已建立;Linux 生产 factory 已自动注入 - Core `MarketplaceApi`,`LinuxHost::download_marketplace_archive` 只把 Core 校验后的归档写入用户 - 选择的绝对路径,使用 create-new 语义拒绝覆盖并在写入失败时清理不完整文件;QA/Remote lagged resync、 - Remote secret wire、Remote WebSocket 单 stream/restart stale-lease 和 Less Computer - listener-first replay/pending/dedup/truncation contract 已补齐;历史重润色、手工重转写和静默 - 重试已统一进入 `AuxiliaryApi`,repolish 只冻结 LLM/Omni,retranscription 只冻结 ASR,并由 - Transcription Adapter 报告默认值解析后的实际 provider/model。云 ASR/LLM/Omni/Auxiliary/QA - 协议构造、凭据路由和取消已统一进入 core;Selection Voice 的 correction、instruction polish、 - 自动 intent model/fallback、delivery decision、EditPlan、translation 和 QA preview revision 也已 - 进入 Core 高层 use-case,Tauri 只保留录音/窗口/热键/opaque insertion target 与 apply outcome。 - provider 验证/模型列表已由 Core `ProviderService` 统一,Tauri command module 的协议请求副本 - 已删除并由 source contract 守护。剩余重点是旧 Coordinator 其他宿主耦合审计与原生平台证明。每个 Tauri - command 只做参数/DTO/错误转换,Linux 未提供的平台能力由真实 Adapter 或稳定 `Unsupported` - 表达。逐项步骤见 8.3 节。 -10. **进行中:收窄旧 Coordinator 的兼容宿主职责。** 已完成的宿主隔离包括:`Inner.app` 已替换为 - 显式 `TauriCoordinatorHost`;`bind_app(AppHandle)` 已删除;`Inner` 与 `capsule_focus` 已恢复 - module 私有;Coordinator/capsule 子模块中的 `AppHandle`、`WebviewWindow`、直接 `emit*` 和 - `tauri::async_runtime::{spawn,spawn_blocking,block_on}` 均已清零;Sherpa/remote 等业务事件经 - typed Core event 与 `tauri_events.rs` 集中映射。capsule layout 去重、cursor passthrough、style、 - fallback card、presentation generation、deferred payload 以及 show/hide/no-activate 和 - macOS/Windows 原生窗口行为均由 Host 持有;`TauriCapsuleWindow::apply_capsule_payload` 只接收 - payload、显示决策、style 和 Space reassert 窄值,不再回调整个 `Inner`。此外, - `core_adapters.rs` 的 `managed_coordinator` 反向查询已删除;hotkey status 与 QA 可见性由构造层 - 创建的窄共享状态分别注入 Coordinator/Adapter,Local ASR 共享同一 repository/native cache。 - Selection Voice 本批已删除 Tauri 中的 correction、prompt、自动分类、EditPlan、translation 和 - output-mode 分支,QA Adapter 直接调用 Core `edit_preview` 后只绑定平台 target;相应 source - contract 会阻止这些业务 token 回流。 - Less Computer 的生产热键按下现在先调用 Core capture lease,再由 - `coordinator/hotkey_loops.rs` 进入 `begin_session_as_with_session_id`;松开、静音自动停止和 - Starting pending stop 仍调用兼容层 `end_session`,但 Core 通过同一 session id 接管提交、取消 - 和 Agent 终态。Coordinator 中剩余的 `state` 字段只表达宿主录音/热键生命周期,不能被 Linux - egui 读取或当作业务 API。下一步按生产调用图逐项分类剩余 Coordinator 方法:纯 Host 生命周期、 - 授权、wire 转换和 socket/native runtime 留在 Tauri Adapter;跨宿主业务状态、provider 协议和 - 设置/热键事务迁入 core;仅由旧测试引用且无生产消费者的 wrapper 删除。settings/hotkey 的“兼容化解/校验 → 生成显式 effect plan → 平台 - prepare/commit → 单次持久化/事件 → receipt 逆序补偿”事务已经迁入 core;Tauri/Linux Adapter - 不再回读偏好文档来猜 listener 目标,style-pack 删除也直接消费 Core 专用 outcome。完成每批迁移后重跑 command/event baseline、 - source contract、Tauri 全量测试和残余引用检查。窗口、托盘、updater、dialog、shell、autostart、 - single-instance、Android JNI/overlay/IME 与 native ASR runtime 始终留在宿主,不为形式共享塞入 core。 - 还必须完成 runtime seam 审计:core provider transport 可以使用宿主已经启动的 Tokio runtime, - 但生产路径不得在无 runtime 时自行 `Runtime::new()`;应改为注入的 runtime/task spawner 或 - 明确要求由异步宿主调用,并增加 headless no-private-runtime contract。`rg` 对 - `tokio::spawn`、`Handle::current`、`Runtime::new` 的结果要逐项标注为“宿主 runtime 内运行”或 - “测试专用”,未标注项不能进入 M9 完成状态。 -11. **已完成 Linux 非 UI runtime 接线;真实原生确认待 runner。** `SelectionPolishEvent` 已调用共享 - `SelectionApi`;空闲态先收到的 `TranslationModifierEvent` 已作为下一次 dictation press 的 - `DictationStartOptions`,不会修改已经冻结的活动 session;`LinuxNativeRuntime` 已统一拥有 - primary broker、hotkey listener、错误 drain 和 shutdown/join。下一步在真实 fcitx5 上记录 - translation 与 dictation 信号顺序:若 translation 可能后到,必须先冻结关联规则并补事件 - 时间线测试,不能靠修改活动 session 猜测用户意图。 -12. **已完成(Interface):冻结 1.x egui 接口交付。** contract、公开 re-export、完整 headless example、 - 能力 fixtures、view-model 映射和 `AuxiliaryApi` 单轮处理/取消/归因契约已更新;egui 组可以 - 只依赖 facade/DTO/event/fixture 并行开发 UI,不读取 core 私有模块,也不等待 M10 正式打包。 - 设置/快捷键 DTO、`LinuxHost::save_settings` reconcile 入口、`update_settings_strict` 严格拒绝入口、 - snapshot revision 和显式 Linux effect target 已进入公共 contract;egui 组不能直接调用底层 - `set_preferences*` 绕过事务。Selection/Selection Voice 的 preview、confirm、cancel、stale、 - outcome-unknown 与 Linux preview/revert `Unsupported` 已由 fixture、headless 示例和第 4 项 - host contract 覆盖;当前公共面/受影响门禁已经重跑通过。 -13. **已完成:消除测试旁路。** 已删除 `backend-tests/tests/backend_rust.rs` 的 `#[path]` - include 与 Tauri stub;`backend-tests` 现在只直接依赖公开 `openless-core` 并运行一项 core - contract。原 118 项测试由 Tauri crate 自身的 - `cargo test --locked --manifest-path "src-tauri/Cargo.toml" --lib` 承担,不再复制源码;capsule - Host 收口前的历史 Windows 基线为 1080 passed、7 ignored、0 failed;该数字已由下一步的最新 - 工作树结果取代。 -14. **已完成当前 Windows 本地重验;原生 runner 仍由下一步单独验收。** capsule Host 收口之前,Windows 本地的 - frontend build/58 项 tests、workspace fmt、Core 261 项 unit 与 75 项领域 contract、Linux 22 项 - crate 与 2 项 host contract、公开 Core compatibility 1 项、Core/Linux 严格 clippy、Tauri - `cargo check --lib`、Tauri 1080 passed/7 ignored 的 `cargo test --lib`、command/event baseline、 - 依赖方向、secret surface、测试隔离和公共面门禁均通过。最新 capsule 增量已通过 - `shared-backend-wire-contract`、macOS capsule Spaces、Windows UI config 三个源码契约、Tauri - `cargo check --locked --lib` 和 15 项 `capsule_` 定向测试;当时 check 报告 280 项既有/迁移期 warning, - warning 数不作为成功证明。共享 provider 抽取后的当时工作树已通过 Core 567 unit + 75 - integration contract、Linux 25 crate + 3 host contract、Core/Linux 严格 clippy、Tauri - `cargo check --locked --lib`(compiler summary 276 项 warning)和 778 passed/0 failed/7 ignored; - frontend build/58 tests、公开 Core compatibility 1、fmt、196/30/29 基线、依赖方向、秘密面、 - 测试隔离、Linux 公共面和 diff hygiene 也曾在同一工作树通过。此后删除了 Tauri Adapter 中 - 永久禁用的 legacy 云 ASR/润色/Omni provider 副本,并加强 source contract;因此 source - contract、workspace fmt、Tauri check/test 必须重新运行。该轮后续工作树已通过 frontend build/58 - tests、Core 596 unit + 79 integration contract、Linux 30 crate + 4 host contract,另有 3 个显式 - ignored native contract、公开 Core compatibility 1、Core/Linux 严格 clippy、Tauri check 与 730 - passed/0 failed/7 ignored 的 macOS Tauri suite;最终证据以 fork CI run 33408317390 为准。fmt、 - 196/30/29 基线、依赖方向、秘密面、测试隔离、Linux 公共面、source contract、headless example - 和 tracked diff hygiene 也在该 CI run 通过。该本地证据不替代第 15 步的原生 runner 结果。 -15. **取得原生 CI 证据。** Ubuntu 验证 dbus/keyring/cpal/fcitx5、Linux host 和无 WebKitGTK - 依赖;macOS/Windows 验证 Tauri adapter;Android 验证 mobile target/JNI/Gradle。任何缺失的 - runner 证据保持未完成,不能由 Windows cross-target check 推断。 -16. **验证 Linux 打包。** 在 Ubuntu runner 构建 fcitx5 plugin 与 release binary,生成 deb、 - rpm、AppImage,检查 desktop/AppStream metadata、ELF `ldd`、包内路径、AppImage 解包内容、 - 单实例协议、资源解析、SHA-256、minisign 和独立 updater manifest。 -17. **解除发布门禁。** 只有 egui 团队替换 `main.rs` UI stub、UI 验收完成、签名 secret 可用、 - M9/M10 原生证据全部通过后,才允许 Linux workflow 响应 release tag;在此之前只允许 - `workflow_dispatch`/`workflow_call` 生成验证产物。 -18. **最终文档与删除审计。** 更新 README/RELEASING/contract/迁移说明和 M0 baseline;用 - `rg` 确认 core 无 Tauri/egui、Linux 无 Tauri/WebKitGTK、Tauri 业务模块无遗留直接 emit, - 最后逐项勾选第 12 节,不用“整体看起来可用”代替逐项证据。 - -### 8.3 剩余复杂领域的逐项执行清单 - -本节是 8.2 第 9–12 步的可执行展开。每个领域都遵循同一顺序:先冻结 Interface 和 observable -contract,再把业务 Implementation 放进 core,随后实现平台 Adapter,最后切薄 Tauri command。 -不能先让 command 代理旧 Coordinator,再把代理层称为共享实现。 - -#### 8.3.1 设置与热键事务收口 - -设置和快捷键不是单纯的 JSON 持久化:一次保存可能同时改变 legacy 字段、快捷键冲突关系、 -原生 listener、活动 ASR provider 的安全存储映射和 Windows 键盘列表。业务规则必须归 core, -平台调用必须归 Adapter,而“全部成功或按既定策略恢复一致状态”的事务语义也必须只有一份。 - -**当前已完成** - -1. `openless-core::shortcut_types` 已拥有快捷键字符/修饰键语法、左右修饰键限制、物理重叠判定、 - legacy trigger 转换、dictation legacy 字段同步,以及 dictation/translation/QA/style/open-app/ - Selection/Coding Agent/style-pack 之间的冲突规则。 -2. Core 已提供 `SettingsCollisionPolicy`、`SettingsUpdateOptions`、`expected_preferences_revision`、 - strict/reconcile、preserve-style、legacy 同步、typed effect plan、typed receipt/failure/outcome 和 - 单写入 gate。stale revision 在运行平台副作用前稳定返回可重试 `Busy`。 -3. Tauri `shortcut_binding` 只保留 `ShortcutBinding -> global_hotkey::HotKey` 的原生转换;mobile - stub 复用 core 语义校验,但原生解析继续显式返回 mobile unavailable。 -4. `OpenLessBackend::update_settings` 已成为 core-owned transaction use-case:先 prepare/commit - 平台 effect,再只持久化一次、发布一次;prepare、commit 或 persistence 失败时按 receipt 逆序 - restore,补偿错误与主错误结构化返回,不会把部分成功伪装成成功。 -5. `commands/settings.rs::reconcile_hotkey_collisions` 的“核心 dictation 优先、非核心键按优先级 - 恢复旧值或停用、translation 必须回退默认值、style-pack hotkey 最低优先级”的产品规则迁入 - core。整表 settings 保存可以按既有 #904 兼容策略自动化解;单项快捷键命令仍应对冲突直接 - 拒绝,两个入口保持不同的既有产品语义。 -6. 已定义最小 `HotkeyRuntime` Interface。输入是 core 计算出的完整目标 binding set/diff,输出是可供 - 补偿的 typed receipt;Adapter 不得反向读取已保存 preferences,也不得接收 `Coordinator`、 - `AppHandle` 或窗口 label。Tauri 实现注册 global-hotkey/combo/side-aware listener,Linux 实现 - fcitx5/DBus listener,测试实现记录 apply/restore 顺序。 -7. 设置事务严格执行以下顺序: - - 1. 读取并规范化 `previous`,生成经校验或兼容化解后的 `next`; - 2. 计算 hotkey、活动 ASR provider 和平台设置的 typed effect plan; - 3. 让对应 Adapter 以显式 `next` 执行可失败副作用,不允许 Adapter 从全局状态猜目标值; - 4. 全部副作用成功后只持久化一次 `next`,再发布一次变更事件; - 5. 任一步失败时按逆序补偿到 `previous`;补偿失败时返回包含主错误和补偿错误的结构化失败, - 按现有一致性策略决定恢复旧状态或 roll-forward,绝不能返回假成功或留下无报告的分叉; - 6. 整个事务使用单写入 gate,拒绝并发设置保存相互覆盖。 - - 活动 ASR provider 继续通过安全存储 Interface 同步;Windows 键盘列表只由 Windows Adapter - 执行。不要为了复用而把 Windows 注册表、global-hotkey 或 fcitx5 类型放进 core。 -8. 整表 `persist_settings`、dictation/translation/QA/switch-style/open-app/selection/Coding Agent/ - combo/style-pack 快捷键生产入口已切换到该 use-case。command 内旧 settings/hotkey 事务副本及 - previous/write/refresh/rollback helper 已删除;style-pack 删除通过 Core 专用 outcome 返回显式 - hotkey effect。 -9. Linux 公共面只暴露携带 snapshot revision 的 `LinuxHost::save_settings`(reconcile + - preserve-style)和 `update_settings_strict`;合法保存、冲突拒绝、active provider、stale revision、 - effect compensation 与稳定 `Unsupported` 已由 3 项 host contract 覆盖。 -10. Core 成功/失败矩阵已覆盖校验失败、持久化失败、runtime prepare/commit 失败、补偿失败、 - 并发/stale revision、一次持久化/一次事件和 preserve-style;Linux runtime 覆盖 receipt 逆序恢复。 - -11. `legacy-preferences-write` feature 与 `OpenLessBackend::set_preferences*` 公共兼容面已删除;四个 - 旧 writer 仅以 core crate 内 `#[cfg(test)] pub(crate)` helper 存在,Tauri/Linux 宿主无法启用或 - 调用该旁路。公共面门禁同时拒绝 feature 回归与重新出现 `pub fn` writer。 - -**仍需完成(原生 runner)** - -1. 在对应原生 runner 完成跨宿主失败矩阵:原生注册失败、ASR vault 同步失败、Windows - keyboard apply 失败、第一次补偿失败、listener restore 失败、并发写入。每项都断言最终偏好、 - 原生 listener、revision、事件数和错误码;mobile/不支持能力必须稳定返回 `Unsupported`。 -2. **本地已完成,原生 runner 待完成。** settings/hotkeys/QA、Linux public-surface/host contract、 - Tauri 全量 suite 和 frontend compatibility 已重跑;残余引用确认旧 write/refresh/rollback 编排 - 与 Tauri 事务 helper 均已删除,listener runtime 不再从 preferences 反推 target。 - Android/macOS/Ubuntu 的原生失败矩阵仍按上一项保持未完成。 - -**退出条件** - -- 快捷键语法、冲突、兼容化解和设置事务只有 core 一份 Implementation。 -- Tauri/Linux Adapter 只执行显式 effect plan,并能以 receipt 恢复;不读取或修改业务偏好。 -- React 旧 command/字段/错误兼容不变,egui 只依赖 validated Interface 即可获得同样规则。 -- 成功只产生一次持久化与一次语义事件;任何失败都有可测试的一致最终状态和明确错误。 - -#### 8.3.2 Local ASR 收口 - -**当前已完成** - -1. core 已定义 Generic、Foundry、Sherpa ONNX 的统一 runtime/target/mirror、catalog、settings、 - status、remote info、model card 和 model test DTO。 -2. `LocalAsrService` 已拥有设置校验、模型选择、镜像、语言、keep-loaded、存储迁移和运行时 - 生命周期的业务语义;原生模型引擎、下载和文件操作通过 `LocalAsrRuntimeAdapter` 注入。 -3. 三组 Tauri command 已只调用 `BackendServices.local_asr`,只保留旧参数和 React wire DTO - 转换;Generic 下载进度已改由 typed core event 进入集中事件桥接。 -4. 成功的 runtime mutation 会读取并发布最新 `LocalAsrRuntimeStatus`;失败操作不发布伪造的 - 成功状态。`set_active_model`、`set_foundry_runtime_source`、`set_keep_loaded_secs`、`prepare`、 - `release` 和 `delete_model` 已统一该语义。 -5. Sherpa core model 到旧 wire DTO 的转换已改为 `TryFrom`;未知 family/mode 返回错误,不再 - `panic!`。 -6. 定向证据:core `local_asr_contract` 6 项、Tauri `wire_contract_tests` 4 项通过;Local ASR - 接线后的 Tauri `cargo check --lib` 已通过。 -7. `TauriLocalAsrRuntimeAdapter` 已直接注入共享 preferences repository 与 - `TauriNativeAsrDependencies`;storage/status/release/preload/delete/test 不再回取 Coordinator, - 非 Windows Coordinator 与 Core native ASR 使用同一 Qwen/Whisper cache。 -8. 格式检查、完整 frontend 58 项、Tauri wire contract、Tauri `cargo check --locked --lib` 与 - `cargo test --locked --lib` 已通过;旧 command 名、camelCase/nullable/error 字段由源码契约和 - 完整 suite 共同守护。 - -**剩余步骤** - -1. 在对应原生 runner 验证 Generic、Foundry、Sherpa runtime 的准备、释放、取消和 engine-changed - 事件;Windows 上的 fake/contract 不能替代 macOS/Linux/Android 的原生能力证明。 - -**退出条件** - -- Local ASR command 不再直接读取 Coordinator、native runtime `State`、下载 manager 或偏好 store。 -- core contract、Tauri wire contract、typed event mapping 和完整本地门禁全部通过。 -- Linux 未提供某个 native runtime 时返回 `Unsupported`,不引用 Tauri runtime 作为替代。 - -#### 8.3.3 Marketplace 与 GitHub OAuth - -Marketplace 的 HTTP、OAuth、归档校验和安装事务属于跨宿主业务规则,现已形成深的 core -Module;文件选择器、目标路径授权和 Android `content://` 最终写入仍属于宿主能力。 - -**当前已完成** - -1. `MarketplaceUploadResult`、`MarketplaceLikeResult`、`MarketplaceMyPackItem` 和 tagged - `OAuthPollResult` 已进入 core,并有稳定 host-facing JSON fixture。 -2. `MarketplaceApi` 已表达结构化 upload/like/my-packs/OAuth poll 结果;`download_archive` 返回由 - core 下载并验证的 bytes,最终 filesystem 或 Android `content://` 写入归宿主。 -3. `MarketplaceConfig` 和构造接线已进入 `BackendDependencies`;公共请求使用匿名 client 且绝不 - 附加 bearer,匿名与认证 client 都拒绝 redirect,认证 redirect 不会访问目标地址。 -4. `list/detail/install/download_archive/upload/toggle_like/delete/my_likes/my_packs/auth_status` 以及 - device-flow 的 start/poll/cancel/logout 均由 Core Implementation 提供,不再由 Tauri command - 持有 HTTP 或认证状态机。 -5. 通过注入的 `CredentialStore` 读写 GitHub token;401 会先设置 backend 实例内 tombstone,再 - 尝试持久删除。即使删除失败,认证状态也立即变为 signed-out,后续请求不会再次发送旧 token。 -6. core 同时检查 declared `Content-Length` 与 streamed bytes 上限,下载后执行 ZIP 校验;实例级 - `try_lock` 保证并发 install 在第二次请求出网前返回 `Busy`。 -7. 安装使用 `StylePackStore::import_from_zip_bytes_with_origin` 原子提交 pack/origin,成功后 revision - 只增加一次并发布一次 `StylePacksChanged`;失败不留下 pack、revision 或成功事件。 -8. upload 直接复用 core ZIP export 生成 multipart;首次上传成功后把 remote ID/login 写回本地 - origin,并沿用 style-pack revision/event 语义。 -9. device-flow registry 已收进 backend 实例,拥有 generation、start 竞态失效、cancel、expiry、 - poll interval、`slow_down`、in-flight cancellation guard 和单次 token consumption;token 保存前 - 会再次核对 lease。device code、access token、Authorization header 不进入 Debug、日志、event、 - error details 或普通 DTO。 -10. Tauri Marketplace/OAuth command 已只做参数转换、core 调用及旧 React wire/error 转换;归档 - 下载后的 filesystem 或 Android `content://` 写入仍留在宿主。旧 command 的全局 lock、HTTP - helper、OAuth registry 和 ZIP 业务逻辑已删除。 -11. 最新工作树已完整运行 `marketplace_contract`,17 项全部通过; - `cargo clippy --locked -p openless-core --all-targets -- -D warnings` 通过。 -12. Tauri Marketplace host sink 2 项与 GitHub OAuth wire 2 项通过;残余引用检查未发现 command - 中保留 HTTP、token vault、ZIP validation、全局 install lock 或 device-flow registry。 - -**剩余验证步骤** - -1. **已完成(Windows 本地)**:完整 Tauri `--lib`、frontend contract 和第 12 节可在本机执行的 - 全量门禁已经重跑;结果见第 12.4 节。该证据只证明当前 Windows 工作树,不能替代下列 Linux、 - Android 和 macOS 原生 Adapter 验证。 -2. **已完成(Linux Interface)**:生产 factory 通过 Secret Service credential Adapter 使用同一 - `MarketplaceApi`,`LinuxHost::download_marketplace_archive` 提供 filesystem archive sink;egui - 只接触 Interface/DTO,不接触 token、HTTP client 或 URI 解析。 -3. 在 Android/macOS/Ubuntu 原生 runner 验证各自 credential、文件授权和最终归档写入 Adapter; - Windows contract 不能替代这些平台证据。 - -**退出条件** - -- Marketplace 业务规则只有 core 一份 Implementation,宿主只处理平台授权、wire 转换和最终写入。 -- public/auth/401/archive/install/upload/OAuth/secret-surface contract 在同一最新工作树上全部通过。 -- React 字段、tagged union 与错误兼容测试通过;Linux 未接线的宿主能力明确返回 `Unsupported`。 - -#### 8.3.4 Selection polish 与 selection voice - -**当前已完成** - -1. `SelectionCapture`、`SelectionRuntimeAdapter`、`SelectionPhase`、`SelectionSnapshot` 和公开的 - `SelectionPolishOutputMode` 已成为 core Interface;窗口 label 与平台选区句柄没有进入 core。 -2. `SelectionService` 已拥有 preview、session-scoped confirm、direct apply、cancel、completed - replacement 单次 revert、重复 begin 的 `Busy`、generation guard、迟到 provider 结果丢弃和 - `Completed/Cancelled/Failed` 单次终态。 -3. Selection 与听写复用同一 provider resolution 和 `TextPolisherRouter`;每个 session 冻结 LLM - channel/provider type/model,以及 capture-time `front_app`,不读取 cursor context、dictation - history 或 ASR prompt。 -4. 成功 direct replacement 会写入 Selection history 并统计 vocabulary hits;失败会释放平台 - target;apply 的 `OutcomeUnknown` 会进入可见 snapshot 且绝不自动重试。 -5. `SelectionStateChanged` 已是 typed core event。当前工作树的 `selection_contract` 17 项整体通过, - 覆盖 preview/confirm、显示/隐藏事件顺序、shutdown、outcome-unknown、history、vocabulary、最终 - 纠正、Raw passthrough、防注入 instruction envelope、activity/timing attribution、provider/context - 冻结、单次安全 revert、cancel/Busy/迟到结果和 provider failure。 -6. Tauri 生产构造已注入 `TauriSelectionRuntime` 和共享 polisher;runtime 按 `SessionId` 保存 - `SelectionInsertionTarget`,区分 preview/direct apply,并把平台插入结果映射为 `InsertOutcome`。 -7. `TauriSelectionRuntime` Adapter contract 已覆盖 target 注册、preview 目标恢复、stale/cancel、重复 - capture 和单次安全 revert;切换窗口、session 过期、重复 revert 或 outcome-unknown 时不会向未知 - 前台窗口发送通用 Undo。旧 `SelectionCoordinatorBridge`、`ManagedSelectionCoordinator`、 - `TauriSelectionApi` 与重复 session 真相已删除,正式 Selection 热键和 preview commands 调用 Core。 -8. Selection Voice 的 intent、prompt、preview owner、confirm/cancel/revert、自动分类 fallback、 - stale-session guard、shutdown 和 typed lifecycle event 已进入 core;新增的 `process_transcript`、 - `prepare_edit` 与 `edit_preview` 高层 use-case 统一 transcript correction、instruction polish、自动 - intent model、输出模式、EditPlan/translation 和 QA preview revision。13 项 contract 覆盖模型 - prompt/输入、翻译 target、direct action、conversation action、首次 preview 与单步 revision;QA - 问答与编辑预览通过稳定 `conversation_id` 关联。 -9. `LinuxSelectionRuntime` 已通过 fcitx5 读取并在 commit 前重新校验 selection;变化或取消的 target - 返回 `Cancelled`,无法安全保留 preview/revert 的路径明确返回 `Unsupported`。 - -**完成状态与剩余原生验证** - -1. **已完成(Core/Tauri 业务边界)**:`selection_voice*` prompt snapshot、intent confirm、cancel、 - preview query/ticket/finish/revert commands 已直接调用 Core Selection Voice/QA Interface;原始 ASR - transcript 直接交给 `process_transcript`,编辑分支只消费 Core `SelectionVoiceEditAction`,QA 编辑 - 只调用 `edit_preview`。`SelectionVoiceHostState` 只保存物理热键仲裁、录音资源和 opaque insertion - target,不复制 selection text、instruction、intent、preview 或业务 phase。Tauri 中的 correction、 - instruction polish、自动意图 LLM、EditPlan、translation、preview answer 和 output-mode 判断均已 - 删除;Coordinator 只保留物理热键、QA panel 优先级、窗口创建/聚焦、录音和平台插入。 -2. **已完成(兼容契约)**:旧 React command 名、 - camelCase/nullable 字段、事件 payload、窗口来源授权和错误字符串的 compatibility contract。 - `SelectionSnapshot` 与 Selection Voice apply outcome 已有稳定 serde fixture;当前变更保持 - `BACKEND_CONTRACT_VERSION = "1.0.0"`,没有用版本升级掩盖 wire 破坏。 -3. **已完成(headless 移交)**:headless example 和 deterministic fixture 演示 preview → confirm、preview → cancel、 - stale session 被拒绝、apply outcome-unknown 不自动重试,以及 Linux preview/revert 稳定返回 - `Unsupported`;示例不创建窗口、不读取真实选区、不实现任何 egui 控件,并已实际运行通过。 -4. 在 Windows Tauri 上验证真实选区 capture/preview/revert/窗口切换,在 Ubuntu/fcitx5 上验证 - capture/commit/cancel/Unsupported 分支;原生证明完成前不能只凭 17+13 项 contract 宣布领域收口。 - -**退出条件**:Tauri Coordinator 不再拥有 selection session/preview 真相;Tauri 与 Linux 通过同一 -`SelectionApi` 得到一致状态,宿主仅实现选区读取、目标恢复、窗口和文本插入 seam;Core、Tauri -Adapter、React wire 与 Linux headless contract 在同一最新工作树全部通过。 - -#### 8.3.5 QA 与 Less Computer 会话 - -**当前已完成** - -1. `QaApi`、`QaRuntimeAdapter`、`QaProgressSink`、`QaInput`、`QaTurnRequest/Result` 和稳定 - `QaSnapshot` 已进入 core Interface;message log、recording/thinking/approval/completed/cancelled/ - failed phase、edit-instruction mode、pending approval token 和公开错误均由 `QaService` 表达。 -2. `QaService` 已实现文本/语音 turn、selection 防注入 envelope、recording level、answer delta、 - stale-result guard、cancel/dismiss 幂等、provider 错误脱敏和 shutdown cancel;`session_id` 是每轮 - generation token,`conversation_id` 是成功多轮间稳定的 Selection Voice preview owner;15 项 - core contract 覆盖文本、语音、多轮、取消、迟到回答、approval token、错误脱敏、preview 清理和 shutdown。 -3. `EventBus` 已由每个 backend 实例持有 2048 条有界 replay;`EventReplay` 显式返回 - `oldestSequence/latestSequence/truncated`,Less Computer sync 从 core replay 续接,不再依赖 - 进程级静态 event log。 -4. `TauriQaRuntimeAdapter` 已只持有 recorder/ASR、selection capture 的 opaque host context 和 - LLM/Coding Agent runtime 资源;生产 `BackendDependencies.qa_runtime` 已由 `QaService` 消费。 -5. QA hotkey、Esc、overlay finalize、`qa_toggle_recording`、`qa_submit_text`、edit-instruction 和 - dismiss 已接到同一 `QaApi`;Selection Voice 问答与编辑预览也复用同一 Core 链路。 -6. 独立 `QaHostState` 已完全删除;Coordinator、`TauriQaRuntimeAdapter` 与 `TauriHostActions` 共享 - 一个 `TauriQaHostContext`,其中 `AtomicBool` 只表达 Tauri panel 可见性,业务 phase/messages/ - cancel 仍只属于 `QaService`。dismiss 使用稳定 `conversation_id` 清理匹配的 Selection Voice preview。 -7. Tauri QA Adapter 4 项 contract 已通过(含共享 show/clear 可见性);React 已处理 - `awaiting_approval` 与 `cancelled` 终态; - QA snapshot resync 与 live event 共用同一字段转换,lagged 后不会产生第二套 phase/可选字段规则。 -8. QA panel、Less Computer window、键盘焦点、macOS NSPanel、热键优先级和 shortcut recording - 仍留在 Tauri host;这些不进入 `QaSnapshot`。 -9. `begin_recording`/`submit_text` 在 `HostAction::ShowQa` 失败时按 session/phase 原子回滚, - 不启动 recorder/prepare runtime;失败后可立即重试且不会残留 `Recording`/`Thinking` 活跃态。 -10. Less Computer 的文字入口已直接调用 `OpenLessBackend::submit_less_computer`;语音入口在 - Tauri 负责录音/native ASR 后,使用 `submit_less_computer_with_session` 把同一 session 交给 - Core。Tauri 不再构造 provider/model/permission/prompt/guard/continuation,也不再发射重复的 - `user/delta/tool/approval/terminal` 事件;这些全部来自 `LessComputerService` 的 typed event。 - -11. QA 编辑预览的 opaque insertion target 绑定已收窄为构造阶段注入的 - `TauriQaHostContext` callback;QA Adapter 不再从 `AppHandle` 反查 `Coordinator`,也不持有 - Coordinator 强引用。focused QA test 与 `shared-backend-wire-contract` source contract 已覆盖 - 该 seam,关闭时由 weak callback 自动失效。 - - 语音热键入口已补上 Core capture lease:按下先创建 Core session,宿主 recorder/ASR 与 - `submit_less_computer_with_session` 共用该 id;空转写、启动失败和取消会释放未提升的 lease。 - Starting pending stop、静音自动停止仍由 Coordinator 兼容层调用 `end_session`,但只负责宿主 - 录音/ASR 资源和热键生命周期,不得向 Linux egui 暴露其 `state`。Core 仍是 Agent provider、 - prompt、guard、approval、continuation、stream、cancel 和终态的唯一来源。 - -**剩余步骤(必须按顺序完成)** - -1. **已完成**:QA/Selection Voice/Remote Input 的 React source compatibility fixture 已覆盖 - command 名与 camelCase 参数、`awaiting_approval`/`cancelled`/`error`、typed QA event 字段、 - Remote status/error listeners 及 lagged resync;完整 `npm.cmd test` 58 项通过。 -2. **已完成(实例隔离)**:Less Computer 工具审批复用实例级 `CodingAgentApi.approve`,静态 approval - registry 已删除;contract 证明 token 不能跨 backend 实例解析。 -3. **已完成(compatibility UI)**:Less Computer mount 先建立实时订阅并暂存 pending,再以 - `afterSequence` 读取 `replay_events_after(sequence)`;按 replay 后 pending 的顺序合并,带 seq - 事件按最大水位去重,无 seq fallback 保留。`truncated=true` 时清空旧派生时间线、把水位重置为 - `oldestSequence - 1` 并从本次保留 replay 重建;同步期间新事件、重复 sequence 与截断重建已有 - 可观察 TypeScript contract。Linux/egui view model 必须实现同一语义,不复用 React 状态。 -4. **已完成**:为 Linux/headless 提供 `QaRuntimeAdapter` fixture 和显式 `Unsupported` 示例;egui 只消费 - `QaSnapshot`/typed events,不依赖 Coordinator、WebView backlog 或 Tauri window label。 -5. **已完成(Less Computer Core seam)**:`begin_less_computer_capture`、active session、 - capture cancellation/abort、同 session submit,以及 `LessComputerRunRequest/Result`、 - `LessComputerRuntimeAdapter`、 - `submit/cancel/dismiss/approve`、有界 continuation、approval timeout、stale stream 丢弃、唯一 - `Completed/Failed/Cancelled` 终态及 `Unsupported` 语义已由 Core contract 覆盖;Tauri - `TauriCodingAgentApi` 只实现进程/Git/临时护栏文件/stream transport。egui 只需要调用 facade、 - 订阅 `LessComputerEvent`、按 `seq` 去重并回传 approval。 -6. 在 Windows/macOS/Android Tauri 与 Ubuntu Linux host 上取得对应原生运行证明;本地 contract - 不能替代平台 recorder、selection capture、窗口和关闭生命周期验证。 - -**退出条件**:QA/Less Computer 的会话真相只在 core;Tauri command 只保留窗口来源授权与 wire -转换,Linux 不需要 Coordinator 或 WebView event backlog 即可驱动自己的 view model。 - -#### 8.3.6 Remote Input - -**当前已完成** - -1. `RemoteInputApi`/`RemoteInputRuntimeAdapter` 已冻结 status、configure、locale、显式 PIN read/ - rotation、local IP、connect/disconnect、start/feed/stop/cancel stream;status 是无 I/O 的同步快照, - transport 与 secret persistence 仍为 async。 -2. `RemoteInputService` 已拥有 enable/disable/port 状态转换、PIN 生命周期、locale、连接/session - 关联、64 KiB 上限的非空偶数字节 signed Int16LE frame 校验、重复/迟到 PCM guard、端口错误分类、 - typed status/error event 和 shutdown 清理。PIN 不进入 snapshot/event/serde/Debug;8 项 core - contract 通过。 -3. `TauriRemoteInputRuntimeAdapter` 只承载 PIN 文件、TLS/WSS server handle、local IP 和共享 backend - external dictation 桥接;认证后的连接与所有 PCM lifecycle 调用 Core。Coordinator 的 server、 - refresh generation/lock、PIN、locale、no-insert 状态和旧 persistence tests 已删除。 -4. settings diff、启动恢复和 remote commands 已调用 Core;Tauri `cargo check --lib` 与 7 项 - `remote_` 定向测试通过。托盘从 Core status 读取 locale,托盘刷新失败不改变业务结果。 - -**剩余步骤(必须按顺序完成)** - -1. **已完成(定向 contract)**:`get_remote_input_status` 保持旧 - `running/port/pin/urls` shape,PIN 只由这个显式 secret command 注入;core status/event 不含 - PIN。共享 React source contract 同时固定 locale、status/error listener 与 lagged resync 接线。 -2. **已完成(本地 Adapter contract)**:PIN 认证失败先于 `connect` 且继续使用 constant-time - compare;每连接仅一个活动 stream,重复 start 返回 Busy 且保留原 lease;disconnect 必须 cancel, - stop/cancel 后拒帧,服务 restart 取消旧 session 并让旧 connection/session 返回 `Cancelled`。 -3. **已完成(headless fixture)**:`RecordingRemoteInputRuntime` 提供不绑定 socket 的内存 transport, - 记录 server/audio start/stop/cancel 与 PCM frame;未注入生产 transport 时仍走稳定 - `Unsupported`,capability 不得伪造为 available。 -4. 在真实宿主验证证书安装、端口占用、WSS/H5、局域网 IP 与长连接 shutdown;Windows 本地 - contract 不能替代 Linux/macOS socket、证书或防火墙证明。 -5. **已完成(接口手册)**:fixture、错误码、16 kHz mono signed Int16LE、64 KiB frame 上限、 - 单 stream/restart/stale lease、幂等与 secret 规则已写入接口手册;后续破坏性变更才提升 - `BACKEND_CONTRACT_VERSION`。 - -**退出条件**:core 可以用内存 transport + external PCM fixture 完成远程听写 contract;Tauri -command/remote server 不再拥有业务 session、PIN 或 locale 的第二份真相。 - -#### 8.3.7 旧 Coordinator 与宿主耦合删除 - -1. 用 command/event baseline 逐项确认所有业务入口已有 core use-case 和 compatibility test。 -2. 删除 `Inner.app`、业务路径上的 `AppHandle`、直接 `emit*`、`tauri::async_runtime::spawn` 和由 - Coordinator 持有的重复领域状态;仅保留真正的 Tauri host orchestration。 -3. 把窗口、托盘、updater、dialog、shell、autostart、single-instance 和 Android JNI/overlay/IME - 移到明确 host Module;这些代码不得被 Linux package 引用。 -4. 对删除后的 import、State 管理、`manage(...)` 和 handler registration 做残余引用检查;不得保留 - 无消费者 manager 来掩盖迁移不完整。 -5. 重新生成 command/event baseline;任何数量变化都必须有兼容说明和前端调用点证据。 -6. 删除 `core_adapters.rs` 中已永久禁用的 `legacy_cloud_asr`、`legacy_cloud_polish` 和 - `legacy_omni` 迁移考古副本;对应行为只由 Core provider contract 保留。最终源码门禁应拒绝 - Tauri 重新出现第二份 endpoint/model/credential/cancellation 协议构造逻辑。 -7. 已删除无生产消费者的 `Coordinator::{start,stop,cancel}_dictation*` 兼容 facade;测试直接 - 覆盖宿主 helper,生产 React/CLI/Android/热键入口统一调用 Core。style-pack prompt 诊断和 - ASR vocabulary priority 同样已移入 Core,source contract 防止这些业务规则回流。 - -#### 8.3.8 2.0 Interface 冻结与 egui 移交 - -1. **已完成**:只 re-export facade、DTO、errors、events、capabilities、fake/headless fixtures;core 私有 - repositories、transport 和状态机实现不进入 egui 可依赖面。 -2. **已完成**:更新 `linux-egui-backend-contract.md`,逐项记录方法、字段、单位、nullable、幂等、取消、超时、 - outcome-unknown、事件顺序、lagged resync、线程规则和 capability 降级。 -3. **已完成**:headless example 演示 lifecycle、dictation、settings/history/style-pack、Local ASR、 - Marketplace、Selection、Selection Voice、QA、Remote Input 的可用或 `Unsupported` 分支;Selection - 还必须覆盖 preview/confirm/cancel/stale/outcome-unknown 和 Linux preview/revert `Unsupported`; - 示例不创建 egui 窗口,当前版本已实际运行通过。 -4. **已完成**:更新 Linux capability fixtures 和 view-model 映射表;每个未接线能力明确显示 unavailable,不能 - 用 fake 成功状态冒充生产支持。 -5. **本地已完成,原生 runner 待完成**:以固定审查基线 - `a569a8749188e7843d426f159523193c8d5363ce` 运行第 12 节 Windows 本地门禁并记录命令与测试数。 - 当前冻结版本为 `BACKEND_CONTRACT_VERSION = "2.0.0"`;破坏性变更必须附迁移说明。 - Android/macOS/Ubuntu 与发行包证据继续由 12.4 的未勾选项约束。 - -#### 8.3.9 Provider 验证与模型列表迁入 Core(核心迁移已完成;发布前收口进行中) - -这是本轮已收口的 provider 管理面迁移。云端 ASR/LLM/Omni 的正式运行与 -`validate_provider_credentials` / `list_provider_models` 现在都由 `openless-core::ProviderService` -承载;Tauri command 仅保留参数/旧 wire/error 转换,Linux 的 -`LinuxBackendBuilder::from_shared_providers` 注入同一 service。真实网络、Secret Service 和各平台 -原生 runner 仍属于 M9/M10 的独立证据,不能由本地 fixture 代替。 - -**目标边界** - -- Core 拥有 provider 类型解析、channel-scoped credential 读取、默认 endpoint/model、协议选择、 - endpoint 安全校验、验证请求、静态/远端模型列表、超时/取消和稳定错误码。 -- Tauri 只负责旧 command 参数解析(`kind` 字符串、可选 `channel_id`)、调用 Core、旧 JSON/错误 - 字符串兼容和 React event/wire 转换;不得再持有 HTTP/WS 请求或 provider 分支。 -- Linux 生产 factory 注入与 Tauri 完全相同的 Core `ProviderApi` 实现;egui 只调用公开 - `BackendServices::provider`,不读取凭据、不构造 client、不选择协议。 -- native/local ASR 的模型加载仍属于 Local ASR Adapter;本节只迁移 provider “连通性验证”和“模型 - 列表”管理面,不能把 macOS/Windows 专属 native runtime 引入 Linux workspace。 - -**建议文件与责任人** - -| 文件/目录 | 变更 | 主责 | -| --- | --- | --- | -| `openless-all/app/crates/openless-core/src/provider_service.rs`(新增) | `ProviderService`、credential resolver、validate/list_models 分派与错误映射 | Core 负责人 | -| `openless-all/app/crates/openless-core/src/provider_rules.rs` | 汇总默认值、协议判定、endpoint/model 校验;删除重复规则 | Core 负责人 | -| `openless-all/app/crates/openless-core/src/provider_service.rs`(module tests) | channel credential 隔离、Omni channel 拒绝、静态/远端模型解析、秘密边界和错误映射 contract | 测试负责人 | -| `openless-all/app/src-tauri/src/commands/providers.rs` | 仅保留 command 参数和旧 wire/error 转换,删除业务实现 | Tauri 负责人 | -| `openless-all/app/src-tauri/src/core_adapters.rs` | 注入共享 `Arc`,删除 `TauriProviderApi` 反向代理 | Tauri 负责人 | -| `openless-all/app/linux-egui/src/backend.rs` | `from_shared_providers` 注入同一 Core service | Linux host 负责人 | -| `openless-all/app/linux-egui/src/backend.rs`(module tests) | 断言 Linux factory 的 provider 非 `Unsupported` 且不依赖 Tauri | Linux host/测试负责人 | -| `openless-all/app/scripts/shared-backend-wire-contract.test.mjs` | command 源码门禁:禁止 vault/HTTP/provider 构造回流 | 测试负责人 | -| `docs/linux-egui-backend-contract.md` | provider 请求、错误、能力和版本契约 | 架构负责人 | - -**实施步骤记录(核心迁移与宿主接线已完成;发布前收口项见下)** - -1. **冻结输入输出契约。** - - 保留公开 `ProviderRequest { kind, channel_id }`、`ProviderCheckResult` 和 - `ProviderModelsResult` 的 serde 字段;`channel_id = None` 继续表示当前 active channel,不能 - 静默改变旧 React 行为。 - - 为 `ProviderApi` 增加文档化的超时、取消、幂等和错误映射:参数/模型缺失用 - `InvalidArgument`,凭据缺失用 `Provider`(带可操作的稳定 sentinel),网络/HTTP/WS 失败用 - `Provider`,取消用 `Cancelled`,未接线能力用 `Unsupported`;不得把错误统一压成普通字符串。 - - 明确秘密边界:API key、token、Authorization、device code、完整 endpoint credential 不得出现在 - DTO、`BackendError.details`、日志、`Debug` 或测试 fixture;验证结果只返回 `ok` 或脱敏错误码。 - - 在 `docs/linux-egui-backend-contract.md` 增加 provider 表格:请求字段、默认值、验证是否发真实 - 请求、模型列表是静态还是远端、超时上限、可重试性和 Linux capability。 - -2. **建立 Core-owned credential resolver。** - - 在 core 新增窄的 `ProviderCredentialResolver`(或等价私有 module),只依赖 - `CredentialStore::read(CredentialKey)`;按 `CredentialNamespace::{Asr,Llm,Omni}` 和 channel id - 读取 key/endpoint/model/extra headers/temperature/advanced config。 - - 将 `ProviderScope` 的 channel 规则迁入 core:ASR/LLM 允许 channel id,Omni 明确拒绝 channel id; - channel 的 `provider_type` 来自非秘密 metadata,不能用 id 猜协议;`None` 回退 active provider - 只能由 resolver 统一完成。 - - 将 `CredentialAccount` 到 core `CredentialKey` 的映射集中定义并加单元测试,验证 channel A/B - 不串 credential,active 切换不改变已捕获的请求配置。 - - resolver 返回不含秘密的 `ResolvedProviderSummary`(provider type、model、endpoint 是否配置、 - auth mode),真实 secret 仅在构造请求的短生命周期对象中存在。 - -3. **迁移 provider 规则和默认值。** - - 将 `parse_provider_kind`、默认 endpoint/model、Bailian endpoint 派生、StepFun 模型协议判定、 - DashScope/Whisper 请求格式、模型白名单和 URL scheme 校验迁入 core `provider_rules`/provider - service;已有同名规则只保留一份实现。 - - 复用 core 已有的 `SharedCloudTranscriptionEngine`、`SharedCloudTextPolisher`、 - `SharedOmniDictationEngine` 构造路径,确保验证请求和正式运行请求使用相同 provider type、 - channel、model、endpoint 与 credential account。 - - provider-specific 常量(Bailian、Qwen realtime、Volcengine、Xfyun、StepFun、Mimo、 - ElevenLabs、DashScope、Codex OAuth 等)放在 core provider module;只有 native engine 和平台 - 文件路径留在 Adapter。 - - 验证策略必须逐 provider 固定,不能由 Tauri 继续隐式决定: - - | provider 类别 | Core 验证动作 | 模型列表 | 关键凭据/规则 | - | --- | --- | --- | --- | - | OpenAI-compatible LLM/ASR | 最小 chat completion 或真实 transcription 请求 | 远端 `/models`(失败即明确错误) | endpoint URL、model 必填;LAN 无鉴权模式按 provider 规则允许空 key | - | Gemini LLM/Omni | `generateContent`/等价最小文本探活 | Core 静态或 Gemini 列表转换 | API key、endpoint、model 按 channel 读取 | - | Codex OAuth | 使用已保存 OAuth 状态执行最小 polish/授权检查 | Core 静态 Codex 模型清单 | token 只在 secret store;失效返回脱敏 OAuth 错误 | - | Bailian classic/Qwen realtime | WSS 握手 + 最小静音帧 + 收尾 | Core 静态清单,按模型分协议 | endpoint 按协议派生;必须校验 `ws/wss` scheme | - | Volcengine/Xfyun/StepFun realtime | 对应 WS 鉴权、session.update、静音收尾 | Core 静态清单 | 多字段鉴权、模型协议判定和错误码归 Core | - | Mimo/ElevenLabs/DashScope batch | 规范 WAV/官方示例音频的真实 HTTP 请求 | Core 静态清单或受限远端列表 | 响应大小、超时、示例音频和模型白名单固定 | - | local/native ASR | 不在本节验证;转交 `LocalAsrApi` runtime | 由 Local ASR catalog 提供 | 无法接线时返回 `Unsupported`,不把 Tauri native runtime 带入 Linux | - -4. **实现 Core `ProviderService`。** - - 新增 `ProviderService { credentials, task_spawner, http_client_factory/transport }`,实现 - `ProviderApi::validate` 与 `ProviderApi::list_models`;构造时注入 `CredentialStore`,不访问 - Tauri `State`、`AppHandle` 或全局 vault。 - - `validate` 按 `ProviderKind` 和 resolved provider type 分派:LLM/Codex OAuth、传统 HTTP ASR、 - realtime WS ASR、Omni 文本探活分别调用对应 core provider;静音音频、示例音频和握手收尾规则 - 与正式 provider 实现保持一致。 - - `list_models` 对无远端列表接口的 provider 返回 core 静态清单,并先执行与 validate 相同的凭据/ - endpoint 校验;对 OpenAI-compatible 等远端列表接口使用 core HTTP transport,限制响应大小、 - 禁止 redirect 到未经允许的地址并做 JSON schema 校验。 - - 所有请求使用显式 timeout 和 cancellation token;超时不得隐式切换渠道或重复发起可能产生 - 计费的探活请求。请求完成后释放 secret 和 transport handle。 - - 将当前 Tauri sentinel(例如 `providerHttpStatus:*`、`endpointInvalid`、`asrModelMissing`) - 转成 `BackendErrorCode` + 稳定 machine detail;Tauri 兼容层再把 code 映射回旧字符串,Core - 本身不依赖中文文案。 - -5. **补齐 provider fake/transport fixture。** - - 提供 `FakeProviderTransport`,可按 endpoint/model 返回成功、401/403/429/5xx、超时、无效 JSON、 - redirect 和取消;fixture 不保存真实 key。 - - 提供 `InMemoryCredentialStore` 的 channel A/B、active provider、缺失凭据和错误注入场景;每个 - 测试使用唯一临时目录并自动清理。 - - 为静态模型清单、远端模型清单、空模型、未知 provider type、Omni channel 拒绝、Bailian/StepFun - 双协议和 Codex OAuth fallback 建立 serde/行为 fixture。 - -6. **迁移 Tauri Adapter。** - - `TauriProviderApi` 改为在构造时持有 `Arc`(或直接复用 Core service),只做 - `String -> ProviderKind`、`ProviderRequest` 构造和 `BackendError -> legacy String/JSON` 转换。 - - 删除 `commands/providers.rs` 中的 `ProviderScope`、credential 读取、`ProviderConfig`、HTTP/WS - validation、model-list 分支、provider-specific request body 和 provider 错误分类;文件只保留 - `#[tauri::command]` 薄函数及兼容转换。 - - 删除 `TauriProviderApi` 对 `validate_provider_service`/`list_provider_models_service` 的反向 - 调用;source contract 必须拒绝 `CredentialsVault::get*`、`reqwest::Client`、provider 构造器和 - `tokio::time::timeout` 在该 command module 重新出现。 - - 保留旧 command 名、参数 key、nullable 语义和 React 错误映射;新增字段只允许向后兼容,破坏性 - 变更必须提升 `BACKEND_CONTRACT_VERSION` 并附迁移说明。 - -7. **接入 Linux 生产 factory。** - - `LinuxBackendBuilder::from_shared_providers(config)` 打开一次 `LinuxCredentialStore`,构造 - Core `ProviderService` 并写入 `services.provider`;不得让 egui 注入 provider factory 或 credential - account。 - - `LinuxBackendBuilder::new(...)` 的显式 provider 注入仅用于测试/特殊宿主;测试构造仍可用 fake - `ProviderApi`,但生产入口必须经过 shared factory。 - - Linux 无法提供 native/local runtime 时只对对应 Local ASR 能力返回 `Unsupported`;云端 provider - validate/list 不得因为 UI 非 Tauri 而返回 `Unsupported`。 - - capability snapshot 增加 provider 管理面状态(configured / unconfigured / unsupported),但 - 不泄露 key;egui 根据 snapshot 和错误码显示降级文案。 - -8. **补跨宿主 contract 与回归测试。** - - Core contract:channel scoped credential、provider/type/model 冻结、每种协议的成功/失败/取消、 - 静态/远端模型列表、秘密不泄漏、超时不重复、未知 provider 和 Unsupported。 - - Tauri wire contract:旧 command 名、camelCase 字段、错误 sentinel、Codex/Omni/ASR 分支结果与 - 现有 React fixture 一致;command 源码 contract 证明不含业务实现。 - - Linux host contract:使用同一 fake credential/transport 调用 `services.provider.validate` 和 - `list_models`,证明不经过 Tauri;生产 factory smoke 至少断言 `services.provider` 不是 - `UnsupportedDomainServices`。 - - 将 provider contract 纳入 M9 门禁和 `shared-backend-wire-contract.test.mjs`;任何 Tauri provider - 业务 token 回流、Linux factory 未注入或 channel credential 串线都必须使门禁失败。 - -9. **删除旧实现并做残余审计。** - - `rg` 检查 `commands/providers.rs` 不再出现 `CredentialsVault`、provider HTTP/WS client、 - provider struct constructor、模型列表静态清单和协议分支;残余只允许兼容转换函数。 - - `rg` 检查 core provider module 不出现 `tauri::`、`AppHandle`、window label、React event name; - Linux crate 不出现 Tauri/WebKitGTK。 - - 重新生成 command/event baseline,运行 `cargo fmt --check`、Core/Linux clippy、Core/Tauri/Linux - provider tests、frontend contract、完整 Tauri suite 和 `git diff --check`。 - - 记录迁移前后 provider 请求/错误行为差异;若发现行为变化,先补 fixture 再改实现,不通过修改 - React 调用方掩盖兼容问题。 - -**当前实现的发布前收口项(不阻塞 egui UI 开发,但阻塞 provider 生产发布)** - -- **已完成:可替换 transport 与 fake 覆盖。** `ProviderService` 通过 `ProviderTransport` 注入模型列表 - 请求;生产使用无 redirect、显式 15 秒 timeout、2 MiB response 上限的 reqwest 实现,测试使用 - `FakeProviderTransport` 覆盖 401/403/429/5xx、timeout、connection/request、cancel、invalid JSON、 - response-too-large 和 redirect 状态,并验证 URL/header value 不进入 `Debug`、错误或 fixture 输出。 -- **已完成:静态模型 parity。** Core 静态清单已按迁移前 Tauri 顺序补齐 Bailian/Qwen realtime、Mimo、 - Fun-ASR、ElevenLabs 与 Codex OAuth 条目;测试固定每个 provider 的顺序和去重,后续新增模型必须先更新 - parity fixture,不能把“可返回列表”误认为完整 parity。 -- **已完成:显式 channel-scoped LLM 写入。** `temperature` 与 `extra headers` 写入按指定 - `provider_id` 定位,不再无条件写 active channel;A/B channel 回归测试证明 active channel 不会被旁路 - 修改。读取期间的 provider snapshot 仍必须保持同一 channel 语义。 -- **仍未完成:真实 provider 与平台证据。** 真实 provider 网络、Secret Service/keyring、取消/超时在真实 - runtime 下的行为,以及 Android/macOS/Windows/Ubuntu runner 证据仍按 M9/M10 执行;本地 parser、unit、 - fake transport 和 WSL contract 只能证明纯函数与接线,不能代表真实服务可用。 - -**本节退出条件** - -- `ProviderApi` 的验证和模型列表在 Core 只有一份可测试实现,Tauri 与 Linux 使用同一实例语义。 -- Tauri provider command 只剩参数转换、Core 调用和旧 wire/error 兼容;源码门禁无业务副本。 -- Linux 生产 factory 不再把 provider 管理面设为 `Unsupported`,egui 可在无 Tauri 环境调用公开接口。 -- Core/Tauri/Linux provider contract、秘密扫描、依赖方向、完整本地门禁全部通过;真实网络、keyring - 和平台 runner 仍按 M9/M10 单独留证,不能用 fake 证明正式发布。 - -### M0:冻结决策和兼容契约 - -**任务** - -1. 确认目标平台:macOS/Windows = Tauri,Linux = egui,Android = 继续 Tauri mobile。 -2. 确认 Linux egui 与 core 是否同进程;本计划默认同进程直接 Rust 调用。 -3. 建立当前 command 名称、参数、返回 JSON、事件名和能力字段的基线清单。 -4. 用 `rg` 统计所有 `tauri::`、`AppHandle`、`emit`、`listen` 和 `#[tauri::command]` 使用点,保存为迁移 checklist。 -5. 标记 UI 专属命令:窗口打开/关闭、focus、拖动、动画和 WebView bridge 不进入 core。 -6. 写下 Android 保持 Tauri 的决定,避免迁移时误删 mobile 分支。 - -基线漂移检查由 [`scripts/check-command-event-baseline.ps1`](../openless-all/app/scripts/check-command-event-baseline.ps1) -执行;它会从当前 `lib.rs` handler 宏重新提取 command 名称,并拒绝缺失、意外新增、重复或计数不一致。 - -**产物** - -- 本计划文档完成评审。 -- `docs/linux-egui-backend-contract.md`(M7 生成的接口手册)目录和版本策略确定。 -- [`docs/linux-egui-command-event-baseline.json`](./linux-egui-command-event-baseline.json): - 机器可读的 command/event/capability 基线清单(当前观察到 196 个 Tauri command、30 个 - legacy event、29 个 core event kind)。 -- 每个模块的 owner、依赖和迁移顺序表。 - -**验收** - -- 任何新增公共接口都能回答“core、Tauri host 还是 Linux host 的职责”。 -- 产品与发布决策显式列在本文档“M0 决策记录”,不通过临时代码默认。 - -### M1:建立 package 骨架和依赖门禁 - -**任务** - -1. 创建 `crates/openless-core`,加入最小 `Cargo.toml` 和空 facade。 -2. 创建 `linux-egui` package 的空 host stub;不实现 UI,只验证能依赖 core。 -3. 将 `src-tauri` 标记为 Tauri 适配器(现有 Cargo package 名称可继续为 `openless`),先不改变 React command 名称。 -4. 根 workspace 只包含 core/Linux 并使用根 `Cargo.lock`;Tauri 与 backend compatibility tests 使用独立 manifest/lockfile,避免 Linux 解析 macOS/Tauri dependency。 -5. 为 core 增加依赖检查脚本:core 的正常依赖树不得包含 `tauri`、`wry`、`webkit2gtk`、`egui`、`eframe`。 -6. 为 Linux package 增加同样的依赖检查;允许 Linux 原生窗口依赖,但不得出现 Tauri/WebKitGTK。 -7. 审计 `qwen3-asr-rs`、`qwen-asr` 等 vendored path;Tauri 作为独立 manifest 保留 native runtime,根 core/Linux workspace 显式 exclude Tauri,避免 macOS-only path 在 Linux 元数据阶段被解析。 -8. 保留现有 Tauri package 能独立 `cargo check` 的能力。 - -依赖门禁脚本为 [`scripts/check-core-deps.ps1`](../openless-all/app/scripts/check-core-deps.ps1), -接受 `openless-core`(默认)或 `openless-linux-egui` 作为 package 参数。 - -**验收** - -```text -cargo check --locked -p openless-core -cargo check --locked -p openless-linux-egui -cargo check --locked --manifest-path "src-tauri/Cargo.toml" --lib -pwsh -NoProfile -File "scripts/check-core-deps.ps1" openless-core -pwsh -NoProfile -File "scripts/check-core-deps.ps1" openless-linux-egui -``` - -### M2:迁移共享类型、错误和能力模型 - -**任务** - -1. 从 `types.rs` 提取平台无关的枚举、DTO、快照和 serde 字段。 -2. 保持现有 `camelCase` / `snake_case` / `lowercase` 序列化名称,避免 React IPC 破坏。 -3. 把 `CapsulePayload` 重命名或包成 core 的 `DictationStateSnapshot`;Tauri adapter 提供旧名字映射。 -4. 建立 `BackendError` 和错误码,command 层暂时保留旧字符串输出的兼容转换。 -5. 将 `PlatformCapabilities` 的结构放入 core;`current()` 改为由 host 传入或由平台 adapter 构造。 -6. 将秘密字段、日志字段和 UI 可见 status 分开,禁止快照包含明文凭据。 -7. 把纯类型测试移到 core,确保 JSON fixture 与现有前端契约一致。 - -**验收** - -- `openless-core` 不包含任何窗口、WebView、Tauri 或 egui import。 -- React 现有 command 的字段名和枚举值不变,或有显式版本兼容转换。 -- core 类型测试、serde fixture、错误码测试全部通过。 - -### M3:定义宿主 Interface、依赖注入和生命周期 - -**任务** - -1. 定义 `BackendConfig`、`BackendDependencies`、`TaskSpawner`、`Clock`、`HostActions`。 -2. 把凭据、录音、文本插入、热键、资源目录等真实变化点定义为最小 Interface。 -3. 提供 `InMemoryCredentialStore`、`FakeRecorder`、`RecordingInserter`、`FakeHotkeyController` 和 `HeadlessHostActions`。 -4. 明确每个 Interface 的线程安全、超时、取消和错误语义。 -5. 在构造阶段完成依赖注入;禁止 core 在方法内部自行 `new` Tauri plugin 或系统窗口。 -6. 增加 `start()` / `shutdown()` lifecycle tests,包括重复调用、启动失败和取消中关闭。 - -**验收** - -- 无真实麦克风、窗口或 keyring 时可以构造 core 并运行状态机测试。 -- fake inserter 能验证成功、fallback、失败和 outcome-unknown,不需要 Tauri。 -- shutdown 后没有后台 task、录音或热键泄漏。 - -### M4:抽取 Coordinator 和统一事件总线 - -**任务** - -1. 将 `Coordinator` 拆为 core coordinator 与宿主 host action/event bridge。 -2. 移除 `Inner.app: Mutex>`,替换为 core event bus 和 `HostActions`。 -3. 把 `tauri::async_runtime::spawn` 替换为 core executor Interface。 -4. 将 `emit_to("capsule", ...)`、`emit_to("qa", ...)`、窗口显示/隐藏逻辑移到 Tauri adapter。 -5. 把 `coordinator_state` 的 generation/session guard 与所有异步完成路径接到统一 session token。 -6. 建立事件顺序、sequence、lagged subscription、最终态唯一发布的测试。 -7. 为 `DictationStateChanged`、transcript delta、polish delta、insert fallback 建立最小端到端 fixture。 - -**验收** - -- core 可以在 headless host 中跑完整“开始 → 录音 → ASR → 润色 → 插入 → 终态”测试。 -- Tauri 和 Linux host 都能收到同一套语义事件。 -- 旧 Tauri 事件名只存在于 adapter,不存在于 core。 - -**当前收口状态** - -- `Coordinator` 已不持有或接收 `AppHandle`/`WebviewWindow`,不直接发射 Tauri 事件; - `TauriCoordinatorHost::app()` 保持私有。 -- capsule 原生窗口行为和窗口状态已归 Host;Coordinator 只计算或传递语义状态与窄值。 -- M4 仍保持“进行中”:compatibility Coordinator 尚未完全删除,剩余生产调用必须继续按 - core 业务、Host 原生能力、无生产消费者三类处置,且原生 runner 证明尚未齐全。 - -### M5:迁移持久化、provider 和业务领域 - -按低风险到高风险顺序迁移,保持每个阶段可编译: - -1. `settings` / `preferences` / `PlatformCapabilities` -2. `history` / `activity` / `dictionary` / `correction` -3. `style_packs` / zip import-export / runtime diagnostics -4. `credentials` / provider channels / OAuth status(保留秘密隔离) -5. provider validation/model-list 管理面(按 8.3.9 迁入 Core `ProviderService`,再进入 ASR/LLM/Omni - 正式请求路径) -6. `asr` / `polish` / `omni` / `net` -7. local ASR model catalog、download、prepare、release -8. remote input server、marketplace、coding agent -9. selection polish、selection voice、QA、Less Computer - -每个领域的步骤: - -1. 把实现文件移入 core module。 -2. 把 `AppHandle`、`State`、window label 和 Tauri plugin 调用替换成宿主 Interface/语义事件。 -3. 将原 command 改为薄 wrapper,保留 command 名称。 -4. 将原模块测试移入 core interface tests。 -5. 用 fake adapter 补齐成功、失败、取消、过期结果和权限降级场景。 -6. 更新 Tauri adapter 和 Linux contract 示例。 -7. 运行领域测试、core 全量测试和 Tauri compile gate。 - -`asr` / `polish` / `omni` 的迁移还必须额外满足:provider router 的重复 session 不能覆盖 -原路由;所有生产 Adapter 必须按 `DictationContext` 固定到会话;旧 prompt compose 的 XML、 -净化、防注入、前台应用、光标上下文、历史 turns、翻译和 user prompt envelope 必须有兼容 -fixture。只有“能请求某个 OpenAI-compatible endpoint”不等于完成了旧主链语义迁移。 - -**当前实施状态** - -- settings/preferences 已由 `OpenLessBackend::update_settings` 统一 strict/reconcile、legacy 同步、 - preserve-style、乐观 revision、单写入 gate、显式 effect plan、receipt 补偿及一次持久化/事件。 -- history/activity/vocabulary/correction/style-pack/credentials、prompt compose、云 ASR/LLM/Omni、 - Coding Agent、Local ASR、Marketplace、Selection/Selection Voice、QA 和 Remote Input 已有 Core - Implementation/Interface;操作系统录音、插入、socket、native/local ASR 与授权副作用继续以 - 注入 Adapter 表达。 -- 旧 `set_preferences*` 公共兼容面与 `legacy-preferences-write` feature 已删除;测试私有 helper 不能 - 被宿主启用。Android/macOS/Windows/Ubuntu 原生 effect 与网络/音频/窗口行为仍须由对应 runner - 证明,不能由 Windows contract test 推断。 - -**验收** - -- 同一业务规则只在 core Implementation 中存在;Tauri/Linux Adapter 不保留第二份判断。 -- 领域 Interface 的成功、失败、取消、过期结果、权限降级和秘密隔离都有 contract test。 -- 对应 Tauri command 仍保持既有名称和序列化语义;Linux 未接线能力明确返回 `Unsupported`。 -- 每迁移一个领域即可独立合并和回退,不要求一次性搬完全部领域。 - -### M6:完成 Tauri 薄适配器 - -**任务** - -1. Tauri setup 只负责构造 dependencies、创建 `Arc` 和管理 host state。 -2. 每个 `#[tauri::command]` 只做参数转换、调用对应 core use-case、错误/DTO 序列化。 -3. 建立 `tauri_events.rs`:订阅 core event,映射为当前 React 监听的事件名。 -4. 把窗口创建、窗口定位、拖动、透明/点击穿透、vibrancy/Mica、托盘和 menu 放入 Tauri host。 -5. 把 updater、dialog、shell、autostart、single-instance plugin 保留在 Tauri host。 -6. 把 `restart_app`、system settings、external URL 等系统动作接到 `HostActions`。 -7. 保留 Android Tauri host 的 JNI/overlay/IME 分支,避免 core 被 mobile 专属类型污染。 -8. 为旧 IPC contract 增加 TypeScript/Rust 交叉测试:command 名称、参数 key、事件 payload 和错误码一致。 - -**当前实施状态** - -- setup 已构造共享 `Arc`;React command、CLI、Android JNI、remote PCM、桌面普通 - 听写热键及已迁移复杂领域通过 Core Interface 调用。 -- `backend_dependencies()` 使用同一个 `SystemCredentialStore` 构造 Core 的共享云 ASR、LLM、Omni、 - Auxiliary 与 QA provider;Tauri Adapter 只追加平台录音、native/local ASR、窗口、插入和 runtime。 -- Provider 管理面也已共享:`ProviderService` 的 `validate`/`list_models` 读取 Core credential port - 并执行统一 provider 构造/模型解析;`commands/providers.rs` 不再读取 `CredentialsVault` 或发起 - provider-specific HTTP/WS 请求,Linux factory 注入同一 Core service。 -- settings/QA/全部快捷键入口已切 Core transaction;`TauriSettingsRuntime` 只消费显式 target, - Coordinator 的 listener runtime target 与偏好文档分离,style-pack 删除消费 Core outcome。 -- command 内旧 settings/hotkey 事务副本及 previous/write/refresh/rollback helper 已删除; - `core_adapters.rs` 不再通过 `AppHandle` 反取 Coordinator,Local ASR 与 hotkey/QA 只接收构造层 - 注入的窄依赖;仍需继续审计 compatibility Coordinator 的非 settings 宿主职责并取得原生 runner - 证据。 - -**验收** - -- React 主窗口、capsule、QA、Less Computer、选择润色和设置页面仍可调用原 IPC。 -- `src-tauri` 是唯一出现 `#[tauri::command]` 和 Tauri window label 的 package。 -- Tauri adapter 不包含 provider validation/model-list 核心业务分支;source contract 已证明 command - module 不含 provider 协议、凭据读取和请求构造。 - -### M7:交付 Linux egui 接口包(本计划负责) - -这一阶段不实现 egui UI,只交付让另一组可以开始 UI 开发的完整材料。 - -**任务** - -1. 发布 [`openless-core` 接口手册](./linux-egui-backend-contract.md),包含: - - 构造与生命周期 - - 所有领域接口和参数 - - `BackendSnapshot` 字段 - - `BackendEvent` 分类、顺序和 session 规则 - - `BackendErrorCode` - - `PlatformCapabilities` 能力矩阵 - - 线程、取消、超时和重连规则 -2. 提供 `linux-egui/examples/headless_host.rs`,展示构造 backend、订阅事件、执行听写和 shutdown;示例不绘制 UI。 -3. 提供 `FakeBackend` 或 fake provider fixture,允许 egui 团队在无网络、无麦克风环境调试页面。 -4. 提供事件到 UI view model 的推荐映射表: - - | core 事件 | egui 团队应更新的状态 | - | --- | --- | - | `DictationStateChanged` | 录音/转写/润色/完成状态和 level | - | `TranscriptDelta` | 原文增量文本 | - | `PolishDelta` | 输出增量文本 | - | `InsertFallback` | fallback 提示卡片状态 | - | `PreferencesChanged` | 设置缓存 | - | `CredentialsChanged` | provider 是否配置,不显示秘密 | - | `HistoryChanged` | 历史列表失效并重新读取 | - | `DownloadProgress` | 模型下载进度 | - | `PermissionChanged` | 权限状态和降级文案 | - | `HotkeyStatusChanged` | 热键能力/错误状态 | - | `Notification` | 非阻塞通知队列 | - -5. 提供 Linux capability fixture,覆盖 X11、Wayland、fcitx5 可用/不可用、无托盘、无权限和不支持更新器等状态。 -6. 提供 headless host 的 contract tests:调用顺序、事件顺序、错误码、取消和 snapshot resync。 -7. 给 egui 团队一份“不得依赖内部实现”的检查表,明确只能依赖 core facade、DTO 和 event subscription。 -8. 约定接口版本:破坏性字段变更必须更新 contract version 和迁移说明;新增可选字段默认兼容。 -9. 设置公共入口只暴露 `LinuxHost::save_settings(preferences, expected_revision)` 和 - `update_settings_strict(preferences, expected_revision)`;调用方先从 `snapshot()` 获取 revision。 - reconcile 入口用于整表兼容保存,strict 入口用于单项/严格保存;两者都由 Core 事务执行 effect、 - 持久化和补偿,UI 不得调用低层 `set_preferences*`。 -10. 生产构造入口固定为 `LinuxBackendBuilder::from_shared_providers(config)`;它打开 Linux - `CredentialStore`,注册 Core 共享 ASR/LLM/Omni/Auxiliary、`ProviderService` 和传统 Pipeline。 - Provider 管理面已由 Core `ProviderService` 接线;egui UI 不注入 `TranscriptionEngine`、 - `TextPolisher`、credential account 或 provider router;显式 - provider 注入的 `LinuxBackendBuilder::new(...)` 只用于测试和特殊宿主。 -11. Marketplace 由同一生产 factory 注入 `MarketplaceConfig::production()`;UI 通过 - `LinuxHost::download_marketplace_archive(pack_id, target)` 保存 Core 已校验归档。`target` 必须是 - 已有父目录下的绝对路径;宿主不得覆盖已有文件,写入失败必须清理不完整文件。 - -`EventSubscription::try_recv()` 是 egui 帧内消费事件的非阻塞入口;收到 `Empty` 结束本帧 -drain,收到 `Lagged` 必须用 `snapshot()` 或领域查询重同步。Linux host contract test -位于 `linux-egui/tests/host_contract.rs`,不创建窗口也不依赖 Tauri。 - -**交付边界** - -- 我们负责 Rust core、host Interface、示例和 contract tests。 -- egui 团队负责 `eframe::App`、布局、控件、交互、绘制、输入法体验、视觉和 UI 测试。 -- egui 团队不需要修改 core 内部模块;发现缺少能力时提交接口需求和可复现 contract test。 - -**验收** - -- egui 团队只依赖公开 facade、DTO、事件订阅、fake/headless Adapter 和 contract 文档即可开始开发。 -- Interface 手册明确字段、线程、顺序、取消、错误、能力降级和版本兼容规则,不要求阅读 core Implementation。 -- 未完成的真实 Adapter 返回稳定的 `Unsupported`;示例和 fixture 不把未接线能力伪装为可用。 -- provider 验证/模型列表必须来自 Core `ProviderService`;Linux factory contract 已断言该 service - 已接线且未回退为 `Unsupported`,egui 不得复制 Tauri provider 逻辑。 -- headless 示例实际执行并覆盖听写、Selection、Selection Voice、stale session 与 outcome-unknown; - Linux host contract 从公开 API 验证同一能力边界。 -- 本阶段最初只移交接口;后续 F01/F02 已把可操作的 egui UI 纳入本 PR。生产 UI 的连续会话、审批和增量显示现纳入复核,视觉设计仍由 egui 团队负责。 - -### M8:Linux host 接口接线准备 - -此处保留早期非 UI 适配器阶段的设计;最终生产 UI 与其启动、事件和目标验证见本轮复核记录: - -1. 实现 Linux `TaskSpawner`、`HostActions`、`ResourceResolver`、`CredentialStore`。 -2. 将 fcitx5 DBus commit、热键同步、选区读取接入 Linux platform adapter。 -3. 把 `ensure_plugin_installed(app: &tauri::AppHandle)` 改为资源目录/目标目录接口;Tauri 和 Linux host 各自提供路径。 -4. 实现 Linux 单实例、启动器参数、退出和后台生命周期;CLI intent 只转为 core action。 -5. 实现 Linux 音频设备枚举、level monitor、录音和插入 fallback adapter。 -6. 明确 X11 / Wayland 的支持矩阵和降级行为;不把“egui 能启动”当作 overlay、global hotkey 或 fcitx 全部可用。 -7. 输出给 egui 团队的 host capability snapshot 和错误文案。 -8. 用统一 `LinuxNativeRuntime` 持有 primary single-instance broker 和 fcitx5 hotkey listener; - `pump()` 非阻塞 drain intent/event/error,`shutdown()` 先停止并 join 宿主线程,再关闭 core。 -9. 在 Ubuntu/fcitx5 记录 translation modifier 与 dictation press 的真实信号顺序,并用时间线 - contract test 固定关联规则;晚到 modifier 不得修改已经启动的 `DictationContext`。 -10. 实现 `LinuxSettingsRuntime`,消费 Core 显式 hotkey/active-provider target,通过 fcitx5 DBus 与 - credential metadata 执行平台 effect,并以 typed receipt 逆序恢复。Coding Agent 启用与语音热键必须有真实 fcitx5 effect;其余未实现的 switch-style、open-app、style-pack hotkey 和 Windows keyboard effect 才保持明确 `Unsupported`。 -11. 以 `LinuxBackendBuilder::from_shared_providers(config)` 作为唯一生产 factory:UI 只传配置, - factory 内部创建 Linux credentials、Core `ProviderService`、共享云 ASR/LLM/Omni/Auxiliary - router、cpal recorder、fcitx5 inserter 和 settings runtime;测试/特殊宿主才使用显式 provider - 注入构造器。factory contract 必须断言 `services.provider` 已接线,不能静默回到 - `UnsupportedDomainServices`。 - -**验收** - -- headless Linux host 可以调用 core 的听写主链路。 -- Linux 生产 factory 可以调用共享 Core `ProviderApi::validate/list_models`,不经过 Tauri;provider - 凭据按 channel 隔离且错误/取消语义与 Tauri 一致。 -- fcitx5 缺失时返回 `Unsupported`/`Platform`,不会让 core panic 或假装插入成功。 -- 资源路径、插件文件和用户目录写入行为在 AppImage/deb/rpm 场景分别有测试。 - -**当前原生证据与边界(WSL Ubuntu)** - -- `secret_service_contract` 已在真实 `dbus-run-session`/gnome-keyring 下显式通过,证明 - `LinuxCredentialStore` 的 set/read/remove 和 secret 不落 metadata;普通 `cargo test` 不运行该 - contract,避免把桌面服务设为默认依赖。 -- `fcitx5_contract` 已在真实 fcitx5 加载仓库 plugin 后显式通过,证明 DBus object/method、listener - 启停及 press/release/combined/translation signal 映射;插件在无焦点输入上下文时只记录警告并 - 返回,fcitx5 不崩溃。合成 signal 不等价于真实物理按键顺序,仍需桌面 runner。 -- `cpal_contract` 已显式通过;当前 WSL 无 ALSA 输入设备,adapter 返回明确的平台/权限/不支持错误。 - 真实设备下的 stream start/stop、settings effect/单实例退出和焦点插入仍需 runner。 - -### M9:测试迁移和质量门禁 - -**测试层次** - -1. **Core unit tests**:状态转换、提示词、纠正规则、数据迁移、provider 规则/默认值/协议判定、错误分类、sequence 和 generation guard。 -2. **Core integration tests**:fake recorder、fake ASR/LLM、fake inserter、fake vault、fake provider transport、fake clock 的完整听写链路及 provider validate/list_models。 -3. **Adapter contract tests**:Tauri event mapping、Tauri provider wire mapping、Linux host actions、Linux provider factory、能力矩阵和资源目录。 -4. **Tauri compatibility tests**:现有 React command/event JSON 不变。 -5. **Linux dependency tests**:Linux package 编译不拉 Tauri/WebKitGTK。 -6. **Egui UI tests**:由 egui 团队负责;我们只提供 headless backend fixtures,不验收视觉布局。 - -**现有测试迁移** - -- `src-tauri/backend-tests/tests/backend_rust.rs` 及其 Tauri stub 已删除; - `backend-tests/tests/core_contract.rs` 直接依赖公开 `openless-core`,只验证 framework-independent - contract。 -- 原先被 path include 的纯规则测试归 `openless-core`;Windows IME、macOS host、Linux fcitx - 等平台测试归各自 crate;Tauri 内部单测由 Tauri crate 自身的 `--lib` 测试运行。 -- 现有 `src-tauri/src/lib.rs`、`coordinator.rs`、`types.rs` 的纯 Rust tests 随对应 - Implementation 迁移到 core;尚未迁移的测试留在 Tauri crate,不复制第二份源码。 -- 保留 Windows IME、macOS host、Linux fcitx 的平台 tests,但不让它们成为 core 的编译依赖。 -- 所有会写 repository 的测试必须使用每测试唯一且自动清理的临时 `data_dir`;禁止使用 crate-local - `"data"`。门禁在测试前后检查 `crates/openless-core/data/` 不存在,避免并行污染和未跟踪产物。 - -**建议门禁** - -以下命令从 `openless-all/app` 执行;本地和 CI 都必须使用已提交 lockfile: - -```text -npm.cmd test -npm.cmd run build -cargo fmt --check --all -cargo clippy --locked -p openless-core --all-targets -- -D warnings -cargo test --locked -p openless-core -cargo test --locked -p openless-core provider -cargo clippy --locked -p openless-linux-egui --all-targets -- -D warnings -cargo test --locked -p openless-linux-egui --all-targets -cargo test --locked -p openless-linux-egui provider -cargo test --locked --manifest-path "src-tauri/backend-tests/Cargo.toml" -cargo check --locked --manifest-path "src-tauri/Cargo.toml" --lib -cargo test --locked --manifest-path "src-tauri/Cargo.toml" --lib -pwsh -NoProfile -File "scripts/check-command-event-baseline.ps1" -pwsh -NoProfile -File "scripts/check-core-deps.ps1" openless-core -pwsh -NoProfile -File "scripts/check-core-deps.ps1" openless-linux-egui -pwsh -NoProfile -File "scripts/check-core-secret-surface.ps1" -pwsh -NoProfile -File "scripts/check-core-test-isolation.ps1" -pwsh -NoProfile -File "scripts/check-core-runtime-seam.ps1" -pwsh -NoProfile -File "scripts/check-linux-public-surface.ps1" -node "scripts/shared-backend-wire-contract.test.mjs" -git diff --check -``` - -依赖检查命令预期无匹配;如果某个正常依赖间接拉入禁止包,必须先解决依赖方向,再增加 allowlist,不能把问题隐藏在脚本中。 - -**验收** - -- core unit/integration、Tauri compatibility、Linux Adapter contract 和依赖门禁在 CI 中分别可见,失败时能定位到所属 Module。 -- backend contract tests 直接依赖公开 crate/Interface,不再用 `#[path]` 或伪造 `AppHandle` - 绕过真实 package 关系;Tauri crate 的原测试数单独记录,不能再沿用旧“compatibility 118”数字。 -- Windows 本地、macOS/Android cross-target 和真实 Ubuntu 原生验证分别记录;缺少某个平台证据时保持未完成状态。 -- 所有质量门禁使用已提交 lockfile 和 `--locked`,避免验证时静默改写依赖解析结果。 -- Core tests 并行执行时不共享持久化目录,结束后不在源码树留下 history/preferences/activity/style-pack 数据。 - -### M10:构建、打包、发布和文档收尾 - -**构建** - -1. 保留 macOS / Windows Tauri 构建和签名路径。 -2. 新建 Linux egui 构建 job,直接构建 `openless-linux-egui` binary。 -3. Linux job 不安装 `libwebkit2gtk`,只安装 eframe/winit 实际需要的 X11/Wayland/音频/图形依赖。 -4. Linux fcitx5 plugin 继续独立编译;主程序通过 host resource adapter 找到插件资源。 -5. 根据最终打包工具生成 deb、rpm、AppImage;打包器不能重新引入 Tauri。 -6. 产出独立的 Linux updater manifest、签名文件和 artifact 命名,避免与历史 Tauri Linux asset 混淆。 - -**工作流** - -1. 把现有 `.github/workflows/release-tauri.yml` 的 Linux matrix 从 Tauri build 中移出,或拆成独立 `release-linux-egui.yml`。 -2. macOS/Windows job 继续使用 Tauri cache 和 `src-tauri` manifest。 -3. Android job 继续使用 Tauri mobile manifest。 -4. Linux job 使用 Linux package manifest、独立 cache key 和独立 artifact path。 -5. 更新 release notes、artifact 校验、updater endpoint 和安装说明。 -6. 增加发布后验证:ELF 依赖、AppImage 内容、fcitx5 plugin 路径、桌面文件、单实例和 updater manifest。 - -**平台 runner 验证步骤(发布前必须逐项留证)** - -| runner | 执行顺序 | 必须保存的证据 | 不能用来替代的证据 | -| --- | --- | --- | --- | -| Ubuntu 22.04 真实桌面 | 安装 X11/Wayland、PipeWire/ALSA、DBus、Secret Service、fcitx5 和打包工具;运行 Core/Linux contract;在真实登录会话中验证焦点输入、物理热键/translation 顺序、真实麦克风 start/stop、设置 effect、单实例转发/退出;再安装 deb/rpm/AppImage 做启动、升级、卸载 smoke | runner 日志、输入/音频设备信息、安装前后版本、包清单、ELF `ldd`、AppStream、签名和 updater SHA-256 | WSL 合成 DBus signal、无音频设备时的错误分类、临时 minisign、Windows Linux crate test | -| Windows | 先跑 frontend/Core/Tauri 全量门禁;再构建 MSVC Tauri artifact,执行 installer、启动/退出、真实 IME/插入、麦克风权限和 updater smoke;保留安装包、日志和校验值 | `cargo test --locked --manifest-path "src-tauri/Cargo.toml" --lib` 结果、artifact/installer、启动与升级日志、签名状态 | `cargo check` 或 Linux crate cross-platform contract 不能证明安装和原生输入 | -| macOS | 使用对应 SDK/Metal/ speech entitlement 构建 Tauri bundle;验证签名/notarization(如发布要求)、安装启动、NSPanel/Space、麦克风/插入和 updater;再运行 macOS 专属 contract | bundle/DMG、签名与 notarization 输出、真实窗口/输入日志、升级前后版本 | Windows 本地 Tauri test 或跨 target compile 不能证明 macOS native behavior | -| Android | 准备完整 JDK/SDK/NDK/Gradle cache;执行 `copy:android-scaffolding` 和全部 manifest/dependency merge 脚本;运行 `cargo ndk -t arm64-v8a check --manifest-path "src-tauri/Cargo.toml"`、`cargo ndk -t x86_64 check --manifest-path "src-tauri/Cargo.toml"`;执行 `npm run tauri:android:build:debug`(Windows shell 使用 `npm.cmd`)与 release/APK 分 ABI 构建;运行 Gradle JVM/unit、instrumentation、设备安装和 JNI/overlay/IME smoke;最后执行签名和产物校验 | Rust target、Gradle/JVM、APK/AAB、instrumentation、设备安装、签名和每 ABI SHA-256 | Rust cross-target check 不能替代 Gradle/APK、设备运行或签名证明 | - -**文档** - -- 更新 `openless-all/README.md` 和 `README.zh.md` 的平台说明、开发命令和 Linux 安装说明。 -- 更新 `RELEASING.md`,区分 Tauri desktop、Android 和 Linux egui 发布流程。 -- 新增 core API / Linux contract 文档,写明线程、事件、错误、能力降级和版本兼容策略。 -- 删除“Linux 使用 Tauri”或“所有桌面平台共用 Tauri bundle”之类的过期描述。 - -**验收** - -- macOS/Windows Tauri、Android Tauri mobile 与 Linux 原生宿主使用互相独立且可重复的构建入口。 -- Linux 依赖树和最终 ELF/AppImage 中都没有 Tauri、WebKitGTK 或历史 WebView 运行时。 -- deb、rpm、AppImage、desktop/AppStream metadata、fcitx5 资源、updater manifest、签名和校验值均由真实 Ubuntu runner 证明。 -- 发布 job 在检测到 UI stub、缺失签名、ELF 依赖缺失或 contract version 不匹配时必须失败。 -- 发布说明明确区分“Windows 本地 contract 通过”“跨 target CI 编译通过”和“真实 Linux 安装/运行通过”,三者不能互相替代。 - -**回退原则** - -- 在 Linux 正式切换前保留最近一个已发布 Linux 产物和安装说明;不复用相同 artifact 名覆盖历史文件。 -- 单个领域迁移失败时回退该领域的 Adapter 接线,不回退已经稳定的 core Interface 或其他领域。 -- Tauri compatibility gate 失败时停止对应领域迁移;不得通过修改 React 调用方来掩盖无意的 IPC 破坏。 -- Linux 原生验证失败时停止 Linux 发布,不影响 macOS/Windows/Android 的独立发布流程。 - -## 9. Tauri 适配器的命令迁移模板 - -迁移后的 Tauri command 应接近以下形状: - -```rust -#[tauri::command] -async fn start_dictation( - backend: State<'_, Arc>, -) -> Result { - backend - .start_dictation() - .await - .map(SessionIdDto::from) - .map_err(CommandError::from) -} -``` - -不允许在 command wrapper 中: - -- 判断 provider 优先级、重试、session phase 或 fallback 逻辑; -- 直接访问 `Coordinator` 的私有字段; -- 直接修改 preferences/history/vocabulary; -- 根据窗口 label 决定核心业务状态; -- 捕获错误后返回“看起来成功”的空结果。 - -事件桥接应集中在一个模块: - -```rust -async fn forward_core_events( - backend: Arc, - app: AppHandle, -) { - let mut events = backend.subscribe(); - while let Some(event) = events.recv().await { - for mapped in map_event_for_react(event) { - let _ = app.emit_to(mapped.target, mapped.name, mapped.payload); - } - } -} -``` - -`map_event_for_react` 是兼容层,不是业务层;Linux adapter 不应复用它。 - -## 10. Linux egui 团队接口手册要求 - -交付给 egui 团队的文档必须包含以下内容,缺一项就不能认为接口准备完成: - -### 10.1 调用示例 - -- 构造 backend 的最小示例。 -- 读取 startup snapshot。 -- 非阻塞订阅事件并触发 egui repaint。 -- 调用 settings/history/dictation/style pack 等领域接口。 -- 取消运行中的 session。 -- 正常关闭和异常关闭。 - -### 10.2 字段契约 - -- 每个 DTO 的字段、单位、默认值和 nullable 语义。 -- 时间统一使用明确的毫秒/秒或 ISO-8601 规则。 -- 音量 level 的范围固定为 `0..=1`。 -- 流式 delta 的 session、sequence、offset 和最终态规则。 -- `PlatformCapabilities` 每个字段在 Linux 不可用时的含义。 - -### 10.3 失败契约 - -- 权限未授权、provider 未配置、fcitx5 不存在、插入失败、下载失败、取消和超时的错误码。 -- 哪些错误可重试,哪些错误需要用户操作。 -- 哪些操作是幂等的:dismiss、cancel、shutdown、set enabled 等。 -- outcome-unknown 时 UI 应等待 snapshot 或显示待确认状态,不能自行重复执行。 - -### 10.4 能力契约 - -至少覆盖: - -| 能力 | core 字段 | Linux 可能状态 | UI 应看到的行为 | -| --- | --- | --- | --- | -| 全局热键 | `supports_desktop_hotkey` | available / unavailable | 隐藏或显示降级设置 | -| fcitx5 插入 | insertion capability | plugin missing / ready | 失败时提供 clipboard fallback | -| 托盘 | `supports_tray` | desktop / unavailable | 提供主窗口内替代入口 | -| 悬浮反馈 | host action | X11 / Wayland limitation | 不把窗口显示失败当作听写失败 | -| 本地 ASR | `supports_local_asr` | model absent / ready | 显示下载、准备和释放状态 | -| 自动更新 | `supports_auto_update` | package-dependent | 不显示假更新按钮 | -| 麦克风 | permission + device status | granted / denied / no device | 明确区分权限和设备 | - -## 11. 风险与对策 - -| 风险 | 影响 | 对策 | 责任 | -| --- | --- | --- | --- | -| 搬迁时把 Tauri 类型带进 core | Linux 仍无法独立编译 | 依赖 grep 门禁;core 禁止 Tauri import | core 负责人 | -| 两套 UI 演化出两份业务规则 | 行为不一致、修复重复 | 业务判断只进 core;adapter 只翻译 | 全部 | -| 事件丢失或顺序错乱 | egui 显示旧状态、重复插入 | sequence + session + snapshot resync + lagged 测试 | core 负责人 | -| egui frame 被网络/磁盘阻塞 | Linux UI 卡死 | 事件 channel + 非阻塞 drain + repaint | Linux host / egui 团队 | -| Tauri command 仍包含业务逻辑 | Tauri 与 Linux 结果不同 | command contract review;wrapper 禁止业务分支 | Tauri 负责人 | -| Provider 验证/模型列表回流 Tauri | Linux 设置页无法复用 provider 管理面,凭据/协议出现第二份真相 | `ProviderService` 已迁入 Core;Core/Tauri/Linux contract + source contract;factory 断言非 `Unsupported` | core / Tauri / Linux host | -| Linux fcitx5 缺失 | 无法插入文字 | 明确 capability;clipboard fallback;不假成功 | Linux host | -| Wayland overlay/点击穿透限制 | 胶囊体验不完整 | 单独记录支持矩阵;不把 UI 反馈失败升级为 pipeline 失败 | Linux host / egui 团队 | -| 多 lockfile 发生依赖漂移 | 两个宿主可能使用不同传递版本 | 三份 lockfile 分别提交;CI 逐项使用 `--locked`,core Interface 由 path version + contract tests 约束 | 构建负责人 | -| Android cfg 被误删 | APK 回归 | Android job 和 Tauri mobile compile gate 保留 | Tauri 负责人 | -| 迁移测试仍 path include | 测试通过但实际 package 不可用 | backend-tests 直接依赖 core;删除 Tauri stub | 测试负责人 | -| secrets 泄露到 DTO/日志 | 安全事故 | status/value 分离;日志扫描和 fixture 检查 | core 负责人 | -| 大模型加载和下载被重复初始化 | 内存和启动时间回归 | backend 统一 runtime/cache 生命周期;增加资源计数测试 | core 负责人 | -| Core 私自创建 Tokio runtime | Linux egui 关闭/取消路径可能启动隐藏线程,生命周期和错误不可控 | 生产路径禁止 `Runtime::new()` fallback;实时 ASR 后台任务与关闭清理由宿主注入 `TaskSpawner` 提交;`scripts/check-core-runtime-seam.ps1` 扫描 `tokio::spawn`/`Handle::current`/`Runtime::new`,并在 CI 与 Linux release workflow 执行 | core 负责人 | -| 重复 session 覆盖已固定 provider | 返回 `Busy` 后原会话被错误接管 | registry 使用 entry/原子占位;回归测试验证旧路由仍可 finish/cancel | core 负责人 | -| fcitx5 translation modifier 信号晚于 dictation press | Linux 翻译模式与旧产品语义不一致 | Ubuntu 记录真实顺序;冻结关联窗口和时间线测试;不修改活动 session | Linux host 负责人 | - -## 12. 验收标准 - -### 12.1 架构验收 - -- [x] core package 的源码和依赖树没有 Tauri、egui、eframe、WebView 类型。 -- [x] Tauri 是 adapter,不再是 core 的隐式运行时;Core 只经显式依赖注入与 `TaskSpawner` 运行,Tauri command/source contract 不再保留已迁移领域的第二份业务实现。 -- [x] `Coordinator` 不持有或接收 `AppHandle`/`WebviewWindow`,不直接 emit Tauri 事件;源码契约与残余引用检查已覆盖该规则。 -- [x] core 事件不包含窗口 label 和前端事件名。 -- [x] Linux package 不通过 path include 复用 Tauri 源码。 -- [x] Android 仍能通过现有 Tauri mobile compile gate;CI runner 已验证 `aarch64`/`x86_64` Rust target、Gradle scaffolding、JVM/instrumentation 和 Keystore contract。 -- [x] Core 生产异步路径不创建私有 Tokio runtime;实时 ASR 的后台任务和关闭清理由宿主注入 - `TaskSpawner` 提交,`check-core-runtime-seam.ps1` 已作为 no-private-runtime contract 在本地通过, - 并已加入 CI/Linux release workflow。 - -### 12.2 接口验收 - -- [x] `OpenLessBackend` 有构造、启动、快照、订阅、取消和关闭契约。 -- [x] 领域 Interface 覆盖现有 React IPC 的有效业务分组:运行时 provider 与 - `ProviderApi::validate/list_models` 均由 Core `ProviderService` 实现,Tauri/Linux factory 共用同一 - service;其余未注入实现显式返回 `Unsupported`。 -- [x] DTO serde 字段与现有 React IPC 的本地契约兼容;Local ASR 的 4 项 Tauri wire contract、Marketplace - host sink 2 项、OAuth wire 2 项、Selection Core 17 项、Selection Voice Core 13 项、QA Core 15 项、 - Tauri QA Adapter 4 项和 Remote Input Core 8 项已通过;QA/Remote/Selection Voice 的 React source、 - Remote WebSocket lifecycle、Less Computer replay contract 及完整 frontend 58 项已通过;原生 - 宿主行为证明由 12.4 的独立平台项约束,不与 DTO/serde 契约混算。 -- [x] 错误码稳定,默认错误/事件序列化 fixture 不包含秘密字段;后续领域仍需继续执行敏感信息扫描。 -- [x] event sequence、session guard、lagged resync 和终态唯一性有测试。 -- [x] egui 团队的 1.0.0 领域 Interface、完整 headless 示例、mock、fixture、能力矩阵和 contract - 文档已具备;设置/快捷键 DTO、携带 snapshot revision 的 validated transaction、Linux settings - runtime、Selection/Selection Voice 完整 headless 场景与 4 项 host contract 已进入移交基线, - 当前公共面门禁通过。该项只表示 Interface 移交完成,不包含 egui UI 或真实 Ubuntu 验收。 - -### 12.3 行为验收 - -- [x] core headless 测试覆盖听写成功、取消、ASR 失败、润色 fallback、插入 fallback 和 outcome-unknown。 -- [ ] Tauri React 主链路、设置、历史、词典、风格包和 provider 页面可用。 -- [x] Linux host 可通过 fake recorder/provider/inserter 调用同一 core Pipeline,且生产 factory 的 - provider validate/list_models 不经过 Tauri;Linux factory contract 已验证 provider service 非 - `Unsupported`。 - 真实 Linux 设备与桌面集成仍由 Ubuntu 原生门禁证明,egui 视觉由另一组验收。 -- [x] Linux fcitx5 可用/不可用、X11/Wayland、无托盘和无权限场景有 contract、fixture 与非 UI Adapter 测试。 - -### 12.4 构建和发布验收 - -- [x] Windows 本地 `cargo check -p openless-core`、`cargo check --manifest-path "src-tauri/Cargo.toml" --lib` 和 `cargo check -p openless-linux-egui --all-targets` 通过。 -- [x] core 与 Linux package 依赖检查无 Tauri/WebKitGTK。 -- [x] Tauri `cargo check --locked --lib` 在 legacy provider 副本清理后于当前工作树通过;既有/迁移期 - warning 不作为测试成功或跨平台原生证明。 -- [x] 历史 Windows 本地 Tauri `cargo test --locked --lib` 为 745 passed、0 failed、7 ignored;远端 - macOS CI 在 run 33408317390 运行 737 项,其中 730 passed、7 ignored。Provider - 旧 command 测试旁路已删除,解析与模型响应测试归入 Core `ProviderService`;旧“backend - compatibility 118 项”已由 Tauri 原 crate 测试取代,不再作为当前证据。 -- [x] 历史 Windows 本地门禁记录 frontend build/58 项 tests、Core 594 项 unit + 79 项领域 - contract、Linux crate 29 项 + 4 项 host contract;远端 CI 的最新数字以 1.2.2 的 - `openless-core` 596 unit、Linux crate 30 tests 和 4 项 host contract 为准。 -- [x] Core/Linux 严格 clippy、command/event baseline(196/30/29)、Core/Linux 依赖方向、secret - surface、test isolation、Linux public surface、workspace fmt、provider command source contract、 - headless example 与 tracked `git diff --check` 均在当前工作树重跑通过;Core/Tauri/Linux provider - 管理面 contract 已通过。 -- [x] Core runtime seam contract 已通过:生产源码不创建私有 `Runtime`,不直接调用 `tokio::spawn`; - 实时 ASR 的后台任务和关闭清理由宿主注入 `TaskSpawner` 提交,`check-core-runtime-seam.ps1` - 已加入 CI 与 Linux release workflow。 -- [x] CI artifact 门禁已通过:run 33408317390 的 Linux runner 生成 deb/rpm/AppImage、fcitx5 - plugin 和独立 `latest-linux-egui-x86_64.json`,验证 ELF/包内容/desktop/AppStream 及 - manifest SHA-256;artifact `openless-linux-egui-x86_64`(ID `9764249814`)可下载,空 - `release_tag` 的 `minisign` 为 `null`,不会被误当作正式签名。 -- [x] Core tests 使用每测试唯一且自动清理的临时目录;`check-core-test-isolation.ps1` 同时拒绝固定 crate-local `"data"` 和源码树运行残留。 -- [x] Local ASR command 接线后的 core contract 6 项、Tauri wire contract 4 项和 Tauri - `cargo check --lib` 通过;这只是定向证据,不能替代 Tauri 全量 tests 或其他平台证明。 -- [x] Marketplace/OAuth 的 core contract 17 项、core 严格 clippy、Tauri host sink 2 项、OAuth - wire 2 项和 Tauri `cargo check --lib` 已通过;本地全量门禁也已重跑。 -- [x] 前序 Selection Core contract 17 项、Selection Voice Core contract 13 项和 Tauri Selection - focused 22 项通过;Selection Voice Core/Tauri source contract 已覆盖业务边界;跨平台原生证明 - 仍未完成,不能把定向 contract - 当作最新全量门禁或全领域迁移完成。 -- [x] QA Core contract 15 项、Tauri QA Adapter 4 项与 Remote Input Core contract 8 项通过;QA/Remote - lagged resync、Remote secret wire、Remote WebSocket lifecycle、Less Computer replay 和共享 React - source contract 已由完整 frontend/Tauri suite 覆盖。跨平台原生证明仍未完成。 -- [x] 快捷键迁移增量的 Core shortcut 5 项与 Tauri hotkey 14 项定向测试通过。 -- [x] Core settings 成功/失败原子性、Linux validated 公共面与原生 effect substitute contract 已补; - 当前 fmt、Core/Linux clippy/test、frontend build/test、Tauri check/full test 与所有脚本门禁均通过。 - Android/macOS/Ubuntu 的真实 native effect 继续由对应未勾选项约束。 -- [x] Provider validation/model-list Core/Tauri/Linux contract 已通过,且 - `commands/providers.rs` 不再包含 provider 协议、凭据读取或 HTTP/WS 请求构造。 -- [ ] macOS/Windows Tauri artifact 的正式签名、完整安装/升级 smoke 仍未完成;run 33405500864 - 已构建 macOS arm64/x86_64 DMG 和 Windows x64 NSIS,Windows NSIS 安装/卸载与 IME smoke 已通过, - MSI 因 Beta.7 非数字版本按设计跳过。 -- [x] Linux egui deb/rpm/AppImage、fcitx5 plugin、ELF 依赖、desktop/AppStream metadata 和临时 - minisign 签名/验签已在 WSL Ubuntu 验证;正式 updater manifest 仍需 release workflow 注入正式 - secret 后验证,UI stub 和正式发布门禁仍保持未完成。 -- [x] README、RELEASING 和开发命令已区分 Tauri hosts 与 Linux egui host,并明确 UI stub 发布门禁。 -- [x] Android CI debug artifact gate 已通过:run 33405500972 上传四个 ABI debug APK, - `Collect split APKs` 校验每个 APK 只包含预期 ABI;这不包含 release 签名、设备运行或安装证明。 -- [ ] Android release 签名、设备安装/升级和 JNI/overlay/IME 真实 smoke;本机 `cargo ndk` 的 - `arm64-v8a` 与 `x86_64` Rust cross-target check 不能替代这些证明。 -- [ ] Ubuntu 真实桌面 runner 完成焦点输入、fcitx5 物理按键顺序、真实音频设备 start/stop、设置 - effect、单实例退出和安装后启动;WSL 合成 signal、无设备错误和临时签名均不能替代该证明。 -- [ ] Linux 正式签名密钥注入后的 updater manifest、artifact 校验和、安装/卸载与回滚验证。 - -## 13. M0 决策记录 - -以下事项已经冻结;若要改变,必须同步更新 contract version、fixtures 和两个宿主: - -1. `openless-core` 的 Interface 覆盖现有有效业务领域;egui UI 可以分阶段展示,但不能复制或绕过 core 规则。 -2. Linux deb/rpm 使用 `fpm`,AppImage 使用 `appimagetool`,产物由独立 workflow 生成;打包器不得引入 Tauri/WebKitGTK。 -3. runtime 由宿主提供,core 只依赖注入的 `TaskSpawner`;当前默认 Adapter 为 `TokioTaskSpawner`。 -4. `BackendEvent` 使用有界 `tokio::broadcast`,落后订阅者收到显式 `Lagged` 并从 snapshot/query 重同步。 -5. core 使用 `DictationStateSnapshot` 等语义名称;`CapsulePayload` 等旧名和窗口 payload 只存在于 Tauri compatibility Adapter。 -6. Linux tray/autostart/overlay/updater 都是 capability;不可用时 UI 隐藏或降级,不能伪造支持。 -7. `BACKEND_CONTRACT_VERSION` 独立管理 Interface 破坏性变更;应用发布版本仍由宿主产物共同决定。 -8. Linux 发布 workflow 在真实 egui 入口替换 stub 前不响应 tag;即使脚本能生成包,也不能把 stub 标为正式发布。 - -## 14. 参考资料 - -### 仓库内依据 - -- [`openless-all/app/src-tauri/Cargo.toml`](../openless-all/app/src-tauri/Cargo.toml) -- [`openless-all/app/src-tauri/src/lib.rs`](../openless-all/app/src-tauri/src/lib.rs) -- [`openless-all/app/src-tauri/src/coordinator.rs`](../openless-all/app/src-tauri/src/coordinator.rs) -- [`openless-all/app/src-tauri/src/commands/mod.rs`](../openless-all/app/src-tauri/src/commands/mod.rs) -- [`openless-all/app/src-tauri/src/types.rs`](../openless-all/app/src-tauri/src/types.rs) -- [`openless-all/app/src-tauri/backend-tests/tests/core_contract.rs`](../openless-all/app/src-tauri/backend-tests/tests/core_contract.rs) -- [`openless-all/app/crates/openless-core/src/provider_registry.rs`](../openless-all/app/crates/openless-core/src/provider_registry.rs) -- [`openless-all/app/linux-egui/src/runtime.rs`](../openless-all/app/linux-egui/src/runtime.rs) -- [`openless-all/app/src/lib/ipc/index.ts`](../openless-all/app/src/lib/ipc/index.ts) -- [`release-tauri.yml`](../.github/workflows/release-tauri.yml) - -### 稳定的上游资料 - -- [Tauri Architecture](https://v2.tauri.app/concept/architecture/) -- [Cargo Workspaces](https://doc.rust-lang.org/cargo/reference/workspaces.html) -- [eframe API](https://docs.rs/eframe/latest/eframe/) -- [egui API](https://docs.rs/egui/latest/egui/) - -这些资料只用于确认宿主/库的常见职责和 Cargo workspace 组织方式;项目最终接口以本文的仓库事实、contract tests 和实际构建结果为准。 - -## 附录 A:本次接口准备的实际交付 - -当前已落地、可供 egui 组开始 view model 和 headless 集成的内容: - -- `openless-core`:无 Tauri/egui 依赖的 facade、快照、错误码、语义事件、宿主 Interfaces、 - 听写状态机、共享 `PipelineDictationEngine`、16 kHz mono Int16LE PCM/WAV 契约、 - `start/finish/cancel` engine Interface、录音 level/阶段/增量 progress sink、非阻塞事件订阅 - 和取消/迟到结果/生命周期测试; -- 设置/快捷键:语法、左右修饰键、legacy 同步、跨功能冲突、strict/reconcile、preserve-style、 - revision guard、显式 effect plan、receipt 补偿和单写入 gate 已进入 core;完整设置 DTO 与 - `LinuxHost::save_settings`/`update_settings_strict` 已从 Linux contract 交付。Core settings 11 项、 - Linux 4 项公共 host contract 和本地全量门禁通过;真实原生 listener 事务仍由对应 runner 证明; -- 词典与 style-pack 诊断:`enabled_vocabulary_phrases`、`asr_vocabulary_phrases` 和 - `preview_style_pack_runtime` 均由 Core facade 提供;ASR 热词保底/命中排序/大小写去重及 - prompt 诊断不再由 Tauri Coordinator 复制,Tauri command 只做 Core/wire 转换; -- `openless-linux-egui`:只依赖 core 的 Linux host seam 和无 UI 的 - `examples/headless_host.rs`; -- `openless_core::testing`:记录 host action、fixture recorder/transcription/polisher/engine/inserter/ - selection 和固定结果/错误的 headless 测试替身;inserter 记录 session-scoped - prepare/insert/cancel 顺序,selection fixture 记录 capture/preview/apply/revert/cancel 并可表达 - Linux preview/revert `Unsupported`; -- `LinuxCapabilityFixture`:X11 完整、Wayland 降级和 headless 能力/权限快照; -- `scripts/check-core-deps.ps1`:core/Linux package 的禁止依赖门禁; -- `scripts/check-core-runtime-seam.ps1`:禁止 Core 生产路径创建私有 Tokio runtime 或直接 spawn, - 并确认后台任务经过宿主注入的 `TaskSpawner`; -- [`linux-egui-backend-contract.md`](./linux-egui-backend-contract.md):当前可用接口、 - 事件/错误/能力契约和未完成领域的明确边界; -- `BackendServices`:provider/local ASR/selection/QA/remote input/marketplace/ - coding-agent/platform/auxiliary 的稳定 Interface 与 DTO;未注入 Adapter 时统一失败为 - `Unsupported`;`AuxiliaryApi` 额外交付单轮 repolish、规范 PCM retranscription、实际 ASR - provider/model 归因、terminal Foundry fallback 和 future-drop cancel 契约; -- Provider 管理面已完成迁移:云端 provider 运行时及 - `ProviderApi::validate/list_models` 均由 Core `ProviderService` 共享实现承载,Tauri - `commands/providers.rs` 只做参数/旧 wire/error 转换,Linux 生产 factory 通过 - `from_shared_providers` 注入同一 service;`ProviderTransport`、fake transport 覆盖、静态模型 - parity 与 LLM 显式 channel 写入均已有回归测试。egui 可以直接调用公开 provider Interface;真实 - 网络、keyring 和平台 runner 仍须按 M9/M10 留证,不得用 fixture 冒充生产能力。 -- Coding Agent:provider/model/权限/预算/路径/风险/版本/MCP 解析等跨宿主规则位于 core; - Tauri 使用真实 `TauriCodingAgentApi` 处理 CLI 进程、Git 快照、临时 guard 配置、审批和 typed - event 转发,commands 只保留主窗口授权与旧 React wire 转换;Linux 可直接复用同一 Interface, - 未提供进程 Adapter 时稳定返回 `Unsupported`; -- Less Computer 语音生命周期:Core 提供实例级 capture lease、active session、取消可见性和 - 幂等 abort;Tauri 热键按下先预留 lease,再以同一 session id 驱动兼容 recorder/ASR,转录后 - 通过 `submit_less_computer_with_session` 进入 Core Agent run;Esc/启动失败/空转写不会遗留 - capture lease。egui 只消费这些 facade 与 typed events,不读取 Coordinator 状态; -- Local ASR:Generic/Foundry/Sherpa 的 catalog、设置事务、生命周期 Interface、engine-changed - 事件语义与共享 `LocalAsrService` 位于 core;Tauri 使用 `LocalAsrRuntimeAdapter` 承担原生引擎、 - 下载和文件操作,三组 command 只保留旧参数/DTO 转换,Generic 下载已通过 typed event 进入 - 集中桥接,未知 Sherpa family/mode 使用 fallible conversion 返回错误;Adapter 直接注入共享 - preferences repository 与 native cache,不再通过 `AppHandle` 回取 Coordinator;Coordinator 的 - ASR 就绪门禁也消费同一 Core 偏好快照,不再重新打开第二份 preferences store; -- Marketplace:完整 `MarketplaceApi`、HTTP/认证策略、归档大小与 ZIP 校验、安装事务、 - upload/origin 写回、实例级 OAuth device-flow registry、401 tombstone 和 secret-surface 规则已 - 进入 core;Tauri Marketplace/OAuth commands 只保留旧 wire/error 转换与归档最终写入;Linux - 生产 factory 已接线 Marketplace,`LinuxHost::download_marketplace_archive` 以 create-new 语义把 - 已校验归档写入绝对 filesystem path,拒绝覆盖并清理失败写入; -- Selection:Core Implementation 已拥有 preview/confirm/direct apply/cancel/revert 状态、typed - event、provider/context 冻结、history/vocabulary 写入、迟到结果与 outcome-unknown 语义;17 项 - Selection contract 与 13 项 Selection Voice contract 当前通过。fixture、headless 示例和 Linux - host contract 已覆盖 preview/confirm/cancel/stale/outcome-unknown 以及 Linux preview/revert - `Unsupported`。Tauri 已注入新的 runtime 和共享 polisher,旧 `TauriSelectionApi`/Coordinator - wrapper 已删除;Selection Voice 的 correction/instruction/intent/output-mode/EditPlan/translation - 和 QA preview revision 已由 Core 高层 use-case 统一,Tauri 只保留 native recorder/window/hotkey、 - opaque insertion target 与 apply outcome;跨平台原生验证仍待收口; -- QA:`QaService` 已拥有 message log、phase、text/voice turn、selection envelope、level/delta、 - approval token、cancel/dismiss、错误脱敏、`ShowQa` 失败回滚和 shutdown 语义,15 项 contract 通过;生产构造已注入 - `TauriQaRuntimeAdapter`,QA hotkey/commands/dismiss 与 Selection Voice 问答/编辑预览均调用同一 - `QaApi`,独立 `QaHostState` 已删除,Coordinator/QA Adapter 共享一个窄 `TauriQaHostContext`, - Tauri QA Adapter 4 项通过;Less Computer 已实现 listener-first replay、同步期 pending - 合并、sequence 去重与截断重建;原生平台证明仍待收口; -- Remote Input:`RemoteInputService` 已拥有配置、PIN、locale、连接/session、PCM 校验、事件与 - shutdown,8 项 contract 通过;Tauri TLS/WSS/PIN 文件与 external dictation 位于 runtime Adapter, - Coordinator 已删除重复状态;WebSocket contract 覆盖认证顺序、constant-time PIN、单 stream、 - disconnect/restart cancel 和 stale lease,`RecordingRemoteInputRuntime` 可供 headless 测试;真实 - WSS/证书/防火墙原生网络证明仍待补; -- Linux Adapter:Secret Service/keyring 凭据、非秘密 metadata、资源布局、fcitx5 DBus 与插件 - 安装契约、X11/Wayland/headless 能力、cpal 设备枚举与录音、DBus 热键 listener、 - `LinuxBackendBuilder`、HostActions 和 Unix socket 单实例 intent 转发;新增 3 个显式 ignored - native contract,已在 WSL Ubuntu 分别验证 Secret Service set/read/remove、fcitx5 plugin/method/ - listener/signal 映射和 cpal 无设备错误分类;无焦点输入时 fcitx5 plugin 不再抛异常导致宿主崩溃; -- `src-tauri` 已添加对 core 的 path dependency 并复用同一组 repository;云 ASR/LLM/Omni、 - Auxiliary、QA provider 运行时及 provider validation/model-list 管理面均改用 Core 共享实现; - Tauri provider commands 只保留旧参数/DTO/error 转换。Tauri 仍提供 - `SystemCredentialStore`、平台录音、native/local ASR、窗口/插入与 runtime;旧 Coordinator 仍按 - M4–M6 收窄,原 12 个 `migrationRequired` 事件已全部进入集中桥接; - React command、CLI、Android JNI、remote PCM 和桌面普通听写的主要热键边沿已切 core;QA/shortcut/combo/ - debounce 等宿主仲裁仍留在 Tauri。Less Computer 文字入口、capture lease、同 session submit/cancel - 已接线;其语音按下/松开、Starting pending stop 和静音自动停止仍使用 Coordinator 的兼容 host - session 状态,但该状态不再承载 Agent 业务规则,也不能被 Linux egui 读取。`Coordinator::Inner` 与 `capsule_focus` 已恢复 module 私有, - `bind_app(AppHandle)` 已删除,Coordinator/capsule 子模块不再出现 `AppHandle`、`WebviewWindow`、 - 直接 emit 或直接 `tauri::async_runtime`;capsule 原生窗口操作和 layout/cursor/style/fallback/ - deferred cache 已移入 `TauriCoordinatorHost`,payload 应用只接收窄值。compatibility Coordinator - 仍持有显式 Host,并承担部分热键仲裁、native runtime 生命周期和兼容编排,需要继续按生产调用图 - 收窄;`core_adapters.rs` 已无 `managed_coordinator` 反向查询,hotkey/QA 状态通过窄共享依赖注入; -- core facade 在插入、结果、history 和 activity 之前统一应用启用的最终纠正规则;traditional - history 使用冻结的 ASR/LLM channel 与 model,multimodal history 清空 ASR 归因并记录冻结的 - Omni channel/model; -- provider router 明确区分 `provider_id`(channel/scoped credential)与 `provider_type` - (协议路由),并冻结 session 的 provider ID/type/model;Tauri 生产 Adapter 的重复 session - 使用原子占位,不会在返回 `Busy` 时覆盖原取消路由。 -- Linux 生产 UI 只调用 `LinuxBackendBuilder::from_shared_providers(config)`;Core 共享 - ASR/LLM/Omni/Auxiliary、ProviderService、Linux credentials、cpal recorder、fcitx5 inserter 与 - settings runtime 由 factory 内部组装。`LinuxBackendBuilder::new(...)` 的显式 provider 注入只用于 - 测试/特殊宿主;仅 native/local ASR 等尚未提供 runtime 的能力可以返回 `Unsupported`。 - -补充本地与远端全量证据:历史 Windows 本地记录 frontend build 与 58 项 frontend/contract tests、 -Core 594 项 unit、Linux Adapter 29 项 crate tests + 4 项 host contract、Tauri 745 -passed/0 failed/7 ignored;最新 fork CI run 33408317390(head `06e85f7b`)记录 Core 596 unit、 -Linux Adapter 30 tests + 4 host contract、macOS Tauri 730 passed/0 failed/7 ignored,并通过 -workspace fmt、Core/Linux 严格 clippy、测试隔离、公共接口、command/event baseline(196/30/29)、 -依赖方向、secret surface、source contract、headless example、Provider command 禁回流 contract -与 tracked `git diff --check`。已删除 path-include suite 的 118 项数字不再作为证据。 -Windows 上的 Linux package test 只证明跨平台 Rust contract,不证明 DBus/Secret -Service/cpal/fcitx5 的真实 Linux 行为;Android、macOS、Ubuntu 打包与原生集成必须由对应 -runner 证明,不能从本机结果推断。 - -### 2.0.0-Beta.1 版本与许可证边界 - -本批 Tauri 应用版本统一为 `2.0.0-Beta.1`;`BACKEND_CONTRACT_VERSION` 已升级为 -`2.0.0`,应用版本和接口版本仍各自独立。根项目从该版本起采用 `AGPL-3.0-only`,已发布 1.x 版本仍保持 MIT, -第三方 vendor 文件保留其原始 MIT/Apache/LGPL 条款。Less Computer 语音 session、实时 -`TranscriptDelta` 和 Linux 三档热键事件已进入 Core/Host contract;真实设备、签名和 UI -验收继续按 M8–M10 单独取证。 diff --git a/docs/plans/active/2026-09-06-core-2-ux.md b/docs/plans/active/2026-09-06-core-2-ux.md deleted file mode 100644 index a32e669b..00000000 --- a/docs/plans/active/2026-09-06-core-2-ux.md +++ /dev/null @@ -1,67 +0,0 @@ -# Core 2.0 入口、设置与 Linux 界面整理 - -状态:historical;阶段:implementation-backed;更新:2026-09-07;任务状态:complete。 - -## 意图基线 - -用户要求用云端最新已合并主线替换本地内容,删除未提交修改;梳理 2.0 并提出改进建议,落实更直觉的入口、设置与页面布局,为 Linux 增加引导和设置准备。 - -- 应用仓库:`1-app`,远端 `Open-Less/openless`。官网、市场后台、训练与审计项目独立,不在本次修改范围。 -- 2026-09-06 实时 fetch:默认主线 `beta`,Core 2.0 在 #1019 合并,基线 `fc38edd1a3da3a4f2fa56f433be84278c3456ed1`;稳定 `main` 是 `08d70601`(1.3.18)。代码版本 `2.0.0-Beta.1`。 -- 已按授权 reset/clean 应用工作区,并切到 `feat/core-2-ux-linux`。 - -## 目标、非目标与约束 - -主页提供可操作的开始入口;设置按用户任务拆分,保留保存、平台能力和原有功能;Linux 在现有 egui 页面中增加导航和环境准备引导。梳理整个 2.0 迁移面,以界面改动为实施重点。 - -不重写共享业务、不扩展公开 IPC、不变更发布版本、不实现完整 Linux 原生适配、不操作其他项目或生产配置。使用现有 React/TypeScript/i18next、Tauri 2、Rust Core 与 egui/eframe 0.31;不增加依赖。Linux 已有功能、待接线功能、设备待验收分别表达。 - -## Read Set - -- 本仓库唯一 `AGENTS.md`(本地入口)及 `docs/2.0-requirements.md`、`2.0-desktop-acceptance.md`、`core-platform-boundary-audit.md`。 -- `docs/linux-egui-handoff/README.md`、`02-gap-register.md`、`04-ui-domains.md`;其他交接小文档按需要读取。 -- `openless-all/app/src/components/{FloatingShell,SettingsModal,Onboarding}.tsx`、`src/pages/Overview.tsx`、`src/pages/settings/`、平台与 IPC 门面。 -- `openless-all/app/crates/openless-core/src/{lib,api,ports,events}.rs` 的公开结构;合同与测试;不逐行递归阅读全部旧迁移记录。 -- `openless-all/app/linux-egui/src/{main,capabilities,settings}.rs`、已有 Host 接线;包清单与测试入口。 - -## 方案与影响范围 - -1. React 主页将下一步和真实配置状态前置,服务卡片可直接进入设置;短窗口保留自然滚动。 -2. 设置保留稳定 ID general/services/privacy/advanced/about,增加 shortcuts、appearance,将长通用页按任务拆分。添加说明、可搜索的分类入口、键盘关闭和焦点返回;正确挂载/卸载原有区块。 -3. Linux 复用已有功能,按开始/听写/AI 服务/模型/工具/手机输入/历史/设置组织页面;环境指导使用现有能力信息,提供实际可执行的准备步骤,保留取消、审批和后台事件消费。 -4. 补文档入口与 2.0 架构/体验梳理,记录建议、落实项、Linux 剩余工作和验证范围。 - -## 里程碑 - -- [x] 实时核实并同步云端基线,清理本地改动。 -- [x] 核对 2.0 范围、平台边界、现有页面与验证方式。 -- [x] 桌面主页入口和概览布局改进。 -- [x] 设置分类、查找、布局及可访问性改进。 -- [x] Linux 原生导航与环境引导。 -- [x] 补齐中英及现有语言文案,文档与代码同步。 -- [x] 类型/构建、前端测试、可行 Rust 验证、浏览器桌面/窄屏检查、最终 diff 审阅。 - -## 用户追加的视觉改进(同一任务) - -用户在预览后指出 LLM 设置元素不好看、排布不清楚,模型设置和下载也有同样问题。这是本次前端目标的继续细化。 - -- [x] 设置改为完全实底,消除透底文字干扰;AI 服务按语言模型/语音识别/本地模型/连接分类,Omni 模式保留真实配置入口。 -- [x] 渠道列表明确名称、模型、使用状态和验证结果,整理添加/编辑/启停操作;编辑表单分组。 -- [x] 本地模型管理与下载目录减少嵌套卡片,完善空态、主操作和窄屏排布。 -- [x] 浏览器检查 LLM、编辑页、模型管理与下载页,并对最终代码重新构建/测试。 - -第一轮已通过前端构建及71个测试模块;990像素进入服务页时的空目录布局更新循环已复现并修复;历史重润色结果从宽窗缩至990像素已验证保留。Linux Host50+合同4、macOS临时UI9及追加Omni2项测试通过;原生Linux目标编译仍缺环境,不作通过声明。最终结果以追加视觉改进后的收口记录为准。 - -## 验收与回滚 - -主页服务入口能打开对应设置;录音/快捷键/外观/服务/隐私/高级/关于均可达;移动端没有丢失录音或语言设置;查找与空结果可恢复;设置支持关闭和焦点返回。Linux 每个旧操作保留真实调用,导航不停止事件消费或丢弃会话。 - -使用 `npm test`(含构建)、必要的定向行为测试;Linux 源文件格式检查、Host/Core 测试及能执行的目标检查;浏览器检查常规桌面、短窗口与窄屏。当前主机是 macOS,Linux 原生桌面效果须明确留给 Linux 设备验收,不能以 macOS 上 cfg 排除的构建冒充。 - -改动均保持在此分支,按文件撤销可回到 `fc38edd1`;已删除的用户未提交修改不作可恢复承诺。风险集中在设置入口迁移、短窗口溢出、平台 gating 和 Linux 隐藏页面的会话状态。 - -## 最终收口 - -追加视觉改进已实现并完成浏览器检查。最终 `npm test` 构建及72个测试模块通过;Core公开目录生成器编译运行通过。渠道编辑焦点、供应商下拉可访问性、快捷键菜单Escape、保守布局、历史缩窗结果保留与空模型目录白屏均已关闭。预览目录由Core生成,原生IPC合同不变。 - -验证详情、设备边界与下一轮建议统一见[体验梳理](../../2.0-ux-review.md#验证结果2026-09-07-收口)。原生Linux编译/真机与真实服务验收仍在已有交接清单内;本任务没有宣称完成这些产品发布门。 diff --git a/docs/pr1019-2.0-final-review.md b/docs/pr1019-2.0-final-review.md deleted file mode 100644 index 1774c14a..00000000 --- a/docs/pr1019-2.0-final-review.md +++ /dev/null @@ -1,90 +0,0 @@ -# PR #1019:2.0 合并前完整复核与修复记录 - -> 更新范围后的新团队复审另见[桌面复审闭环](./pr1019-desktop-review-2026-09-06.md)。其中R29起的问题覆盖此前审核未发现的生产路径;不能用本文旧轮次的零未关闭项代替最新结论。 - -> **2026-09-06需求调整**:本文保留此前问题/修复/测试证据;当前完成条件以[2.0需求](./2.0-requirements.md)为准。Windows/macOS完整保留各自1.x功能;Linux本批交付Core和[拆分交接资料](./linux-egui-handoff/README.md)。辅助全局热键等Linux剩余Host/UI缺口已列入[L01–L12](./linux-egui-handoff/02-gap-register.md)交egui团队,不再单独阻塞本批桌面2.0;本记录不能作为“Linux所有功能已完整”的证明。 - -本轮从 `2f961caec5d749ed444a0fa3dc24ef136a58f9a5` 开始,比较点为 -`beta` / `fc9824eeccf7218e3b44c1de6c6e284a4c494c81`;merge-base 等于该 base。 -平台优先级:Windows、macOS、Linux;同时覆盖 Android 条件编译和共享合同。 -本文件记录新发现、复现、修复及验证。此前 CI 成功是基线证据,不替代本轮行为复核。 - -## 问题登记 - -| 编号 | 优先级 / 平台 | 触发与证据 | 状态 | -| --- | --- | --- | --- | -| R01 | P1 / Windows | TSF Rejected/Failed 被映射为 CopiedFallback,却没有复制,因而跳过新 Adapter 的失败回退。 | 已修复;真实 Failed 与 OutcomeUnknown 分离,Windows IME 7 项通过 | -| R02 | P2 / 所有录音 Host | RecordingPlan 错读 history_max_entries,忽略独立 audio_recording_max_entries。 | 已修复;100/7 独立上限用例先红后绿 | -| R03 | P2 / 共享凭据事务 | CredentialDirectory 异步 load → mutate → save 无共享锁,并发创建得到相同渠道 ID;Tauri 的 revision CAS 会拒绝冲突保存,避免静默丢数据,但普通并发操作仍会失败,弱存储实现则会覆盖。 | 已修复;克隆共享事务锁,并发创建/重命名/激活回归通过;Tauri 持久层 CAS 保留 | -| R04 | P1 / 本地 ASR、跨平台 | activation/目录迁移在 await 前保存整份 preferences,随后提交/补偿覆盖期间编辑;排队 relocation 又使用锁外旧根。独立复核还发现旧渠道补偿会撤销准备期间的新选择。 | 已修复;原子更新所拥有字段、条件补偿、锁内读取根;全部可失败native操作在渠道最终提交之前;Local ASR 22 项通过 | -| R05 | P1 / Linux Less Computer | 第二轮 session 更新被 fresh=false 阻止,输出和审批全部丢弃。 | 已修复;每轮 User 接管 session,fresh 仅清历史;WSL 两轮回归通过 | -| R06 | P2 / Linux Provider 状态 | status 忽略 multimodal 总开关,和真实 pipeline 不一致。 | 已修复;复用 effective_pipeline_mode,开关组合回归通过 | -| R07 | P1 / Windows、macOS 本地 ASR | Generic prepare 和非 Generic preload 固定 Unsupported;Generic preload 读取旧 provider/model。 | 已修复;显式 target/provider type,核验真实 loaded 模型;生产 Tauri Adapter 回归通过 | -| R08 | P1 / Native ASR | 丢失动态超时和错误/取消释放;共享代次还可能遗留旧引擎或卸载新模型。 | 已修复;Core deadline、各 runtime 代次及锁内复验、Arc/Weak 所有权;边界/释放测试通过 | -| R09 | P2 / Windows 流式 | CR 被吞掉后计数减少,正常 CRLF 被 Core 误判部分写入。 | 已修复;已消费源前缀计数;Unicode 9 项和 insertion 11 项通过 | -| R10 | P2 / macOS 普通听写 | 非流式也强制切 ABC,TIS 错误阻止一次性听写。 | 已修复;只准备真实流式,失败回执由 Core降级一次性交付;Core回归通过,原生 TIS 待设备验证 | -| R11 | P1 / Linux 热键 | 启动未同步热键;已有 Less Computer UI 开关却被 settings 拒绝。 | 已修复;启动同步/插件重启重载、启用绑定与禁用解绑;Host contract通过 | -| R12 | P1 / 所有 Remote Input Host | stop 锁/同步等待阻止取消;Linux 丢 terminal;Tauri 全局 result 广播会发送本机/其它连接的听写。 | 已修复;保留可取消 session、异步 stop、逐连接事件隔离和迟到事件保护;Core 12 项及 Tauri Remote 4 项通过 | -| R13 | P2 / Linux QA | delta 覆盖全部状态,历史消失且仅显示最后一个 chunk。 | 已修复;按事件类型合并、终态以全量消息校准;WSL 增量回归通过 | -| R14 | P1 / Linux 普通落字 | 插件按其它按键更新全局 savedIc,听写可跨应用误写。 | 已修复;session ticket 冻结原 IC;真实 C++ InputContext 契约通过 | -| R15 | P1 / Linux Selection | PRIMARY 残留被当作原选区有效证明,光标移动后仍可能错误插入。 | 已修复;严格核对原 IC、文本、cursor/anchor;Debug/Release C++ 契约通过 | -| R16 | P1 / 共享普通听写启动 | AX/凭据捕获期间已有 lease 却无公开 session,Esc 无法取消,慢调用返回后继续录音。 | 已修复;首次 await 前公开 Starting;确定性取消回归及 Core API 73 项通过 | -| R17 | P1 / ASR 静默重试 | retry 的 provider start await 后未复验取消,Esc 只清旧 ASR,新返回的重试 ASR 仍接收 PCM/finish且失去取消路由。 | 已修复;资源注册与once标记同锁,迟到资源直接取消且不送PCM;15项Pipeline测试通过 | -| R18 | P1 / QA 启动 | Linux安装capture后没有arm/取消复验;两Host文本prepare在上下文await后才注册,取消期间无资源可清,迟到注册可泄漏旧session。 | 已修复;await前登记owner,返回后身份复验,Linux arm;两Host Adapter回归通过 | -| R19 | P1 / QA 转写取消 | Host take唯一capture后,Core finish关闭handle并移动transcription,Esc不能取消正在finish的ASR。 | 已修复;共享capture保持Finishing阶段取消句柄,ASR恰取消一次回归通过 | -| R20 | P1 / QA 静音停止 | 延迟静音任务调用无session限制的toggle,可能在旧轮取消后误开新录音或停止下一轮。 | 已修复;stop_recording按每轮token原子校验与转换,21项QA contract通过 | -| R21 | P2 / 手机默认操作模式 | 桌面Remote设置可选默认hold,但H5无已保存模式时固定toggle,设置没有生产消费者。 | 已修复;两Host渲染白名单默认值,手机显式偏好优先;7种组合及音频队列回归通过 | -| R22 | P1 / Linux 原生打包兼容性 | runner的Fcitx版本不支持复制/移动SurroundingText,保存该对象快照导致插件和CTest目标编译失败;WSL较新库未暴露差异。 | 已改为自有普通值快照;5.0.14旧头文件复现后构建通过,5.1.19本机Release CTest通过;完整22.04制品待新CI | -| R23 | P1 / Selection Voice 转写取消 | 与QA相同的take/finish(self)所有权断点导致转写阶段Esc无法取消ASR。 | 已修复;复用共享capture控制,原session限定Stop/Cancel及迟到启动/终态保护通过 | -| R24 | P1 / Windows TSF | 迁移删去native timeout/cancel HRESULT的OutcomeUnknown分类;原生已进入COM写入时超时仍可能迟到提交,随后SendInput回退会双写。 | 已恢复两种HRESULT分类及session/protocol响应验证;IPC10项通过 | -| R25 | P1 / 前台应用快照 | cursor开关错误地同时禁止无需AX的front-app查询,默认关闭丢历史应用/按应用润色/macOS Terminal自动LF。 | 已恢复capture(false)前台查询,禁用时丢弃文档文本;Terminal换行及历史归属回归通过 | -| R26 | P1 / CLI取消 | 两Host在启动取消/输出drain前等待stdin写完;不读stdin的大prompt阻断Esc,Tauri PATH查询亦有取消盲区。 | 已统一stdin/drain/wait/PATH/快照取消;Windows suspended+Job,1/4MiB、孤孙、future-drop、自然成功回归通过 | -| R27 | P1 / Linux渠道删除 | 删除仅移除metadata,保留SecretService和keys;同厂商ID重建时继承已删除账号的密钥。 | 已按namespace+channel清理;部分删除失败保持可重试,未提交孤立secret不可读;8项凭据回归通过 | -| R28 | P1 / Linux 1.x凭据迁移 | 新SecretService命名空间没有读取旧Tauri service/分片/legacy文件;升级后原渠道与密钥丢失。 | 已幂等导入旧JSON/分片/平铺账号,保留来源;新配置优先、失败重试、home_dir=None零迁移访问;旧格式4项及Linux cfg验证通过 | - -## 本轮覆盖范围 - -- Windows:启动、IPC/热键入口、录音、TSF/SendInput/Clipboard、目标恢复、Foundry/Sherpa、本地模型、凭据和安装包。 -- macOS:启动、全局热键、焦点/AX/隐私、输入源/换行/流式、MLX/Whisper/Apple Speech、进程与打包。 -- Linux:生产 factory、egui UI、Provider/凭据、本地模型、QA/Selection/Remote、fcitx5、事件重放、打包。 -- Core:所有领域入口、会话/取消/唯一终态、凭据/偏好事务、模型迁移、ASR/LLM、Agent、历史与录音保留。 -- 合同与仓库:97 个 UserPreferences 字段消费者、2.0 Rust/TS/JNI/Linux DTO、条件编译、锁文件、CI、发布脚本和迁移文档。 - -## 验证记录 - -修复前建立能失败的针对性行为验证;修复后执行相关测试,收尾执行完整本地检查及最终提交的远端 CI。 -本次 Windows 本地已完成:前端 build + 66 项;Core 705 passed / 1 ignored,contract 5、Local ASR 21、Marketplace 17、Prompt 3、QA 19、Remote 12、Selection 15、Selection Voice 15 全部通过;Core strict clippy通过。独立复核补修后,Local ASR 包含新增渠道补偿回归的22项再次全部通过。 -Tauri library 422 项通过,Rust 1.88 check通过,独立 backend contract 1项及其1.88编译通过。 -Linux Windows侧 38 library + 4 host contract 及strict clippy通过。WSL 45 library + 2 UI + 4 host contract及strict clippy通过,3项真实设备测试ignored;C++真实InputContext Debug/Release契约通过,含目标销毁后两类ticket失效。首轮WSL增量编译因内存不足中止,改用 `CARGO_INCREMENTAL=0 CARGO_BUILD_JOBS=1` 后完整通过。 -Core/Linux依赖、command/event基线(199/30/30)、secret surface、测试隔离、runtime seam、Linux公共面、source contract、workspace fmt与diff门禁均通过。 -`remote:result` 在历史兼容基线中保留并标为 retired;H5 wire result 保持兼容,实际socket只消费归属于本连接的Core事件。 - -子代理顺序:初始专项事实核查 → 修复完成后一名独立复核 → 提交推送 → 两名独立全 PR 审核。 -Standards(规范/边界/重复策略)与 Spec(缺失/错误行为/兼容性)分别记录结论。 -最终远端提交、CI和推送后审核结果记录于原 PR #1019 描述,避免把历史run误标为本次证据。 - -提交前独立复核已通过:Standards无未关闭项;Spec发现的R04渠道补偿及R12取消后重开两项遗漏均已修复并复读确认。后者以真实Core+永不完成的Host stop复现,Linux Remote 5项及strict clippy通过;终态后迟到cancel保留清理future。 - -已排除的候选:设置页同步切换ASR与异步渠道保存的混合并发不会静默覆盖元数据;`apply_credential_metadata` 已在持久层锁内检查 revision CAS,旧快照明确失败。该保护通过继续追踪持久层确认,未为此添加第二套机制。 - -### 2026-09-06 最终全 PR 审查的追加修复 - -两名全 PR 审核者又发现 R17–R28;其中 R21 来自97项偏好消费者追踪,R22来自真实Ubuntu22.04打包失败。这些问题均已实施并由独立提交前审查复读通过(Standards/Spec无未关闭确定项)。 - -追加修复后的本地验证:Core **713 passed / 1 ignored**,8组contract全部通过(5/22/17/3/21/12/15/15);Tauri **429 passed**;Linux Windows-host **48 lib + 4 host**;前端build和66项全部通过。Core/Linux严格clippy、全部改动的rustfmt和架构/秘密/基线门禁通过。Rust1.88 Tauri check通过。 - -Windows进程回归使用真实子进程树,验证大stdin阻塞、启动后取消、父先退出、future drop和自然完成的区别;只对本次新建进程作Job控制。Fcitx旧头文件验证准确复现了5.0.14的编译错误,修复后旧头文件构建及本机5.1.19的CTest通过;完整Ubuntu22.04运行仍由最终CI确认。 - -Linux迁移明确依赖调用者提供的home_dir;None不读取真实HOME或系统旧vault。旧文件仅来自该目录,旧来源不删除,Secret Service暂不可用不阻断本地ASR但保持迁移未完成标记。测试/自定义工厂不再依靠cfg(test)判断是否可读系统凭据。 - -旧head `d4430fcf` 的上游CI和桌面构建成功,但fork Linux制品因R22失败;Android APK在Rust编译后因Maven Central Kotlin POM GET 403失败。它们不能计为追加修复的最终CI,最后提交会重新触发全部验证工作流。 - -## 真实环境证据边界 - -当前会话可验证源码、Windows/WSL 自动测试和远端各平台构建。实际 GUI 焦点、麦克风断连、 -TSF/AX/fcitx5 与目标应用交互、系统凭据服务、安装升级回滚及正式签名/公证,需要相应设备证据。 -未完成的设备验证将逐项保留,不能因 CI 或 fixture 成功而标为已完成。 - -当前2.0 macOS普通听写复用了原选区路径的app/pid目标恢复,1.x普通听写未调用该路径;不能倒推为既有普通听写能力。同应用多窗口/多输入控件的精确焦点与光标恢复仍不在已证范围,真实AX/TIS/Secure Input行为需分别验证。 - -既有未跟踪 `openless-all/app/src-tauri/openless-ime-payload/` 不属于本轮修改。 diff --git a/docs/pr1019-desktop-review-2026-09-06.md b/docs/pr1019-desktop-review-2026-09-06.md deleted file mode 100644 index 2870aae6..00000000 --- a/docs/pr1019-desktop-review-2026-09-06.md +++ /dev/null @@ -1,186 +0,0 @@ -# PR #1019:更新范围后的桌面复审闭环 - -范围依据:[2.0需求](./2.0-requirements.md)与[桌面验收清单](./2.0-desktop-acceptance.md)。Linux剩余Host/UI按[交接目录](./linux-egui-handoff/README.md)移交,不计作本批桌面功能缺陷。 -第一轮固定比较:`beta@fc9824ee` → `dc350780`;审查整个PR,不仅审查文档提交。本文续记此前R01–R28之后发现的问题。 - -## 第一轮:新的独立团队 - -| 审查方 | 范围 | 确定问题 | -| --- | --- | --- | -| Windows专项 | 原生输入/热键/录音/本地ASR/进程Job/窗口与打包;对照1.x生产路径 | R32、R33、R35 | -| macOS专项 | AX/TIS/隐私/流式/原生ASR/窗口/CLI/构建;对照1.x生产路径 | R30 | -| 共享产品专项 | React IPC/事件、渠道/凭据/模型、QA/Selection/Agent、历史/词典/风格/市场/Remote | R31、R34 | -| 主代理复核 | Core主听写/停止/取消/重试/插入、需求和证据边界,逐条确认团队发现 | R29及全部问题的修复复核 | - -本轮Standards未确认独立硬性违规;首轮独立审查发现7项确定问题,综合验证与修复追踪追加R36。内部审核不替代GitHub正式批准,也不证明真实设备功能全部通过。 - -## 问题与验证登记 - -| ID | 问题、基线与影响 | 修复及验证状态 | -| --- | --- | --- | -| R29 / P1 | Core停止请求在Starting等待时重新读取当前session;取消A并启动B后,旧stop会完成B并落字,违反D02会话隔离 | 已固定首次目标session;公开Core回归先红(返回B的Inserted)后绿 | -| R30 / P1 | Core流式取消未排空native write即恢复TIS,且先释放voice lease;旧CGEvent输入可能与新会话重叠。1.x先等typer结束再恢复,违反D09 | 已修复:排空后恢复、清理后释放占用;一次性落字也等待已提交效果,收尾由Host executor持有,不随stop调用方丢弃。流式/一次性/调用方drop三项先红后绿 | -| R31 / P1 | QA dismiss等待旧runtime.cancel后无条件清snapshot/HideQa;期间重开的新对话被清空,违反D10 | 已在首await前逻辑关闭,异步部分仅清旧owner;新对话/show-only/新preview三项先红后绿,全QA合同24项通过 | -| R32 / P2 | Windows普通落字恢复原目标失败后直接返回错误,遗漏1.x允许时的copy-only兜底,违反D09 | 已恢复按开关复制降级,不向新焦点粘贴;native源码合同先红后绿,既有结果映射随全Tauri测试通过 | -| R33 / P2 | Windows普通落字复用选区恢复逻辑,漏掉1.x的IsIconic→SW_RESTORE,最小化目标不能恢复,违反D09 | 已补还原后激活和原目标指纹复核;native源码合同先红后绿,真窗口证据单列 | -| R34 / P1 | 本地模型UI在Core激活前创建/启用/置顶渠道,提前修改active;模型缺失或prepare失败仍破坏原云渠道,违反D06 | 已改为Core准备成功后一次metadata提交;三runtime及缺失/禁用渠道回归先红后绿,含并发编辑共24项合同通过;前端source合同约束新生产路径 | -| R35 / P1 | Windows Less Computer窗口虽存在,设置组件、语音快捷键编辑和原生监听仍被macOS条件挡住,违反明确承诺的D12 | 已补内外层UI和原生热键;hook context按安装线程隔离,注册回执参与设置事务,迟到注册核对目标;重绑/禁用只清所属Starting/Recording slot。回归及全Tauri通过,native注入证据见下 | -| R36 / P1 | Less Computer取消会释放Capture,但capture_cancelled把不存在/被替换的lease判为未取消;冷启动迟到后可继续录音,现有headless示例亦失败 | 已将失去捕获所有权判为失效,首await前claim、context/native/转写后复验;取消标记先于资源等待,迟到事件不触碰Agent run。两类回归先红后绿,Less Computer24项及headless示例通过 | - -## 本轮已取得的自动证据 - -- 文档提交`dc350780`:15个文档文件,链接/空白检查通过,已推送原PR分支。 -- 第一轮Windows专项:183项限定Rust测试及4项Node合同通过;包含真实Job进程回归,不代表真实输入/设备验收。 -- 第一轮macOS专项:4项源码/打包合同通过;主机Windows,Speech检查按平台跳过,未执行本地macOS原生测试。 -- 第一轮共享产品:TypeScript/Vite与66项前端/合同测试通过;后续修复后还须重新完整执行。 - -### 第一轮修复后的综合验证 - -- Core:718 passed / 1 ignored,领域合同5/24/17/3/24/12/15/15全部通过。 -- Windows Tauri:434 passed / 1 ignored;Rust1.88 `cargo check`通过。ignored为需显式触发的原生键盘注入smoke,不是普通单元测试失败。 -- 前端:TypeScript/Vite构建与67个测试入口通过,含新增Windows原目标合同与更新后的Core激活接线合同。 -- Linux Windows-host:48 lib + 4 host通过;真实Linux条件编译/设备效果由对应runner/设备单列,不把零运行的cfg测试算通过。 -- Core/Linux严格Clippy与依赖、秘密、隔离、runtime seam、公开面、command/event基线检查通过;headless示例重新可运行。 -- Windows双monitor真实smoke曾通过独立收键、关闭和重建;末次复跑`SendInput=0`未通过,记录为原生注入环境未稳定复验,不宣称重复设备验收成功。最终主听写/ASR/Agent真实设备闭环仍待对应验收。 -- 文档head `dc350780` 的CI `33983624572`四平台通过;这只是修复前代码证据,新修复提交必须重新运行CI。 - -## 后续轮次记录 - -第一轮修复、综合验证并推送后,派全新的团队审查整个PR;若发现确定问题,继续修复、验证、推送,再交新的团队复审。准确head、测试计数和最终结论在完成时补录,不沿用旧head证据冒充新验证。 - -### 第二轮:`fc9824ee...98a0ca38` - -两名全新Windows/macOS审查员交叉覆盖整个PR的原生路径、React入口与共享领域,主代理补充复现及修复复核。不是仅审查第一轮补丁;本轮确认以下9项Spec问题,未确认独立Standards违规。 - -| ID | 确定问题 | 修复/验证状态 | -| --- | --- | --- | -| R37 / P1 | Less Toggle/Auto松键后Esc仅清Core,Host Recording slot常驻导致永久无法再开;关闭面板也未清原生捕获 | 已统一Esc、关闭、胶囊及CLI取消的所属capture收尾;Core CLI回归先红后绿,保留QA独立作用域;Host可取消后复录 | -| R38 / P2 | Less桥丢弃原生Instant,在ASR冷启动排队后重建now,把Auto短按当成长按 | 已透传modifier/combo真实Instant及generation;450ms启动+50ms短按回归先红后绿 | -| R39 / P2 | 录音控制ready-check与pending.push之间可被attach/flush穿过,静音Stop永久丢失 | 已固定pending→slot锁序;Starting保存同一control,静音和胶囊Stop交接前后恰执行一次 | -| R40 / P2 | Less录音、电平与转写没有生产事件,Composer只等旧operating胶囊状态,Windows无glow可代替 | 已接Core typed VoiceState、React/胶囊;保留一条带原seq/session的有效投影供截断重放恢复,拒绝旧终态覆盖新录音;语音忙时保留草稿且不提交 | -| R41 / P1 | Less成功非debug语音未删除WAV,回退到共享Core后丢失1.x成功归档清理 | 已按成功/非debug条件discard,debug与失败保留;录音保留策略回归先红后绿 | -| R42 / P1 | text_inserter.begin尚未登记时取消会释放voice lease;迟到TIS准备/恢复干扰新会话 | 已在begin前登记Shared preparation并持hold,取消与迟到start共同等待一次恢复;原回归和丢弃start回复回归通过 | -| R43 / P1 | 会话中关闭cursor_context_enabled后,完成仍按冻结true重启AX编辑观察 | 已与设置事务串行并读当前开关,普通/流式两分支回归先红后绿 | -| R44 / P1 | QA/Less先release gate再等待native stop,旧mute恢复可破坏新录音;冷ASR取消后仍可开mic | 已分离逻辑取消与资源hold,初始化/stop/ASR清理完毕前Busy;recorder前检查token,已提交任务持有收尾;也覆盖主听写无插入、初始化/停止/取消回复被丢弃与shutdown清理 | -| R45 / P2 | QA Completed/Failed/Cancelled被`phase != Idle`误判仍在使用语音,阻止Less启动 | 已只阻止真实活动阶段,三个QA终态保持面板打开也可启动Less;公开回归通过 | - -第二轮问题全部修复、测试并推送前,不将`98a0ca38`标为最终闭环;下一轮须由新的审查团队复审。 - -第二轮定向证据:Windows Host最终8项通过;Core生命周期10项(含最后CLI回归)、typed VoiceState合同及最近投影回归通过;插入准备、隐私、丢弃回复的回归通过。曾挂起的旧stop测试因owned startup首次poll前使用notify_waiters丢通知,已改为保留permit的notify_one,单项再次通过;不是跳过测试。 - -第二轮综合验证:Core 722 passed / 1 ignored,生命周期10项、其它合同6/24/17/3/24/12/15/15全部通过;Windows Tauri 440 passed / 1 ignored;前端构建与67个测试入口通过;Linux Windows-host 48+4、headless示例、Core/Linux严格Clippy、依赖和公开合同检查通过。MSRV与最终远端CI按精确修复head记录。`98a0ca38`的CI `34002890692`四平台通过,但不能代替第二轮修复提交的CI。 - -第二轮修复提交`c0807d97`已推送;CI `34006442463`的Windows、macOS、Linux、Android全部通过。Linux artifact按PR条件跳过,不算产物验收。 - -### 第三轮:`fc9824ee...c0807d97` - -两名新的独立审查员交叉检查桌面原生路径与整个共享Core,主代理复现并处理完成回调竞态。本轮确认以下6项Spec问题,未确认独立Standards违规。 - -| ID | 确定问题 | 修复/验证状态 | -| --- | --- | --- | -| R46 / P1 | Selection Voice通用Esc/故障/shutdown只取消Core状态,Host麦克风与slot仍存活;冷启动也可能留下目标owner | 在首await前绑定既有RecordingControlSink,所有终止入口只调用同一所属capture清理;同步撤销Starting目标,原生stop/ASR清理完成前保留resource hold。Core与真实Tauri Host seam先红后绿 | -| R47 / P2 | QA Completed/Failed/Cancelled仍拦截通用取消路由,Selection Voice不能收到Esc | 路由仅匹配Recording/Thinking/AwaitingApproval;三种QA终态与Selection并存的公开回归先红后绿 | -| R48 / P2 | Less在Agent启动前遇到recorder.stop、ASR.finish或空转写错误,仅回Idle/日志,没有可见错误 | 复用capture_fault原子终态认领,发布一次安全错误且返回脱敏错误;用户取消不报Error。各错误及取消排除回归先红后绿 | -| R49 / P2 | Host翻译标志可跨按钮/CLI/静音停止泄漏到下次会话;Starting捕获上下文期间的翻译请求又会丢失 | 删除Host重复状态,Core保存当轮意图并在所有停止入口统一应用冻结上下文,显式stop override优先;冷启动、三种停止入口与失败补偿回归通过 | -| R50 / P2 | Host重建Core胶囊payload丢失warming等字段,native start返回后首PCM未到却显示已就绪 | 透传完整payload;兼容缺省的recordingReady仅在首个实际PCM回调置真,零电平/0ms首帧亦有效;Less遵循同一首帧语义。Core、Host和事件桥回归通过 | -| R51 / P1 | A发布Completed后等待设置锁,期间A取消且B启动;A迟到回调会清B状态并为旧文本注册编辑观察 | 观察注册在锁后复核session,迟到原生注册按generation撤销;完成复用按session复位并检查反馈归属。确定性并发回归先红(B被置空)后绿 | - -第三轮综合验证时,新增wire字段暴露canonical fixture未同步、旧热键source合同仍绑定被删除的Host路径;同步实际合同而不跳过断言。完成回调并发测试改用多worker测试runtime,保持Core生产runtime边界检查不变。 - -第三轮最终本地验证:Core 725 passed / 1 ignored,生命周期14项、其它合同6/24/17/3/24/12/15/15全部通过;Windows Tauri 444 passed / 1 ignored;前端构建与67个测试入口通过;Linux Windows-host 48+4、headless示例、Core/Linux严格Clippy、Rust1.88 Tauri检查、依赖/秘密/隔离/runtime/公开面/command-event检查均通过。修复提交的远端平台CI与第四轮新团队结论仍单独记录,不把本地验证表述为macOS设备验收。 - -### 第四轮:`fc9824ee...90cbd014` - -新的Windows/macOS审查员再次审查整个PR,主代理复核共享合同和数据接线。本轮Standards未确认独立违规;确认以下3项Spec问题。第三轮提交已逐Git对象核对SHA后上传至原PR,未重写历史。 - -| ID | 确定问题 | 修复/验证状态 | -| --- | --- | --- | -| R52 / P2 | 市场服务在构造时独立保存HTTP client,绕过共享net代理缓存;关闭useSystemProxy仍使用系统代理,且运行中切换不生效。1.x市场/OAuth使用共享client | 已删除固定client和重复builder,逐请求URL复用共享net缓存,仍禁止重定向并为loopback直连;独立进程假代理回归先红后绿,覆盖启动关闭、同服务true→false→true及混合配置OAuth回环 | -| R53 / P2 | 取消A的原生清理已完成并释放资源,但A调用方尚未恢复时B可以启动;A迟到回复无条件发送HideDictationFeedback。其他失败/shutdown出口也缺归属检查 | 已统一所有Hide出口的session守卫,检查和同步Host enqueue保持同锁;手工延迟cancel回复的公开回归先红(Hide覆盖Show)后绿,R51回归保持通过。Tauri当前该HostAction是no-op,故这是共享Core合同问题,不声称已复现桌面胶囊故障 | -| R54 / P1 | Remote鉴权在获取lifecycle锁前读取旧PIN;轮换持锁期间已读取旧值的鉴权等待后可在新服务代次放行,生产WS没有第二次PIN检查 | 已将PIN读取置于与轮换相同的生命周期临界区;公开接口并发回归先红(Ok,预期BadPin)后绿,旧PIN被拒、新PIN仍可认证 | - -第四轮最终本地验证:Core 726 passed / 1 ignored,生命周期14项及其它合同6/24/18/3/24/13/15/15通过;Windows Tauri 444 passed / 1 ignored;TypeScript/Vite与67个前端测试入口、Linux Windows-host 48+4、headless示例、Core/Linux严格Clippy、Rust1.88 Tauri检查、六个架构/安全/兼容基线检查和定向格式检查均通过。独立修复复核确认R53全部10个原有Hide出口共用归属判断,没有复制遗漏。此提交后继续第五轮全新团队审核。 - -### 第五轮:`fc9824ee...87e09e1c` - -新的Windows/macOS审查员各自完整审核该PR,未确认独立Standards违规;确认两项Spec问题。`90cbd014`的Windows/Linux/Android CI通过,macOS被新提交自动取消,不计通过。为`87e09e1c`发起的桌面手动打包验证`34009260079`在本轮发现新问题后主动取消,待代码复审收敛再为最终head重建,不将旧产物当作新head证据。 - -| ID | 确定问题 | 修复/验证状态 | -| --- | --- | --- | -| R55 / P2 | Tauri与Linux Host注入仅查当前线程Handle的TokioTaskSpawner;真实麦克风OS线程没有runtime上下文,QA/Less/Selection静音与故障收尾任务被直接丢弃,检测器已fired后不会重试 | Tauri使用框架共享executor,Linux捕获已有Host Handle并用于所有原生线程;默认构造无runtime时在开仓前显式失败,自定义Host可显式注入。两Host真实std::thread回归先红(Disconnected/RecvError)后绿,Core不私建runtime;交接01及main构造已同步 | -| R56 / P2 | EventBus在锁外分配seq,backlog更新与broadcast也不处于同一临界区;并发原生/Agent事件乱序,UI最大seq去重丢有效事件,replay水位可能先于入队 | 8线程回归先红(首条seq=6,预期1)后绿;复用backlog锁覆盖分配、投影、入队及发送,replay在同锁内读取水位,事件完整10项通过。不是只对重放结果排序 | - -第五轮最终本地验证:Core 727 passed / 1 ignored,生命周期14项及其它合同6/24/18/3/24/13/15/15通过;Windows Tauri 445 passed / 1 ignored;Linux Windows-host 50+4、headless、TypeScript/Vite与67个前端测试入口、Core/Linux严格Clippy、Rust1.88 Tauri检查和六个架构/安全/兼容基线检查通过。R56线程回归另连续10轮、共8万消息通过;独立有界复核未发现锁序或消费者语义问题。R55两端从真正OS线程调用生产同型spawner,避免只在tokio测试线程内验证。旧大合同的构造示例也已链接当前executor前置说明。修复提交后交第六轮全新团队,不提前宣称闭环。 - -### 第六轮:`fc9824ee...588fb35b` - -两名全新审查员再次覆盖整个PR;macOS专项没有新增确定发现,Windows专项确认以下一项同时影响Windows/macOS的共享Tauri录音回归。Standards没有独立硬性违规;同时纠正两处非阻塞过时注释(Windows Less入口、Codex非流式能力/共享分流位置),不将其计作新功能缺失。 - -| ID | 确定问题 | 修复/验证状态 | -| --- | --- | --- | -| R57 / P2 | 可选录音准备被升级为硬依赖:输出设备不存在/静音操作失败,或归档目录路径准备失败时,TauriAudioRecorder直接返回Err而不启动可用麦克风。1.x对静音失败仅告警,归档路径以Option传入Recorder | 已恢复辅助效果告警降级;三种失败组合以旧策略复验均返回Err,新策略回归通过;成功guard持有/释放及禁用不调用平台另有回归。真实Recorder.start错误仍传播,归档为None时不伪造文件。这里验证准备函数与生产接线,不声称真实麦克风已实测 | - -第六轮独立Windows定向验证:Agent 5项、hook 12项、IME 50项通过,交互键盘注入1项未运行;macOS相关源码合同通过,Speech检查在Windows明确跳过。以上并未覆盖R57,不能以绿灯代替新增回归。修复提交后继续第七轮全新团队审核。 - -第六轮最终本地验证:Tauri 447 passed / 1 ignored,Rust1.88检查通过;Core 727 passed / 1 ignored、生命周期14项及全部领域合同、Linux Windows-host 50+4、headless、严格Clippy、前端构建67项和六个架构/安全/兼容基线检查通过。`588fb35b` CI `34009963206`四平台已通过,但修复提交须重新独立验证;Linux artifact条件跳过不算产物通过。 - -### 第七轮:`fc9824ee...5f668b1d` - -两名全新审查员确认3项Spec问题,主代理再复现QA跨线程关闭竞态,合计4项;Standards无独立硬性违规。macOS目标恢复失败复制候选未计缺陷:被引用的1.x失败分支在macOS不可达,不能仅按共享函数片段推断旧平台语义。 - -| ID | 确定问题 | 修复/验证状态 | -| --- | --- | --- | -| R58 / P2 | Windows默认Agent裸名无法定位npm生成的.cmd入口,产品推荐的安装方式导致检测/运行NotFound | Windows Host按有效PATH解析必要可执行扩展,显式路径不变,复用stdlib转义及原有Job/管道;真实shim先红NotFound后绿,含.exe/显式.cmd/裸名.cmd和空格、引号、元字符参数 | -| R59 / P2 | macOS Qwen/Whisper同属Generic但有独立cache;原子激活不释放同runtime旧lease,状态又优先报Qwen,切Whisper后仍显示旧模型并长期驻留 | 已按真实previous target认领/预载/释放代次;cache同锁核对模型和owner,状态按目标家族读取。Core合同先红双缓存后绿,覆盖同ID新代次、普通使用撤销旧权限、渠道切换和失败补偿;普通ASR迟到加载可用未缓存Arc继续本轮,激活所属迟到加载报错且不覆盖新cache | -| R60 / P2 | Foundry GPU→CPU切换/首次CPU下载提示被延迟缓存到转写完成,Notification又无Tauri消费者,原1.x实时反馈消失 | Native回调立即发布冻结session通知,released拒绝迟到回调;桥核验当前归属/阶段并显示胶囊,QA离开转写、取消与窗口接管清所属提示。实时/展示/清理回归先红后绿,无真实GPU验收声明 | -| R61 / P2 | QA dismiss在两个同步state锁之间允许另一OS线程重开;旧操作清空并隐藏B,发生在首await之前,R31原异步清理回归未覆盖 | 公开事件订阅边界控制线程交错后确定红:B录音返回Cancelled。presentation锁收口录音/文本/仅显示与关闭,所有native await在锁外;状态/回调/模式/答案替换与对应事件在同一state guard内发布,旧阶段先核对session/phase,Host动作不持state锁。三种重开及QA合同25项通过;独立复核又确认并关闭模式切换/撤回旧答案快照窗口 | - -修复提交后继续第八轮全新团队审查;未运行的macOS模型、真实GPU/GUI等仍按设备边界记录,不以源码合同替代。 - -第七轮最终本地验证:Core 727 passed / 1 ignored,生命周期14项及合同6/26/18/3/25/13/15/15全部通过;Windows Tauri 452 passed / 1 ignored;Linux Windows-host 50+4、headless、TypeScript/Vite与67项前端入口、Core/Linux严格Clippy、Rust1.88 Tauri和六个架构/安全/兼容基线检查通过。Windows CLI回归还覆盖大小写重复PATH的实际最后覆盖值;R59普通ASR加载与自动清理出口均保留新owner,R61所有当前修改已完成有界复核。实际macOS cache代码仍必须由新head的macOS CI编译,不沿用前一提交结果。 - -### 第八轮:`fc9824ee...2d8fd1b7` - -两名全新Windows/macOS审查员覆盖整个PR,主代理沿失败及并发链路补充复现,共确认以下8项Spec问题;没有独立Standards硬性违规。`2d8fd1b7`的CI `34013888722`四平台通过,但仍存在这些行为缺陷,绿灯不能替代产品合同复核。 - -| ID | 确定问题 | 修复/验证状态 | -| --- | --- | --- | -| R62 / P1 | Windows npm OpenCode `.cmd`可检测但无法运行:Core总会加入多行自动化提示,旧Argv传输被Rust Windows批处理参数校验拒绝 | 复用现有stdin管道;官方OpenCode v1.18.29及v1.2.18均读取非TTY stdin。实际Windows `.cmd`进程fixture先检测、再传Core多行提示,先红(换行参数被拒绝)后绿;不是已运行真实模型的OpenCode验收,不引入shell自拼转义 | -| R63 / P2 | Selection Voice把Windows已发送的PasteSent判成失败,预览可重复确认且历史丢失;QA确认又丢弃真实回执 | PasteSent贯通原生映射、Core状态、历史与IPC,保持“已发送”不同于“已确认插入”;直接/QA预览、重复确认、失败重试与wire回归通过 | -| R64 / P1 | 默认Raw不经过Polishing,却由真实Pipeline无条件发送最终PolishDelta,被Backend判InvalidState;finish异常也没有回收残留engine资源 | Raw仅返回最终EngineResult并走一次性落字,不准备流式/TIS;只在真实LLM阶段发PolishDelta。真实Pipeline连续两次Raw先红后绿;EngineFailure清理回归先红cancel=0后绿cancel=1,复用已有owned清理入口 | -| R65 / P1 | QA语音编辑已持QA麦克风lease,却通过SelectionVoice.begin再次领麦克风而Busy;文本预览还会长期占音频,阻止语音续问/主听写 | 纯编辑直接进入Processing,不创建音频占用;Preview只释放匹配session的逻辑lease,保留原生cleanup hold。公共回归原4项Busy失败修至5项全通过,覆盖活动录音互斥及原生清理等待 | -| R66 / P1 | QA撤销在会话owner读取、预览撤回、答案写回之间被新turn抢占;确认应用的同根因可拿B预览应用旧文本,或由旧完成回调关闭B | 撤销与签发均收为Core单个按turn校验的状态事务,删除Host三段编排及无身份replace_last_answer;原生完成仅dismiss_session所属回合,stale不撤销真实回执。两类撤销与两类确认窗口均先红后绿,共5项覆盖同conversation、新conversation、仅show空面板、失败重试及重复确认;主按钮/热键dismiss语义不变 | -| R67 / P1 | 旧CLI/按钮停止与已排队物理Press重叠,B已返回Started但A清掉其generation,Hold松开变Noop | 公开入口先真红;只改reset锁序仍红,最终将phase读取、Start决定和真实Starting认领置于同hotkey锁,锁外继续原异步启动。普通入口复用同一认领函数,无新增状态;32轮受控交错绿,runtime seam检查通过 | -| R68 / P1 | QA/划词语音迁移后被Tauri无条件写WAV,违反1.x不落盘边界;归档删除失败还阻断已有内存转写 | Core冻结RecordingPlan.archive_enabled=false,Host跳过归档目录/文件及prune,保留实际停止/清理错误;两种QA管线、划词语音与主听写/Less对照回归先红Persistence后绿,不以忽略删除失败替代不落盘 | -| R69 / P2 | Selection A发Completed后persist重新读全局state;此时B已begin,A历史被写为B/空原文及错误指标 | 在完成原锁内克隆既有SelectionState,并只持该快照持久化;公共事件边界回归先红空原文后绿,核对A原文、结果、LLM与耗时;没有新增并行状态或锁 | - -第八轮最终本地验证:Core 729 passed / 1 ignored,生命周期15项及合同6/26/18/3/5/25/5/13/16/16全部通过;Windows Tauri 455 passed / 1 ignored、Rust1.88检查通过;Linux Windows-host 50+4、headless示例、Core/Linux严格Clippy、TypeScript/Vite与68个前端入口、六个架构/安全/兼容基线检查全部通过。Tauri首轮并行测试在共享全局side-combo监听的既有测试无超时recv处挂起,已终止且不计通过;最终完整测试按单线程执行,两次通过,没有跳过该测试或改动生产监听。Raw/归档独立交叉复核无新增发现,R67已通过完整Core复验,R66完整5项通过后停改。修复提交后继续第九轮全新团队审核;新head平台CI与真实设备验收仍分别记录。 - -### 第九轮:`fc9824ee...d16324c3` - -Windows/macOS两名全新审查员均已完成整个PR的独立复核:Standards无新增确定违规,Spec共3项确定问题,主代理逐条核对并复现。`d16324c3`的CI `34016669726`四平台通过,Linux制品任务按PR条件跳过;该结果属于修复前head。 - -| ID | 确定问题 | 修复/验证状态 | -| --- | --- | --- | -| R70 / P1 | Windows主听写在等待上下文/凭据后才捕获落字目标;期间A切到B,最终把B当作原目标,违反1.x首await前冻结及D09 | `TextInserter::capture_target`在共用认领阶段同步冻结本轮目标,仅异步`begin`准备原生输入源;不增加全局槽/Map。直接/CLI/物理入口A→B先红后绿,取消后新B和不插入零准备共3项通过;macOS共享新机制,但不误称其1.x已有早期恢复 | -| R71 / P2 | QA普通语音的状态/电平仅发送给QA面板,原Recording/Transcribing/Polishing/终态胶囊反馈丢失,违反D03/D10 | 恢复同一Core事件的Host胶囊投影,区分语音/文本、CPU提示及旧owner;已呈现QA使用实际返回epoch,同锁检查/写入,旧终态及自动隐藏不覆盖后继原生提示。生命周期、迟到Recording和原生CAS出口均先红后绿;首帧快失败等小影响边界按维护者决定暂缓,见收口说明 | -| R72 / P2 | 共用context无条件解析ASR/LLM/Omni:Raw被禁用的无关LLM阻断,Omni被禁用的传统ASR阻断;QA文本也被无关ASR阻断 | Raw和Omni分别公开API先红InvalidState后绿。按实际听写/纯ASR/QA文本/QA语音用途解析所需渠道;Raw保留可选LLM解析失败,停止时临时翻译不能切到后来启用的渠道。实需ASR/LLM仍报错,QA文本只要求其响应provider | - -同时订正macOS证据表述:1.x普通听写未调用选区的app/PID恢复路径,当前2.0才复用该机制;不能将新实现倒写成旧基线能力。该项为文档事实更正,不另计运行时缺陷。 - -第九轮最终验证:Core 731 passed / 1 ignored、生命周期15项与合同6/3/26/18/3/5/25/5/13/16/16、Linux Windows-host 50+4、Core/Linux严格Clippy、headless示例、六个架构/安全/兼容检查、TypeScript/Vite及68个前端入口均通过。Windows Tauri完整串行测试460 passed / 1 ignored,Rust1.88检查通过;9个改动Rust文件的限定格式及diff检查通过。以上为本地自动证据,不代替最终head CI和真实设备验收。 - -### 发布优先收口 - -维护者明确要求:若无重大问题,不再修复小影响或不能稳定复现的问题,留下文档后提交推送原PR,准备合并。因此不启动第十轮全面审查,不继续扩展Host显示origin、Core QA开始顺序和旧Selection Voice定时器改造;这些尚未完成的方案未纳入本次代码。 - -已知暂缓项仅涉及QA胶囊展示:首条Recording尚未被桥消费时极快失败可能没有胶囊Error;旧Selection Voice自动隐藏及极短跨会话切换可能让未呈现首帧的QA提示被忽略或提前隐藏。QA面板仍接收Core错误/终态事件,当前证据未表明这些显示边界影响回答、录音结果或数据。其依据是源码时序分析,不宣称真实设备稳定复现。后续如有稳定用户复现或影响扩大,再补完整显示origin绑定和统一定时器归属。 - -本次两名审查员的整个PR复核及有界核验均等待最终返回。最终提交、验证计数和新head CI状态写入原PR描述;不把上一head成功或未执行的设备/制品检查记作本次通过。验收政策同步见[发布收口决定](./2.0-desktop-acceptance.md#5-2026-09-06-发布收口决定)。 - -## 不得混同的完成条件 - -- 源码/自动验证:本记录跟踪确定缺陷是否全部关闭。 -- Windows/macOS设备验收:按D01–D19逐项记录目标应用、录音、原生模型、权限、升级与签名证据;未执行的不填通过。 -- Linux接入:Core合同与文档可交接;Linux产品侧待办独立交egui团队。 -- GitHub:CI、正式review与merge gate分别记录;不自动合并、打tag或发布。 diff --git a/docs/provider-channels-plan.md b/docs/provider-channels-plan.md deleted file mode 100644 index 9dffde51..00000000 --- a/docs/provider-channels-plan.md +++ /dev/null @@ -1,200 +0,0 @@ -# 供应商渠道卡片化 实施计划 - -> 状态:P0 已完成(2026-08-07,PR #918) -> 日期:2026-08-04 -> 范围:设置 → AI 提供商,LLM 润色 + ASR 语音转写 -> 参考:[Calcium-Ion/new-api](https://github.com/Calcium-Ion/new-api) 的 Channel 模型与重试策略 - -## 1. 要解决的问题 - -今天一个供应商只能存一份配置(一把 key、一个 endpoint、一个模型)。实际使用中: - -1. **同一家有多把 key**(主号 / 备号 / 白嫖号),现在只能存一把,换 key 靠手动覆盖粘贴 -2. **key 之间要频繁切换**,切换过程中旧配置就丢了 -3. 某把 key 被限流(429)时没有任何自动应对,整条润色链路直接失败 - -目标:把配置从"一个供应商一个槽"变成"一张张可命名、可排序、可开关的卡片",并让失败能自动顺延到下一张卡片。 - -## 2. 现状核对 - -| 事实 | 位置 | -| --- | --- | -| 存储层已经是 `HashMap`,key 是 preset id | `credentials.rs:169` | -| `CredsLlmEntry` 已有 `displayName` 字段,前端从未使用 | `credentials.rs:257` | -| ASR 凭据按 provider 隔离正确(空槽才填默认值) | `ProvidersSection.tsx:370` | -| LLM 切 preset 会**强制覆盖** endpoint/model,注释所述的"共用槽"bug 早已不成立 | `ProvidersSection.tsx:305` | -| 全局零重试 / 零故障转移(`rg retry\|backoff\|fallback` 无命中) | — | -| 凭据读取是**隐式全局** `CredentialsVault::get(...)` 去查 `root.active.*`,调用方无法指定渠道 | coordinator.rs / commands/providers.rs 共数十处 | -| ASR provider id 同时承担**协议路由 key**(百炼一个 id 分三协议,stepfun 分两协议) | `coordinator.rs:341` | -| 新手引导直接嵌 `` | `Onboarding.tsx:208` | -| Windows 默认 ASR 是本地 Foundry(无需 key,开箱即用) | `credentials.rs:155` | -| LLM 单次请求超时 30s | `polish.rs:23` | - -**结论**:存储结构不用推倒,改 key 语义即可;真正的成本在"凭据显式化"这次重构。 - -## 3. 已定的设计决策 - -| 决策 | 结论 | -| --- | --- | -| 范围 | LLM 与 ASR **都**做卡片 | -| 排序 | 列表可拖拽,越靠上越优先;启用列表的**第一个 = 当前使用** | -| 开关 | 打开 = 加入重试队列;**关掉自动沉到列表末尾**;重新打开回到启用组末尾 | -| 触发切换 | 429 等错误**立即**切下一个渠道 | -| 超时 | **不触发**切换(本期先这样) | -| 渠道失败 | **只在卡片上标红**(如「上次失败 · 401 · 3 分钟前」),**不自动禁用** | -| 全部失败 | 润色链路降级为**直接插入 ASR 原文** + 右上角提示 | -| 特殊项 | 本地引擎(qwen3 / sherpa / Apple 语音 / Foundry)与 Codex OAuth **不做预置固定卡片**,它们是「+添加渠道」供应商下拉里的普通选项,选中即长出卡片,表单里没有 key/地址字段 | - -### 3.1 ASR 与 LLM 语义统一 - -429 只出现在**建连 / 鉴权阶段**——此时一个字都还没吐出来,音频缓冲尚未被消费,换渠道重连是安全的。因此两边共用同一套心智: - -> 排序 = 优先级;开关 = 在不在重试队列;失败(非超时)顺延下一个。 - -ASR 唯一的额外规则:**一旦开始出字就不再切换**,之后连接断了就是断了(流式已吐字,回滚会造成文字重复或跳变)。 - -### 3.2 429 冷却(必须有) - -若不加冷却,限流期间**每一次**听写都会白赔一次「打 1 号 → 429 → 打 2 号」的往返(数百毫秒,同步链路里能感知)。 - -- 渠道返回 429 → 打 **60 秒冷却**,冷却期内直接跳过 -- 冷却是**内存态**,不落盘,重启即清 -- 卡片上显示「限流中 · 47s」小字,到期自动恢复,无需用户干预 - -### 3.3 超时值下调(独立改动) - -保留"超时不切换"的规则,但把 `DEFAULT_REQUEST_TIMEOUT_SECS` 从 **30s 压到 8s**。润色是用户盯着屏幕等的同步链路,8 秒未返回的渠道等下去没有意义。 - -## 4. 数据模型 - -```rust -struct Channel { - id: String, // 迁移沿用 preset id;同厂商新卡按 -2 / -3 分配独立 id - name: String, // 用户取的名字,如「硅基流动-主号」 - provider_type: String, // deepseek / volcengine / sherpa-onnx-local / codex_oauth ... - // 决定协议路由 + 表单形状,必须独立于 id - enabled: bool, - order: u32, // 拖拽排序;关掉时自动置到末尾 - last_error: Option, // { kind, message, at } —— 卡片标红用 - last_test: Option, // { ok, latency_ms, at } —— 连通测试结果 - // 凭据字段沿用现有 CredsAsrEntry / CredsLlmEntry,按 provider_type 决定渲染哪些 -} - -// 仅内存,不落盘 -struct ChannelRuntime { - cooldown_until: Option, // 429 临时冷却 -} -``` - -**`provider_type` 必须独立于 `id`**:否则 `coordinator.rs:341` 那条"按 provider id + 模型名路由到具体协议实现"的链会断——这是漏了就整个 ASR 挂掉的点。 - -`active.llm` / `active.asr` 不再是用户直接选择的第二份真相,而是由排序与开关同步计算的 -**兼容缓存**;旧主链路仍读取它们,"当前使用"始终等于启用列表的第一个。 - -## 5. 迁移 - -1. 遍历现有 `providers.llm` / `providers.asr` 的每个非空 entry,各补齐渠道元信息 - - `id` 沿用原 map key,`provider_type` = 原 map key,`name` = `displayName` 或 preset 显示名 - - 新建同厂商的第二、第三张卡片使用 `-2`、`-3`,不改动迁移前的凭据 key -2. 原 `active.llm` / `active.asr` 指向的那张排到 **order = 0**,其余按 ASR_PRESETS / LLM_PRESETS 原顺序跟随 -3. 全部默认 `enabled = true` -4. **全新安装**(无任何 entry):按平台预置 - - Windows → 一张 Foundry 本地 ASR 卡片(保住开箱即用) - - mac / Linux → 不预置,走引导 -5. 迁移必须幂等,且失败时保留原 JSON 不动(参考现有 `load_credentials_for_update` 的写法) - -## 6. 重试策略 - -> **状态:未实现,属于 P2。** P0 里一次失败就是一次失败,不会换渠道。 -> 下表是已定但**尚未落地**的目标行为。 - -### 6.0 代码里已经存在的两样东西(别和渠道故障转移混为一谈) - -排查时容易在代码里搜到 `retry` 就以为做了,这两处都是**既有代码**,与渠道无关: - -1. **`net.rs::send_with_retry` —— 连接层重连,不是渠道切换。** - 只对 `err.is_connect()`(TCP 握手被拒 / 连接重置,请求**尚未送达**服务端)重试, - 150/300/600/900ms 退避。**拿到任何 HTTP 响应就直接返回**(含 429/401/5xx), - 超时明确不重试。它重连的始终是同一个 endpoint,永远不会换到另一张卡片。 - -2. **润色失败已经会回落 ASR 原文。** - `coordinator/polish_flow.rs::polish_or_passthrough` 的失败分支: - ```rust - Err(e) => { - log::error!("[coord] polish failed, falling back to raw: {reason}"); - (raw.text.clone(), Some(reason)) - } - ``` - 也就是说,「全部渠道试完仍失败 → 插入 ASR 原文」这条决策**天然满足**, - P2 要做的只是在回落之前多试几张卡片,而不是新建一条兜底路径。 - -### 6.1 目标行为(P2) - -照搬 New API `shouldRetry()` 的分类,按桌面场景裁剪: - -| 情况 | 行为 | -| --- | --- | -| 429 | **切下一个** + 当前渠道 60s 冷却 | -| 401 / 403 | **切下一个** + 卡片标红(不自动禁用) | -| 5xx / 连接失败 | **切下一个** + 卡片标红 | -| 超时 | **不切**,直接失败(本期决策) | -| 400 参数错误 | **不切**(换渠道多半是同样的错) | -| 2xx | 成功 | -| 全部启用渠道试完仍失败 | 插入 ASR 原文 + 提示 | - -**不抄** New API 的:`Weight` 加权负载均衡(单用户无负载可均衡,随机选渠道反而让"在用哪个"不可预测)、`Group` / `UsedQuota` / `Balance`(多租户计费概念)、`AutoBan`(桌面软件静默关用户配置会让人一脸懵)。 - -**缓一缓**:`ModelMapping` / `ParamOverride`,有用但非第一版必需。 - -## 7. UI - -``` -┌─ LLM 润色 ──────────────────────────────┐ -│ ⠿ ● 硅基流动-主号 deepseek-v4 28ms ⋮ │ ← 生效中 -│ ⠿ ○ Ark-备用 deepseek-v3-2 — ⋮ │ ← 备用 -│ ⠿ ○ 阶跃星辰 (限流中 · 47s) ⋮ │ ← 429 冷却 -│ ⠿ ⊘ OpenAI (上次失败 · 401) ⋮ │ ← 已关闭,沉底 -│ + 添加渠道 │ -└──────────────────────────────────────────┘ -``` - -添加/编辑弹窗:名字 → 选供应商(自动填 baseUrl / 模型占位)→ 按 `provider_type` 渲染凭据字段 -→ 「测试连通」;字段自动保存,关闭只负责退出弹窗。 - -可复用的现成件: -- `validateProviderCredentials` / `listProviderModels`(`ProvidersSection.tsx:854` 起) -- 按 provider 分支渲染凭据字段的逻辑(火山双鉴权模式、讯飞双字段、百炼词表等) -- 本地引擎卡片不显示凭据字段;模型下载与切换继续集中在「高级 → 本地模型」,避免两处管理同一份模型状态 - -**新手引导**:列表为空时直接摊开添加表单,跳过空态与加号,省一次点击。 - -**平台过滤**:macOS 只显示 Qwen3 Local / Apple Speech;Windows 只显示 Foundry / -Sherpa;Linux 与 Android 不显示这些桌面专有本地引擎。云端供应商全平台可选。 - -**草稿回收**:保持自动保存。只有打开后从未发生任何用户交互的草稿会在关闭时回收; -改过名字、供应商、凭据、模型,或执行过验证/模型拉取后都必须保留,即使内容后来清空 -或异步保存失败。这样无凭据的本地引擎 / Apple Speech / Codex OAuth 也能正常创建,且 -关闭弹窗不会与 blur/debounce 保存竞争删除卡片。 - -## 8. 分期 - -| 期 | 内容 | 可否独立发布 | -| --- | --- | --- | -| **P0** | 渠道数据模型 + 迁移 + 卡片 UI + 拖拽排序 + 测试连通。**不做重试** | ✅ 独立故事:「我有两把 key,想随手切」 | -| | ↑ 已完成(PR #918)。**此时多渠道的价值是"存档 + 手动切换",不是自动容错**:排在第二的卡片永远不会被自动用上,要用得手动拖到第一位。 | | -| **P1** | 凭据显式化重构:`CredentialsVault::get(...)` → 上层解析 `ResolvedChannel` 显式下传 | ❌ 纯重构,无用户可见变化,P2 前提 | -| **P2** | 重试 + 故障转移 + 429 冷却 + 超时下调 + 全挂兜底 | ✅ | - -P1 是本需求最大的单块工作量,比卡片 UI 大得多。P1 + P2 合成第二个 PR。 - -## 9. 待确认 - -- [x] 拖拽排序:手写 pointer 事件(已定)。Tauri webview 默认 `dragDropEnabled` 会吞掉 - HTML5 的 `dragstart`/`drop`,`draggable` 在打包后的 app 里不触发;pointer 方案 - Windows / Android 行为一致,并配合「拖拽结束吞掉补发 click」避免关掉设置弹窗。 -- [x] 卡片列表的移动端(Android)形态:与桌面共用同一渠道 UI;Android 走 - `load_credentials` 的同一条迁移路径,无需单独实现。 -- [x] Android 加密信封:未改版本号。v2 载荷新增的 `providerType`/`order`/`enabled`/ - `lastTest` 均为 `Option` 或带默认值字段,老版本 serde 忽略未知字段,可降级读取。 -- [x] P0 验收判据:已满足(同供应商多卡、重启后顺序与内容不丢、拖拽后第一张生效), - 由 `persistence::credentials` 迁移/排序测试与作者 macOS 实机验证覆盖。 diff --git a/docs/windows-sherpa-onnx-asr-plan.md b/docs/windows-sherpa-onnx-asr-plan.md deleted file mode 100644 index bd85f8a7..00000000 --- a/docs/windows-sherpa-onnx-asr-plan.md +++ /dev/null @@ -1,421 +0,0 @@ -# Windows sherpa-onnx 本地 ASR 实施规划 - -> 状态:草案 / 待评审 -> 日期:2026-05-12 -> 范围:仅 Windows;不替换 macOS `local-qwen3`;不替换 Windows `foundry-local-whisper` - -按 OpenLess 现有架构(Coordinator 单一拥有者、ASR provider 独立模块、`AudioConsumer` -接口)来做,**不重写主链路、不动 macOS、不替换 Foundry**,新增一个 Windows 实验 -provider。 - ---- - -## 1. 目标与非目标 - -### 目标 - -- **Windows 新增本地 ASR provider**:`sherpa-onnx-local` -- **复用现有听写主链路**:Recorder / Coordinator / polish / insert / history -- **支持中文为主,中英混合可用** -- **第一阶段 batch,第二阶段流式** -- **可与 `foundry-local-whisper` 并存切换** - -### 非目标 - -- 不替换 macOS `local-qwen3` -- 不替换 Windows `foundry-local-whisper`,仅作为新选项 -- 不做 Linux 支持(本期) -- 不做语者分离、长会议转写、字幕导出 -- 不做云端模型,不做模型自训 - -### 明确边界 - -- **不动 Coordinator 的 phase enum / hotkey 流程** -- **不动 polish / insertion / history** -- **sherpa runtime 只通过 `AudioConsumer` + 转写函数对外暴露** -- **任何 sherpa 错误必须降级**:不能让用户的话丢失(与现有 ASR 失败语义一致) - ---- - -## 2. 架构定位 - -按现有结构对齐 Foundry 路径: - -``` -asr/local/ - mod.rs # 增加 sherpa provider id 与 helper - foundry_provider.rs # 保留 - foundry_runtime.rs # 保留 - sherpa_provider.rs # 新增:AudioConsumer + transcribe() - sherpa_runtime.rs # 新增:模型加载 / 推理调用 / 生命周期 - sherpa_models.rs # 新增:模型 catalog 静态表 -``` - -主要扩展点: - -- `ActiveAsr::SherpaOnnxLocal(Arc)` -- `coordinator/dictation.rs`:`begin_session` / `end_session` 增加 - `#[cfg(target_os = "windows")]` 分支 -- `commands.rs`:增加准备 / 释放 / 状态 / 模型管理命令 -- `types.rs`:增加 `UserPreferences` 字段 -- 前端 Settings 高级页:在 Windows 下新增第三个本地 ASR toggle - ---- - -## 3. 模型策略 - -### 第一批模型(重点是中文) - -| 模型 | 用途 | 备注 | -|---|---|---| -| **SenseVoice small (zh/en/ja/ko/yue, int8)** | 中文 + 多语言默认 | 体验通常优于 Whisper small;包小、速度快 | -| **Paraformer (zh, int8)** | 中文专用强力档 | 中文听写更稳;不擅长英文 | -| **Whisper small (multilingual, int8)** | 英文/通用 fallback | 与 Foundry Whisper 体验对齐基准 | - -模型形态全部用: - -- **ONNX** -- **量化 int8** -- **CPU 推理优先** - -后续可选: - -- **streaming Zipformer (zh)**:第二阶段流式使用 - -### 模型分发策略 - -- **不打进安装包** -- **首次启用时下载** -- **下载源带镜像**:HuggingFace / 镜像 / 自托管 CDN -- **校验 SHA-256** -- **存放路径**: - ``` - %APPDATA%\OpenLess\models\sherpa-onnx\\ - ``` - ---- - -## 4. 模块设计 - -### 4.1 `sherpa_models.rs` - -静态目录 + alias 解析,模仿 `foundry.rs::MODELS`: - -```rust -pub const PROVIDER_ID: &str = "sherpa-onnx-local"; -pub const DEFAULT_MODEL_ALIAS: &str = "sense-voice-small-zh"; - -pub struct SherpaModel { - pub alias: &'static str, - pub display_name: &'static str, - pub family: SherpaFamily, // SenseVoice / Paraformer / Whisper / Zipformer - pub languages: &'static [&'static str], - pub mode: SherpaMode, // Offline / Online - pub files: &'static [SherpaModelFile], // name + sha256 + size + url -} -``` - -边界: - -- **不在这里写下载逻辑** -- **不依赖 sherpa-onnx 类型**,纯描述 - -### 4.2 `sherpa_runtime.rs` - -只这一处依赖 `sherpa-onnx` crate。 - -职责: - -- **初始化 OfflineRecognizer / OnlineRecognizer** -- **缓存当前已加载的 recognizer** -- **暴露**: - - `ensure_loaded(alias) -> Result` - - `transcribe_pcm(pcm: &[i16]) -> Result`(offline) - - `create_stream() -> SherpaStream`(online,第二阶段) - - `release_now()` - - `status_snapshot()` -- **生命周期**: - - `lifecycle: AsyncMutex<()>`(与 Foundry 一致,串行化加载/释放) - - 闲时延迟释放(参考 `local_asr_keep_loaded_secs` 模式) - -边界: - -- **不知道 Coordinator** -- **不知道 Recorder** -- **不动 UI** -- **不发 Tauri 事件** - -错误统统返回 `anyhow::Error`,由上层翻译为前端文案。 - -### 4.3 `sherpa_provider.rs` - -形状与 `foundry_provider.rs` 完全对齐: - -```rust -pub struct SherpaOnnxAsr { - runtime: Arc, - model_alias: String, - language_hint: Option, - buffer: Mutex>, // PCM s16le 16kHz mono - cancel_generation: AtomicU64, -} - -impl AudioConsumer for SherpaOnnxAsr { - fn consume_pcm_chunk(&self, pcm: &[u8]) { ... } -} - -impl SherpaOnnxAsr { - pub async fn transcribe(&self, timeout: Duration) -> Result { ... } - pub fn cancel(&self) { ... } -} -``` - -边界: - -- **batch 阶段不做实时 token 回调** -- **流式阶段独立加 `transcribe_stream(on_token)`,不破坏 batch API** - -### 4.4 `coordinator/dictation.rs` 集成 - -新增分支,**完全 mirror 现有 foundry 分支**: - -`begin_session`: - -```rust -#[cfg(target_os = "windows")] -if sherpa::is_sherpa_onnx_local(&active_asr) { - let local = Arc::new(SherpaOnnxAsr::new(...)); - store_asr_for_session(inner, sid, ActiveAsr::SherpaOnnxLocal(Arc::clone(&local))); - let consumer: Arc = local; - start_recorder_and_enter_listening(inner, sid, &active_asr, consumer).await?; - return Ok(()); -} -``` - -`end_session`: - -```rust -#[cfg(target_os = "windows")] -ActiveAsr::SherpaOnnxLocal(local) => { - match local.transcribe(sherpa_transcribe_timeout()).await { - Ok(r) => { schedule_sherpa_release(...); r } - Err(e) => { /* 与 foundry 失败分支同形 */ } - } -} -``` - -边界: - -- **不修改 Foundry 分支** -- **不修改 macOS Qwen3 分支** -- **复用 `RawTranscript` / `polish` / `insertion`** - -### 4.5 `commands.rs` - -新增命令(与 Foundry 同形,方便前端代码复用模式): - -- `sherpa_asr_status` -- `sherpa_asr_prepare` -- `sherpa_asr_release` -- `sherpa_asr_catalog` -- `sherpa_asr_set_model` - -只在 `#[cfg(target_os = "windows")]` 下注册。 - -### 4.6 `types.rs` - -新增字段(默认值 Windows = SenseVoice 中文,其他平台不可用): - -```rust -#[serde(default = "default_sherpa_model_alias")] -pub sherpa_onnx_model: String, - -#[serde(default)] -pub sherpa_onnx_language_hint: String, - -#[serde(default = "default_local_asr_keep_loaded_secs")] -pub sherpa_onnx_keep_loaded_secs: u32, -``` - -**不改 `default_active_asr_provider()`**:Windows 默认仍是 -`foundry-local-whisper`,sherpa 通过高级开关启用。 - -### 4.7 前端 - -在 Windows 高级页加第三个 toggle 行: - -- Foundry Local Whisper -- **Sherpa-Onnx Local(新增,实验)** -- 模型选择 / 准备 / 删除 / 路径 - -复用现有 `LocalAsr` UI 模式。i18n key 用 zh-CN 源 + en 镜像(按 AGENTS.md 规则)。 - ---- - -## 5. 依赖与打包 - -### 5.1 Rust crate - -```toml -[target.'cfg(target_os = "windows")'.dependencies] -sherpa-onnx = "..." # 选最新稳定版,feature 关闭非必要后端 -``` - -注意: - -- **关掉 CUDA / DirectML 等 feature**(v1 只用 CPU) -- **避免依赖 dynamic ONNX Runtime**:优先静态或随包附带 DLL -- **不要引入新的 native build chain**:保证 GH Actions Windows runner 能编 - -### 5.2 DLL / native 资源 - -如果 sherpa-onnx crate 自带 `onnxruntime.dll` / `sherpa-onnx.dll`: - -- 通过 `build.rs` copy 到 target dir -- 由 Tauri bundler 一同打进 NSIS / MSI -- WiX 的 `Component` 落到 `INSTALLDIR` -- **严格遵守 AGENTS.md 的 Windows CI 红线**: - - 两轮 NSIS / MSI - - bash shell - - `-sice:ICE80` - - 不动 Repair 步骤 - -如果 crate 不带 DLL: - -- 第一次启用时从镜像下载,与模型同目录 -- 用 LoadLibrary delay-load - -### 5.3 模型下载 - -复用现有 `LocalAsr` 模型管理 UX: - -- 镜像选择 -- 进度 + 取消 -- SHA-256 校验 -- 失败重试 - ---- - -## 6. 实施里程碑 - -### M1 Provider 骨架 (0.5 周) - -- `sherpa_provider.rs` / `sherpa_runtime.rs` / `sherpa_models.rs` 文件结构 -- `ActiveAsr::SherpaOnnxLocal` -- `commands.rs` 桩函数 -- 前端 toggle + i18n -- **不实际推理**,先打通主链路(mock transcribe 返回空串或固定字符串) - -### M2 Batch 推理可用 (1.5 周) - -- 接 `sherpa-onnx` crate -- offline recognizer 加载 -- WAV/PCM → text -- 模型:先只接 **SenseVoice small zh** -- 错误降级:失败回到 Foundry / Volcengine -- Windows 本机 smoke test - -### M3 模型管理 + 多模型 (1 周) - -- 加 Paraformer / Whisper small -- 模型下载 / 校验 / 删除 -- 镜像源切换 -- 模型切换不需要重启 - -### M4 性能与稳定性 (1 周) - -- 启动时延、首次加载时延 -- 内存占用 -- 长录音稳定性 -- 取消(hotkey 再次按下)行为正确 -- DLL 缺失 / 模型损坏 / 路径含中文 / 路径含空格 全部覆盖 - -### M5 流式 ASR(可选,二阶段) - -- 接 OnlineRecognizer -- 边录边 partial → `local-asr-token` 事件 -- 与现有 macOS Qwen3 stream UX 对齐 - -### M6 发布 - -- 高级页打开为实验 -- 收集真实用户反馈 -- 满足质量门槛后再决定是否提升为 Windows 默认 - ---- - -## 7. 风险与对策 - -| 风险 | 对策 | -|---|---| -| sherpa-onnx Windows 打包带 native DLL,触发 WiX / NSIS 兼容问题 | 严格走 AGENTS.md 的两轮 bundle + `-sice:ICE80`;早期就在 CI 跑 | -| ONNX Runtime 版本冲突 | 锁版本;不和其他 crate 共享 ORT | -| 模型体积大,下载失败 | 强制镜像 + 断点续传 + SHA-256 + 明确错误文案 | -| 安装路径含中文/空格导致模型加载失败 | 用 `\\?\` 长路径前缀 + 单元测试覆盖 | -| 首次加载耗时长(用户以为卡死) | 加载阶段发 Tauri 进度事件;胶囊显示"准备模型"态 | -| CPU 性能不足机器卡顿 | 默认 SenseVoice small int8;提供更小模型;超时降级 | -| 推理 panic 干扰主进程 | 推理放 `spawn_blocking`,错误 → anyhow,绝不 panic 向上 | -| 与 Foundry / Qwen3 并存导致状态混乱 | 切换 provider 时强制 release 另一边;测试覆盖 | -| 取消语义不一致 | 严格按现有 `cancel_generation` 模式实现 | -| macOS / Linux 编译被影响 | 全部 sherpa 代码 `#[cfg(target_os = "windows")]` 包裹 | - ---- - -## 8. 验收标准 - -### 功能 - -- Windows 用户能在高级页启用 `sherpa-onnx-local` -- 默认模型 SenseVoice small zh 可下载、加载、转写 -- 中文短句听写质量明显优于 Foundry Whisper small(盲测) -- 失败时不丢用户的话(自动降级或留 raw) -- 取消、重复触发、连按热键不崩 - -### 工程 - -- 不动 macOS 编译产物 -- 不动 Foundry 路径 -- 不引入新的 CI 红线 -- Windows MSI / NSIS 两轮构建仍然通过 -- 包体增量在可接受范围(建议 < 50MB,不含模型) - -### 测试 - -- `cargo test` Windows 通过 -- 手测脚本: - - 中文短句 - - 中文长句(30s+) - - 中英混合 - - 安静 / 噪音 - - 取消 - - 切换模型 - - 切换 provider - - 卸载模型 - - 无网络再次启动 - ---- - -## 9. 不做什么(再次明确) - -- **不重构 ASR trait 体系** -- **不引入 ASR 中间层抽象** -- **不替换 Foundry** -- **不动 macOS Qwen3** -- **不做 Linux** -- **不做云端 fallback 改动** -- **不做模型微调** -- **不做多 provider 自动选择** - ---- - -## 10. 相关参考 - -- 现有 Windows 本地 ASR 实现: - - `openless-all/app/src-tauri/src/asr/local/foundry.rs` - - `openless-all/app/src-tauri/src/asr/local/foundry_provider.rs` - - `openless-all/app/src-tauri/src/asr/local/foundry_runtime.rs` -- 现有 macOS 本地 ASR 实现: - - `openless-all/app/src-tauri/src/asr/local/local_provider.rs` -- 主听写链路集成点: - - `openless-all/app/src-tauri/src/coordinator/dictation.rs` -- Windows CI / 打包红线:见仓库根 `AGENTS.md`「Windows CI 红线」一节 diff --git a/openless-all/app/docs/cursor-context-test-plan.md b/openless-all/app/docs/cursor-context-test-plan.md deleted file mode 100644 index 3a254329..00000000 --- a/openless-all/app/docs/cursor-context-test-plan.md +++ /dev/null @@ -1,186 +0,0 @@ -# 光标上下文 + 手改学习 —— 装机测试案例 - -装的版本:`local/daily`。开关在 **设置 → 隐私 → 数据存储 → 光标上下文(实验)**,默认关。 - -盯日志: - -```bash -tail -f ~/Library/Logs/OpenLess/openless.log | grep -E "cursor-context|cursor context|vocab" -``` - -重装后辅助功能授权会短暂失效(ad-hoc 签名每次构建 cdhash 都变),但 app 自己会恢复——实测重试到第 23 次时自己起来,约 70 秒。看到 `hotkey listener installed` 就能用了;万一一直刷 `CGEventTapCreate 失败`,去 系统设置 → 隐私与安全性 → 辅助功能 → OpenLess 关掉再打开。 - ---- - -## 这个功能干什么 - -开关打开后,每次听写会读**你当时正在写的那个文档里、光标附近的几百个字**,跟着请求一起发给 LLM。这样它知道你在写什么,「接口」不会写成「借口」。 - -落字之后它还会盯一小会儿:如果你手动改了它插进去的某个词,那个词可能进你的词汇表。 - -**只写词汇表,不写纠正规则。** 词条是提示(送给 ASR、进润色 prompt 让 LLM 带上下文判断),错了最多是没帮上忙;纠正规则是字面替换,错了是静默的、全局的。学来的东西配不上后者那份权力。 - ---- - -## 1. AX 覆盖率 —— 不用开口说话 - -**设置 → 高级 → 调试工具 → 光标上下文探针** - -点「探测(5 秒后)」→ 切到目标 app → 在正文里点一下让光标进去 → 切回来看结果。 - -``` -ok · 11ms -备忘录 (com.apple.Notes) -我们这个模块的接口设计得不太好,⟦光标⟧ -``` - -`⟦光标⟧` 是光标位置,左边上文右边下文。 - -| app | 预期 | -|---|---| -| 备忘录 / 文本编辑 | `ok` | -| VS Code / Notion / Claude 桌面版(Electron) | 能读到,但光标位置常常不准 | -| 微信 / 飞书 | `ok` | -| 浏览器普通输入框 | `ok` | -| 浏览器**密码框** | **必须 `blocked` / `secure_text_field`** | -| 终端 / iTerm / Warp | **必须 `blocked` / `blocked_app`** | -| 1Password | **必须 `blocked` / `blocked_app`** | - -后三行是安全验收,任何一条没拦住立刻停下来说。 - -**屏幕上显示出来的原文,就是会发给 LLM 的内容。** 哪个 app 里蹦出了你不希望离开这台机器的东西,那是必须知道的发现。 - ---- - -## 2. 开关关闭时行为不变 - -关掉开关 → 听写几句 → 日志里**不该有任何 `cursor-context` 行**。 - -关着的时候一次 AX 都不发,prompt 也和这个功能不存在时逐字节相同(有单测钉死)。 - ---- - -## 3. 上下文真的进 prompt 了吗 - -同一个 app、同一个位置,开/关各听一次同样的话: - -```bash -grep "effective_prompt_chars" ~/Library/Logs/OpenLess/openless.log | tail -2 -``` - -开着的那次应该多 **427 + 读到的字数**(427 = 上下文块固定措辞 314 + 注入防御条款 113)。 - -必须同一个 app 比——prompt 里带了前台应用名,「备忘录 (com.apple.Notes)」和「Claude (com.anthropic.claudefordesktop)」差 19 个字符,换 app 比会对不上账。 - -**更该盯的是 LLM 拿它干了什么。** 最容易翻车的是把上文复述进输出:光标前写着「这个模块的接口设计得不太好,」,你口述「还得再改」,它输出「这个模块的接口设计得不太好,还得再改」——把你已有的字又插了一遍。prompt 里明令禁止了,但那是软约束。 - ---- - -## 4. 手改 → 卡片 → 词条 - -前提:开关开着,**在备忘录这类原生 app 里测**(Electron 的通知不稳)。 - -**没有自动入库这条路了。** 任何一处手改都只会变成卡片上的一条建议,你点勾它才进词汇表。 - -早期版本让「中文改成英文」这一档静默入库,理由是「没人为了换语气把中文改成英文」。 -真机跑两天,自动收进去 5 条只有 1 条是对的: - -| 自动收的 | 实际是什么 | -|---|---| -| `Tailscale`(was telskill) | ✅ 唯一一条真纠错 | -| `ype`(was ap) | 逐字敲 `Type` 的中间态 | -| `ess`(was ice) | 同上 | -| `typeless`(was TypeScript) | 用户本来就要打这个词 | -| ` claude`(was cloud) | 带前导空格,永远匹配不上 | - -观察器看到的是**编辑过程中的每一帧**,而中间态和一次真纠错在文本上没有区别。分不出来就别猜。 - -### 4a. 卡片长什么样 - -1. 口述一句,把其中某个词改掉 -2. **把光标点到别处**(这是判定「你改完了」的信号) -3. 屏幕**右下角**弹出卡片: - -``` -要记住这个词吗? -扣德克斯 → Codex ✓ ✗ -大禹 → 大鱼 ✓ ✗ -``` - -- 点 **✓** → 进词汇表,落在分割线下面的「自动收集」区 -- 点 **✗** → 丢掉,什么都不记(没有拒绝名单,下次再改同一个词它还会问) -- 等 10 秒不动 → 整张卡片消失,同样什么都不记 -- 连着改两个词 → 合并到同一张卡片,倒计时重置 -- 逐条点完最后一条 → 卡片自己收起 - -**位置必须是右下角,不是屏幕正下方居中。** 居中那块正是你在写字的地方,卡片停十秒会 -直接盖住正在编辑的那一行 —— 这是真机上退回来的。 - -卡片只挡住它自己那一块的鼠标(窗口会缩到卡片大小,逐条点完还会跟着重算),周围照常能点。 - -### 4c. 该拒绝的时候确实拒绝了 - -| 操作 | 期待 | -|---|---| -| 改完切到别的 app 再改 | 无输出(观察器已解除) | -| 落字后等 60 秒再改 | 无输出(硬上限) | -| 落字后再听写一次,回头改第一段 | 无输出(新会话解除旧观察器) | -| 改你自己之前写的内容(不是它插的) | 无输出(只认落在插入文本里的改动) | -| 只是补几个字(纯插入) | 不学 | -| 把一个词删掉(纯删除) | 检测到但不入库 | -| 在聊天框里按回车发送 | 不该产生任何建议 ← 这条曾经翻过车 | -| 慢慢逐字打出一个英文词 | **可能**会弹出 `ap → ype` 这种半截建议 —— 点 ✗ 就行 | - -最后一条不是 bug,是这个设计的已知代价:半截和真纠错在文本上没有区别,粗筛拦不住。 -以前它会**静默入库**,现在最多是让你多点一次叉。 - ---- - -## 5. 词汇表页 - -``` -词汇表 - [你自己加的...] - ───────── 自动收集(N) [全部删除] - [自动收的...] -``` - -试一下「全部删除」,确认**手动加的不会被一起删掉**。 - -学来的词条在 `dictionary.json` 里是**追加到最后**的,手动添加的才插到最前。这条顺序是 -跟 ASR 词表预算的接口约定:预算把最前面的几条当保底席位(理由是「你刚手动加它,多半刚 -被它坑过」),点一下勾不该享受这个待遇。学来的词在 **LLM 热词块里立刻生效**(那一侧没有 -名额限制),ASR 侧则靠命中次数自己爬进预算。 - -早期版本往**纠正规则**里写过 learned 条目(现在不写了)。如果你的 `correction-rules.json` 里还有,纠正规则区有个「只看自动收集的」筛选可以把它们挑出来删。 - ---- - -## 6. 不会冻住界面 - -对着一个卡死的 app 触发听写。AX 调用 200ms 超时、整次读取 1.2 秒封顶、跑在独立线程,不占 tokio worker。 - ---- - -## 已知限制 - -1. **建议只在内存里**,OpenLess 重启就没了。卡片消失即当没发生——下次改同一个词会再问。 -2. **Electron 类 app 的光标位置常常不准**,日志里表现为 `before=0 after=N`(上文读成空)。上下文对润色的价值主要在上文,那种情况下收益有限。 -3. **风格包预览里看不到 ``**,跟 `front_app` 一样是运行时才有值的东西。 - ---- - -## 出问题时给我这些 - -```bash -# 相关日志(诊断细节是 debug 级别,日常不记;要更细的得改 LevelFilter 重编译) -grep -E "cursor-context|cursor context|vocab" ~/Library/Logs/OpenLess/openless.log | tail -100 - -# 学到的词条 -python3 -c "import json,os;[print(' ',e['phrase']) for e in json.load(open(os.path.expanduser('~/Library/Application Support/OpenLess/dictionary.json'))) if e.get('note')=='从手改中自动收集']" - -# 开关状态 -python3 -c "import json,os;print(json.load(open(os.path.expanduser('~/Library/Application Support/OpenLess/preferences.json'))).get('cursorContextEnabled'))" -``` - -`edit watch disarmed` 那一行带两个数字——收到几次通知、学到几处改动。这两个数字足够判断某个 app 到底发不发通知,也就是逐 app 的覆盖率数据。 diff --git a/openless-all/app/src-tauri/src/commands/dictionary.rs b/openless-all/app/src-tauri/src/commands/dictionary.rs index 8112a782..c7cbff4c 100644 --- a/openless-all/app/src-tauri/src/commands/dictionary.rs +++ b/openless-all/app/src-tauri/src/commands/dictionary.rs @@ -42,11 +42,7 @@ pub fn add_correction_rule( /// 卡片上点了勾:把这个词收进词汇表,打「自动收集」标记,随时能在词汇表页删掉。 #[tauri::command] -pub fn accept_pending_correction( - core: CoreState<'_>, - coord: CoordinatorState<'_>, - id: String, -) { +pub fn accept_pending_correction(core: CoreState<'_>, coord: CoordinatorState<'_>, id: String) { match core.accept_pending_correction(&id) { Ok(Some(suggestion)) => { log::info!( @@ -63,11 +59,7 @@ pub fn accept_pending_correction( /// 卡片上点了叉:丢掉这一条,什么都不记(没有拒绝名单)。 #[tauri::command] -pub fn reject_pending_correction( - core: CoreState<'_>, - coord: CoordinatorState<'_>, - id: String, -) { +pub fn reject_pending_correction(core: CoreState<'_>, coord: CoordinatorState<'_>, id: String) { if core.reject_pending_correction(&id) { coord.refresh_vocab_suggestion_presentation(!core.pending_corrections().is_empty()); } diff --git a/openless-all/app/src-tauri/src/commands/github_oauth.rs b/openless-all/app/src-tauri/src/commands/github_oauth.rs index 648724e8..406514b6 100644 --- a/openless-all/app/src-tauri/src/commands/github_oauth.rs +++ b/openless-all/app/src-tauri/src/commands/github_oauth.rs @@ -30,8 +30,7 @@ fn command_error(error: openless_core::BackendError) -> String { pub async fn github_device_flow_start( core: CoreState<'_>, ) -> Result { - core - .services() + core.services() .marketplace .start_device_flow() .await @@ -44,8 +43,7 @@ pub async fn github_device_flow_poll( core: CoreState<'_>, flow_id: String, ) -> Result { - core - .services() + core.services() .marketplace .poll_device_flow(flow_id) .await @@ -57,8 +55,7 @@ pub async fn github_device_flow_cancel( core: CoreState<'_>, flow_id: Option, ) -> Result<(), String> { - core - .services() + core.services() .marketplace .cancel_device_flow(flow_id) .await @@ -69,8 +66,7 @@ pub async fn github_device_flow_cancel( pub async fn marketplace_auth_status( core: CoreState<'_>, ) -> Result { - core - .services() + core.services() .marketplace .auth_status() .await @@ -79,8 +75,7 @@ pub async fn marketplace_auth_status( #[tauri::command] pub async fn marketplace_logout(core: CoreState<'_>) -> Result<(), String> { - core - .services() + core.services() .marketplace .logout() .await diff --git a/openless-all/app/src-tauri/src/commands/marketplace.rs b/openless-all/app/src-tauri/src/commands/marketplace.rs index 92de5eb0..2012c7f8 100644 --- a/openless-all/app/src-tauri/src/commands/marketplace.rs +++ b/openless-all/app/src-tauri/src/commands/marketplace.rs @@ -12,8 +12,7 @@ pub async fn marketplace_list( sort: Option, limit: Option, ) -> Result, String> { - core - .services() + core.services() .marketplace .list(openless_core::MarketplaceQuery { query, sort, limit }) .await @@ -25,8 +24,7 @@ pub async fn marketplace_detail( core: CoreState<'_>, pack_id: String, ) -> Result { - core - .services() + core.services() .marketplace .detail(pack_id) .await @@ -38,8 +36,7 @@ pub async fn marketplace_install( core: CoreState<'_>, pack_id: String, ) -> Result { - core - .services() + core.services() .marketplace .install(pack_id) .await @@ -70,8 +67,7 @@ pub async fn marketplace_upload( pack_id: String, origin_pack_id: Option, ) -> Result { - core - .services() + core.services() .marketplace .upload(pack_id, origin_pack_id) .await @@ -83,8 +79,7 @@ pub async fn marketplace_like( core: CoreState<'_>, pack_id: String, ) -> Result { - core - .services() + core.services() .marketplace .toggle_like(pack_id) .await @@ -92,12 +87,8 @@ pub async fn marketplace_like( } #[tauri::command] -pub async fn marketplace_delete( - core: CoreState<'_>, - pack_id: String, -) -> Result<(), String> { - core - .services() +pub async fn marketplace_delete(core: CoreState<'_>, pack_id: String) -> Result<(), String> { + core.services() .marketplace .delete(pack_id) .await @@ -106,8 +97,7 @@ pub async fn marketplace_delete( #[tauri::command] pub async fn marketplace_my_likes(core: CoreState<'_>) -> Result, String> { - core - .services() + core.services() .marketplace .my_likes() .await @@ -118,8 +108,7 @@ pub async fn marketplace_my_likes(core: CoreState<'_>) -> Result, St pub async fn marketplace_my_packs( core: CoreState<'_>, ) -> Result, String> { - core - .services() + core.services() .marketplace .my_packs() .await diff --git a/openless-all/app/src-tauri/src/coordinator_state.rs b/openless-all/app/src-tauri/src/coordinator_state.rs index 9876a5ad..079e5827 100644 --- a/openless-all/app/src-tauri/src/coordinator_state.rs +++ b/openless-all/app/src-tauri/src/coordinator_state.rs @@ -74,12 +74,7 @@ pub(crate) fn begin_session_state( focus_target: Option, front_app: Option, ) -> Option { - begin_session_state_with_id( - state, - focus_target, - front_app, - new_session_id(), - ) + begin_session_state_with_id(state, focus_target, front_app, new_session_id()) } /// 与 [`begin_session_state`] 相同,但允许宿主在进入 Coordinator 状态机前生成 diff --git a/openless-all/app/src-tauri/src/windows_ime_target.rs b/openless-all/app/src-tauri/src/windows_ime_target.rs index 7ada4a24..050593a8 100644 --- a/openless-all/app/src-tauri/src/windows_ime_target.rs +++ b/openless-all/app/src-tauri/src/windows_ime_target.rs @@ -55,8 +55,6 @@ mod tests { #[test] fn null_window_is_not_a_capture_target() { - assert!(!hwnd_is_present( - windows::Win32::Foundation::HWND::default() - )); + assert!(!hwnd_is_present(windows::Win32::Foundation::HWND::default())); } }