# App 与 Web 版本同步 正式 macOS App 在安装或更新后的首次启动同步本机 Web。只有复制 App、尚未打开时,不执行安装脚本。手机和远程服务器不参与这个过程。 ## 版本规则 | 当前 Web | App 行为 | | --- | --- | | 尚未安装 | 使用 App 携带的预编译服务和 Node 安装独立后台服务 | | 版本较旧,或同版本且提交是 App 构建的祖先 | 等待空闲后向上同步 | | 同一构建/同一干净提交 | 直接连接 | | 发布版本或同版本提交更新 | 保留当前 Web | | 同版本分支关系不明、无法识别的工作区快照 | 启动页提供“使用当前 App 覆盖同版本 Web”,由用户明确选择 | `build-info.json` 包含提交、构建序号、祖先提交、工作区状态和构建时间;构建序号仅用于展示,提交的先后必须有祖先关系,不能比较哈希字典序或文件时间。`runtime-manifest.json` 对完整服务包逐文件计算 SHA-256,并拒绝额外文件、丢失文件和符号链接。App 内置 Node 保持原签名,避免签名后改变已封存的服务包。许可证位于 `web-service/NODE-LICENSE.txt`。 成功后在数据目录的 `updates/desktop-sync.json` 记录本次 App 服务包校验值。再次打开同一构建不会重新同步,允许 Web 继续自行升级或经兼容性检查后单独回滚。该记录属于数据目录,不是全局 App 偏好。失败不写成功记录,启动页可重试。 ## 强制覆盖同版本 Web App 与 Web 的版本号相同,即使提交来自不同历史或属于工作区快照,也可以使用当前 App 的配套服务覆盖。启动页遇到构建关系不明时显示 **使用当前 App 覆盖同版本 Web**;进入 Web 后也可从 macOS 的 **夭夭** 菜单选择同名操作。 覆盖是一次性的明确操作:它跳过构建先后判定和旧的同步成功记录,使用当前 App 自带的完整服务包。仍须校验安装包、服务归属和空闲状态,保留聊天、设置、文件及已有服务配置,并先备份;失败自动恢复。服务忙碌时延后覆盖,不中断进行中的任务。后续普通启动恢复自动同步规则,不持续强制覆盖。不同版本号不使用此入口,升级或回退沿用原有流程。 同步助手通过 `force: true` 传递此选择;只有受信任的原生启动页和 App 菜单可以触发,Web 页面及其他窗口不能调用覆盖 IPC。`service_build_unknown` 仅表示可人工处理的同版本构建冲突,包损坏、权限或身份错误不会提供强制入口。 ## 统一后台服务 App 同步和 Web 的更新/回滚共用 `bin/lib/service-update.mjs` 的事务、发布目录、SQLite 更新互斥和原有 `active.lock`。App 同步使用包内服务,不下载或现场运行 npm 构建;Web 的更新入口仍按发布源准备新版本。两条路径都切换同一个 `current`。 首次接管时核对当前用户的 LaunchAgent、数据目录、端口、监听进程、SQLite 持有者和启动入口。已有地址、TLS、上游、通知凭据文件及其他环境配置保留。不接管未知监听进程、其他数据目录或远程服务器;TLS 身份无法正常验证时在停止前退出。 新服务通过仅限本机、无 Origin、私有随机凭据的接口协商暂停。只有没有进行中的普通聊天、团队运行、Runner 操作及 HTTP 写请求时才暂停新请求;正在运行的任务继续完成。暂停租约在没有持久更新事务时 15 秒过期,避免更新器在切换前退出导致永久暂停。 旧版没有暂停协议时,检查持久任务、普通聊天未完成事件和本机服务进程的活动连接。App 发现服务忙碌时立即延后同步,核验原服务身份后继续进入现有 Web;菜单显示“Web 同步待完成”,并提供“重试同步本机 Web”。不写同步成功记录。Web 自身的更新器仍最多等待一分钟。旧客户端关闭后可重试,不强行打断任务。 ## 数据与恢复 1. 在公共更新锁内准备并校验完整服务包,确认原服务可访问。 2. 等待空闲、暂停新请求,持久记录更新事务。 3. 停止原服务并确认进程、监听和数据库句柄释放;备份 Web 数据及文件。 4. 原子切换程序目录,启动新版本。验证完成前,新服务拒绝业务请求。 5. 用私有实例信息核对 PID、数据目录、实际提交及服务包校验值,并检查 `/healthz`、`/api/status`。 6. 验证成功后记录回退信息、解除暂停,最后记录 App 已同步。 启动或迁移失败时,恢复原程序、数据库结构、数据文件和配置。备份排除 `updates`、桌面浏览器数据、独立 Runner 状态及进程锁;这些内容不会被 Web 回退覆盖。中断事务由下一次 App 同步或更新器运行先行恢复。已经恢复数据但尚未启动成功的事务不会重复用旧备份覆盖数据。 成功升级后的手动回滚保留现有消息,要求升级前后及当前数据库结构一致。涉及结构变化或没有旧兼容性记录时拒绝直接降级,必须使用独立的数据恢复流程。失败更新期间的自动恢复只发生在业务请求尚未重新开放之前。 退出 App 后独立 Web 继续运行。App 自己托管的 Runner 仍按原生命周期退出;若需要退出 App 后仍执行电脑任务,应使用独立 Runner。源码开发模式默认不接管正式服务,仍使用隔离的 App 子进程。 ## 验证入口 - `tests/server/desktopSync.test.ts`:构建选择、包篡改、互斥、忙碌延后、一次同步、数据与结构恢复、中断恢复及降级兼容性。 - `tests/server/serviceInstance.test.ts`:本机权限、空闲协商、更新期间拒绝写入、事务提交后恢复。 - `desktop/service-sync.test.mjs`:实际 Electron App、包内 Node 和隔离 LaunchAgent 的安装、健康/构建核对、Web 更新入口、退出后继续运行及保留更新的 Web。 原生验收使用临时数据目录、端口、发布目录和由目录哈希派生的测试 LaunchAgent 标签;清理只作用于该测试服务,不替换正式 15300 服务。 启动页的 HTML、JavaScript、CSS 与 preload 都是必需打包资源,构建时逐一核验。`desktop/startup-feedback.test.mjs` 在真实 App 中制造同步助手失败,确认错误文本与“重试 / 查看日志”可见,再解除测试阻塞并点击重试进入 Web。只有 `service_busy` 会允许连接已验证的原服务;包校验、身份核对及其他错误继续显示在启动页。 2026-09-14 验证:25 项桌面测试及 23 项同步/服务实例/更新接口测试通过。新增真实 App 流程先安装同版本但无共同提交历史的服务,验证启动页明确提供覆盖入口,点击后构建切换成功、原文件和备份保留;Web 页面不能调用覆盖 IPC。另覆盖旧同步记录、忙碌服务、损坏包、跨版本拒绝及覆盖失败的数据恢复。 2026-09-09 验证:类型检查和生产/桌面构建通过;全量测试 787 项通过、6 项按环境跳过。原有 8 项桌面测试通过,新增真实 App/LaunchAgent 同步流程通过,覆盖退出后服务保留、较新 Web 保留、旧配置保留以及服务停止后再次打开 App 恢复独立服务。开发签名 App 的严格签名、服务包完整性及实际同步流程均已验证。此次修改不包含替换本机已安装 App 或正式 Web 服务。