app-web-sync.md 8.0 KB

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 偏好。失败不写成功记录,启动页可重试。

取消已安装的实验构建

回退 Git 源码不会回退已经安装的独立 Web。同一版本号下,若现有 Web 的提交包含回退后 App 的提交,自动同步仍会保留现有 Web;重新编译或复制相同源码的 App 不会移除其界面功能。

需要让修正版自动替换实验构建时,应在恢复后的源码上增加补丁版本,再重新打包。例如 v0.4.21 会将仍运行实验功能的 v0.4.20 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 服务。