跳转至

云端设置同步(S1)实施计划

供 agentic workers 使用: 由编写本计划的同一会话内联执行(superpowers:executing-plans)。每个任务都是测试先行;每个任务中指定的测试即为契约。

目标: 通过 VoiceStudio Cloud(/v1/sync)在用户的设备之间同步设置、快捷方式和图库收藏;每个设备可选择加入,并可被 S0 开关立即停止。

架构: 后端包 backend/services/cloud_sync/ 运行引擎:拉取 GET /changes,针对最后一致版本做三方合并后应用,再使用 If-Match 推送发件箱(outbox)。所有令牌都通过 services.cloud_account.access_token() 读取,因此主进程(main)的开关仍是唯一闸门。本地值来自三个存储。prefs.json 和设置表在后端接入。渲染进程的 localStorage 通过渲染进程的 syncedStorage 包装器镜像到后端,拉取到的值也以相同方式流回渲染进程。当同步被关闭时,main 会立即告知后端停止。

技术栈: Python 3(httpx 异步、sqlite3、Alembic)、FastAPI 路由、React 渲染进程、Vitest 和 pytest。

规范: account_login/desktop-app-guide.md 第 3–8 节和第 10 节、account_login/sync-api.md 和 account_login/sync-collections.md。这些副本会被 git 忽略;规范权威版本位于 vssaas 的 docs/specs/ 中。

基于: docs/superpowers/plans/2026-10-16-account-sign-in-and-cloud-switches.md(S0)。

全局约束

  • 关闭就是关闭。每个 Cloud 请求都会先调用 cloud_account.access_token();返回 None 就意味着不发起任何调用。main 的 POST /api/cloud-sync/stop 会立即取消同步循环、事件流和定时器。同步停止时绝不删除本地数据。
  • 绝不同步机密信息(secret.*、hf_token)、绝对路径、硬件或引擎选择(compute_device、*_backend、模型 ID)、分析用的 installation_id,以及 sync-collections.md 中设备本地的键。
  • ID 由 secrets.choice 生成 16 位 Base62 字符。幂等键(idempotency key)每个 outbox 行只创建一次,重试时复用,收到 409 后替换。
  • 应用拉取到的变更时绝不会将其重新排队。from_sync 上下文会抑制钩子(hooks)。
  • 绝不同时运行两轮。outbox 中每个条目只占一行。
  • 值按规范 JSON(canonical JSON)比较:键排序,无空白。
  • 本地化:每个新字符串都要放入全部 21 个 locale。对等原则:所有 OS 上的行为一致。
  • 提交不包含 AI 署名(CLAUDE.md)。

裁决(规划期间做出)

  • 表名。 表名为 cloud_sync_state、cloud_sync_items、cloud_sync_outbox 和 cloud_sync_local(渲染进程镜像),而非指南中的 sync_*。VoiceStudioPro 已经有一个 sync_state 表(迁移 0019,文件夹同步),模式不同,而两个应用共享这份代码。如果出错,代价是重命名四张表。
  • 设备与组织。 token 桥接响应还会携带 organization_id(personal_organization_id)和 sync_api 基础 URL,因此后端不需要自己的环境表。
  • 渲染进程存储。 渲染进程的键通过其现有的回环 HTTP 通道(apiJson)到达后端,而不是通过 main。渲染进程 → 后端是其他渲染进程设置已经使用的路径。如果出错,代价为零;指南中的“via main”只是一个建议。
  • 收藏。 voicestudio.gallery.favorites.v1 是一个 ID 数组,因此镜像会在首次看到每个 ID 时记录 added_at。
  • 快捷方式。 shortcuts.global 将 dictation.toggle 映射到渲染进程桥接的 capture 快捷方式 API,并通过现有的 setter 应用,因此 main 的文件保持唯一写入者。
  • dictation 组。 以下列表会发送回 vssaas(S1-23):aecEnabled(localStorage),以及 prefs 的 dictation.mode、dictation.enabled 和 dictation.prompt。排除项:dictation.model_id 和 dictation.silent_models(引擎选择)以及输入设备。
  • 计划风格。 本计划给出接口和测试契约,而非完整代码。同一会话会立即执行它,因此把代码写两遍只会造成重复。

任务

  1. ID 与合并(cloud_sync/ids.py、cloud_sync/merge.py)。提供 new_sync_id()、canonical(v) 和 merge3(base, local, remote) -> Merge(values, conflicts)。条目层面:merge_item(base, local, remote) 处理墓碑(tombstone),其中编辑优先于删除。测试:指南中的六个用例,以及第 7 节表格的每一行。
  2. 允许列表(Allowlist)(cloud_sync/allowlist.py)。它保存 settings(appearance、generation-defaults、review、transcription、dictation、history、llm)、shortcuts.global、favorites.gallery 和 device-preferences 的条目定义。提供 read_values(collection, item_id) 和 write_values(collection, item_id, values),数据来源为 prefs、设置表和渲染进程镜像。还提供 items_for(choices) 和 RENDERER_KEYS。测试:每个允许列表中的键都有读取器和写入器;被拒绝的键永远不会被读取;投影保留被排除的 clone.settings 字段;llm 不读取任何机密。
  3. 本地表(alembic 0014_cloud_sync、_BASE_SCHEMA、cloud_sync/store.py)。涵盖状态、条目(合并基准)、outbox(每个条目唯一)以及带序列号的渲染进程镜像。测试:从 pre-0014 快照升级;outbox 的 upsert 保持一行及其键;镜像的 since 查询。
  4. 变更钩子(Change hooks)(core/change_hooks.py,调用方为 prefs、settings_store 和镜像路由)。提供 notify(source, key)、from_sync() 上下文和 subscribe(cb)。测试:每个允许列表中的键恰好发出一次变更;from_sync 内部的写入不发出任何变更;非允许列表中的键不发出任何变更。
  5. API 客户端(cloud_sync/client.py)。一个异步 httpx 客户端,涵盖设备、集合、用量、变更(changes)、put/delete/batch,并将错误映射为 SyncApiError(status, code, details, retry_after)。测试:通过 httpx.MockTransport 验证请求结构和请求头;错误映射(包括 Retry-After);If-Match 编码。
  6. 引擎(cloud_sync/engine.py)。提供 run_once、pull(带分页,且游标与已应用条目一起保存)、apply_remote 和 push;其中 push 处理 409(合并并重试,最多 3 轮)、413(暂停并显示“full”)、403(not_entitled/collection_disabled/device_revoked/account_suspended)、429 和 5xx。它还包括空基准的首次同步合并、410 时的完全重新同步、超过 10 行等待时的批量处理、大规模删除保护(集合的 >20 或 >25%)、冲突说明和单轮锁。测试:针对假服务器验证指南第 11 节第 5–7 项(merge、outbox、pull)。
  7. 事件流(cloud_sync/events.py)。包含一个 SSE 解析器和一个 runner;runner 具有 60 秒停滞超时、1–60 秒且带 ±20% 抖动的退避、bye/revoked 处理,以及 60 秒轮询回退。测试:解析器处理 head、注释、bye 和 revoked;停滞触发重连;退避序列。
  8. 服务与路由(cloud_sync/service.py、api/routers/cloud_sync.py、main.py 接线、扩展后的桥接响应)。/api/cloud-sync 下的路由:status、PUT choices(开启、切换、“同时从云端移除”)、run、stop、devices、devices/{id}/revoke、confirm-deletes、use-choices-of/{device_id},以及用于渲染进程镜像的 local GET/PUT。测试:没有 token 就不发起 Cloud 调用;stop 取消循环和事件流;重新开启同步时,先完成完整拉取再进行任何推送;路由契约。
  9. 主进程(Main)。 当 syncEnabled 变为关闭时,main 向本地后端 POST /api/cloud-sync/stop;它绝不会对远程后端这样做。桥接响应携带 organization_id 和 sync_api。测试:关闭开关会调用 stop;远程模式不发起调用;桥接载荷。
  10. 渲染进程(Renderer)。 syncedStorage(为 RENDERER_KEYS 包装 setItem/removeItem,向镜像 POST,并通过 storage 事件应用拉取到的值)、快捷方式桥接,以及 设置 → 账户 → 同步(Settings → Account → Sync):带 Pro 锁的“选择要同步的内容”(Choose what to sync)、暂停行、大小和“使用与……相同的选择”(Use the same choices as …)、带“移除”(Remove)的设备列表、指南第 10 节的消息、大规模删除确认和冲突通知。所有字符串都放入 21 个 locale。测试:包装器每次变更只 POST 一次,且绝不回显拉取到的值;UI 状态。
  11. 文档与验证。 HANDOFF.md 记录 S1-23 的 dictation 列表;STRUCTURE.md 记录服务数量;运行完整测试套件。

本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 om/VoiceStudio