云端设置同步(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(引擎选择)以及输入设备。- 计划风格。 本计划给出接口和测试契约,而非完整代码。同一会话会立即执行它,因此把代码写两遍只会造成重复。
任务¶
- 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 节表格的每一行。 - 允许列表(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 不读取任何机密。 - 本地表(alembic
0014_cloud_sync、_BASE_SCHEMA、cloud_sync/store.py)。涵盖状态、条目(合并基准)、outbox(每个条目唯一)以及带序列号的渲染进程镜像。测试:从 pre-0014 快照升级;outbox 的 upsert 保持一行及其键;镜像的since查询。 - 变更钩子(Change hooks)(
core/change_hooks.py,调用方为prefs、settings_store和镜像路由)。提供notify(source, key)、from_sync()上下文和subscribe(cb)。测试:每个允许列表中的键恰好发出一次变更;from_sync内部的写入不发出任何变更;非允许列表中的键不发出任何变更。 - API 客户端(
cloud_sync/client.py)。一个异步httpx客户端,涵盖设备、集合、用量、变更(changes)、put/delete/batch,并将错误映射为SyncApiError(status, code, details, retry_after)。测试:通过httpx.MockTransport验证请求结构和请求头;错误映射(包括Retry-After);If-Match编码。 - 引擎(
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)。 - 事件流(
cloud_sync/events.py)。包含一个 SSE 解析器和一个 runner;runner 具有 60 秒停滞超时、1–60 秒且带 ±20% 抖动的退避、bye/revoked处理,以及 60 秒轮询回退。测试:解析器处理head、注释、bye和revoked;停滞触发重连;退避序列。 - 服务与路由(
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},以及用于渲染进程镜像的localGET/PUT。测试:没有 token 就不发起 Cloud 调用;stop取消循环和事件流;重新开启同步时,先完成完整拉取再进行任何推送;路由契约。 - 主进程(Main)。 当
syncEnabled变为关闭时,main 向本地后端 POST/api/cloud-sync/stop;它绝不会对远程后端这样做。桥接响应携带organization_id和sync_api。测试:关闭开关会调用 stop;远程模式不发起调用;桥接载荷。 - 渲染进程(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 状态。 - 文档与验证。
HANDOFF.md记录 S1-23 的 dictation 列表;STRUCTURE.md记录服务数量;运行完整测试套件。
本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 om/VoiceStudio