Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

本文件记录面向用户和集成方的重要变化。完整的发布叙事、升级说明和下载入口见对应版本的中文发布说明;实现证据和测试门禁见技术发布说明。

## [Unreleased]

## [1.0.3] - 2026-09-15

- 已下载更新由用户决定安装:存储配置、写操作、Watch 和恢复状态不再阻止“重启并安装”。

- 每天首次启动的自动更新检查从 15 秒提前到 5 秒;当天后续启动仍不重复检查。

- 修复非法 UTF-8 被静默替换、数组 payload 误报同步成功、深层 JSON 导致整次准备中止:现在逐文件跳过并保留关联索引,日志显示具体原因。

- Node Sync/Switch/Watch 逐条跳过问题会话并保留关联或不确定 SQLite 行,正常数据继续;预览冻结候选与排除集合,写后收窄恢复范围。
Expand Down
112 changes: 58 additions & 54 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
<div align="center">

非法 UTF-8、数组 payload 或超出首行处理能力的数据会跳过并显示具体原因,关联索引保留;请处理后重新预览。不会自动转码或增加固定嵌套层数限制。

问题会话会被跳过,其关联索引保留原样;正常会话继续同步/切换。有跳过会显示“部分完成”,在预览、结果和操作日志可查看原因及本机完整路径(最多 200 项)。处理问题后重新预览即可纳入;全部历史跳过时,切换仍会备份并更新配置。数据库/目录/备份等全局故障仍停止,已写入时保留备份。诊断导出移除路径和索引标识。

# codex-provider-sync

### 切换 Provider 后,帮助 Codex 旧会话重新可用
Expand All @@ -25,11 +21,15 @@
**不保证跨 Provider / 账号的旧会话一定能继续或压缩**,也不处理登录、认证或加密内容。信息已对齐时,无需重复同步。

<p align="center">
<img src="images/README/provider-metadata-sync-flow.png" alt="Provider 元数据同步示意:保持当前配置不变,将会话文件与 SQLite 索引从 Provider A 对齐到 Provider B" width="760">
<img src="images/README/provider-metadata-sync-flow-zh.png" alt="Provider 元数据同步示意:保持当前配置不变,将会话文件与 SQLite 索引从 Provider A 对齐到 Provider B" width="760">

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid promising that synchronized sessions can resume

Each newly embedded localized diagram labels the post-sync outcome as “Session can resume” (and equivalents in Chinese, Japanese, and Korean), even though Provider alignment does not guarantee continuation when the destination Provider/account cannot decrypt encrypted_content or has other compatibility problems. Because this prominent visual contradicts the caveat immediately above it, replace the outcome with a claim limited to metadata alignment rather than session usability.

AGENTS.md reference: AGENTS.md:L15-L17

Useful? React with 👍 / 👎.

</p>

### 什么时候需要它

按你切换 Provider 的方式,选择对应操作:

- **已用 CCSwitch 等工具切换**:同步到当前配置中的 Provider。
- **希望在本工具中切换**:修改配置后,同步会话文件和索引中的 Provider。
- **希望在本工具中切换**:选择目标 Provider;工具会更新配置,再同步会话文件和索引中的 Provider。
- **Provider 信息已经一致**:不必重复同步;继续失败时应查看 Codex 的具体报错。

## 核心功能
Expand All @@ -41,12 +41,10 @@

## 下载 Windows 桌面版

**Windows x64**,无需 Node.js。
**Windows x64**,无需 Node.js。未签名;便携版须完整解压。

[下载最新正式版:安装版 / 便携 ZIP、版本说明与校验](https://github.com/Dailin521/codex-provider-sync/releases/latest)

未签名。安装版支持下载后确认安装更新;便携版须手动完整解压。1.0.3 支持大首行并跳过问题会话,让正常数据继续同步;1.0.1 用户需手动安装新版一次,详见[升级说明](docs/release-notes/v1.0.3-zh.md)。旧 .NET 版不能通过旧更新按钮迁移。

macOS/Linux Electron 包尚未发布;CLI / Web 的 npm 版本独立发布。

## 日常使用
Expand Down Expand Up @@ -83,68 +81,78 @@ codex-provider sync

CLI 写命令直接执行。切换 Provider、恢复备份、Watch、路径参数及 JSON 退出码见 [CLI 指南](docs/README_CLI_ZH.md);命令是否可用以安装版本的 `--help` 为准。

## 架构:一个共享核心,多种入口
## 一个核心,三个入口

CLI、Web 与 Electron 共用 **Node Core 的同一套业务用例和存储算法**。桌面版不通过启动 CLI、解析终端文本来执行同步,安装 CLI 也不会额外安装 Electron。
Windows 桌面版、Local Web 和 CLI 使用同一套同步、切换、备份与恢复逻辑;选择入口只影响操作方式,不影响同步结果。

```mermaid
flowchart TB
CLI["CLI / 脚本 / WSL"] --> Adapter["CLI 兼容适配器"]
Web["Web 界面"] --> Http["HttpCoreClient · 本地 Web Host"]
Desktop["Electron 桌面界面"] --> IPC["DesktopCoreClient · Preload / Main 窄 IPC"]
IPC --> Utility["Utility Process"]
flowchart LR
subgraph Entry["入口"]
Desktop["Windows 桌面版"]
Web["Local Web"]
CLI["CLI / 脚本 / WSL"]
end

subgraph Core["共享 Node Core"]
Facade["CoreFacade · 统一接口"]
UseCases["业务用例<br/>状态 / 同步 / 切换 / 备份与恢复<br/>历史 / 诊断与修复 / Watch"]
Runtime["OperationRuntime<br/>计划校验 / 并发协调 / 进度与取消"]
Storage["存储端口<br/>Config / Sessions / State DB / Global State"]
Backup["UndoBackup / RestoreRecovery<br/>备份与恢复基础设施"]
Facade --> UseCases
UseCases --> Runtime
UseCases --> Storage
UseCases --> Backup
Interface["统一操作入口"]
Operations["状态 / 同步 / 切换<br/>Watch / 修复 / 恢复"]
Runtime["计划校验 / 并发控制<br/>进度与取消"]
Interface --> Operations --> Runtime
end

Http --> Facade
Utility --> Facade
Adapter --> UseCases
Storage --> Data["config.toml / 会话文件 / SQLite / 工作区设置"]
Backup --> Managed["受管备份 / Restore journal"]
Desktop --> Interface
Web --> Interface
CLI --> Operations
Runtime --> Config["Codex 配置"]
Runtime --> Sessions["会话文件"]
Runtime --> SQLite["SQLite 索引"]
Operations --> Backup["备份与恢复"]
```

- **界面与 Host** 负责交互、传输和桌面能力,不重复实现 Provider 读写算法。
- **CoreFacade** 是新版 Web `/api/core` 与 Electron 的入口;CLI 及保留的旧 Web 写路由仍通过兼容适配层调用同一业务用例。
- **Sync、Repair、Restore 各司其职**:普通同步只对齐 Provider,专项修复需明确选择,恢复独立保留恢复前快照和补偿机制。
- **旧 .NET Windows/macOS** 是独立的 Legacy 实现,保留构建与兼容维护,不是 Node Core 的另一个分发壳。
- **桌面版**:日常双击使用。
- **Local Web**:在浏览器中操作,适合跨平台环境。
- **CLI**:适合脚本、自动化和 WSL。

普通同步只对齐 Provider;专项修复和恢复需要明确选择。旧 .NET Windows/macOS 版仍作为兼容实现维护。

模块归属、依赖方向和不可改变的读写约束,以 [当前 Node Core 架构](docs/architecture/NODE_CORE_ARCHITECTURE_ZH.md) 为准;总体路线见 [Electron + Node 架构基线](docs/VNEXT_ELECTRON_NODE_ARCHITECTURE_ZH.md)。
开发者可查看[当前 Node Core 架构](docs/architecture/NODE_CORE_ARCHITECTURE_ZH.md)和 [Electron + Node 架构基线](docs/VNEXT_ELECTRON_NODE_ARCHITECTURE_ZH.md)。

## 同步如何读写,速度取决于什么

普通同步从 `config.toml` 获取当前 Provider,只解析会话首行的 `session_meta`,再对齐会话文件与 SQLite 的 Provider 信息,**不扫描聊天正文来修复其他字段**。
同步只解析每个会话的首行元数据,并对齐会话文件和 SQLite 中的 Provider;聊天正文保持不变。

| 文件更新方式 | 适用情况 | 读写行为 |
| --- | --- | --- |
| 等长原地替换 | Provider 的 JSON 字面量 UTF-8 字节等长,且定位、身份及占用校验通过 | 定位覆盖 Provider 字节,保持文件身份、大小和正文不变 |
| 流式替换 | 有效首行不满足原地替换条件,如 Provider 字节长度不同 | 更新首行,逐字节复制正文到临时文件,再原子替换 |
- **满足原地写条件(包括 Provider 字节等长)**:直接改写 Provider,无需为替换生成整份会话副本。
- **其他有效首行**:更新首行后流式复制正文到新文件,再替换原文件。

以上策略自动选择,不需要 `--fast`,也不要求用户把 Provider 名称统一成固定长度。原地写失败不会强制退回整文件替换。
这两种方式会自动选择,无需设置加速选项,也不必把 Provider 名称改成相同长度。

**等长不代表整次操作只写几个字节。** 备份、校验、落盘及时间戳恢复仍有开销;大量文件或大体积历史仍可能耗时。概览中的“如何加快同步”提供建议,Windows 操作日志可查看复制、落盘、替换、清理和时间戳恢复的分项耗时。
速度主要取决于需要更新的会话数量;Provider 长度不同且历史文件较大时,还需要复制正文,耗时会增加。备份、写前校验、落盘和时间戳恢复也会占用时间。操作日志可查看各步骤耗时。

[工作原理与路径解析](docs/WORKING_PRINCIPLE_ZH.md) · [Provider I/O 不变量](docs/architecture/NODE_CORE_ARCHITECTURE_ZH.md#3-provider-io-不变量必须保持)

## 常见结果与注意事项
## 常见问题(FAQ)

### 已经用 CCSwitch 切换了,还需要做什么?

打开本工具,确认当前 Provider 是你要使用的,再点击“同步”。如果会话文件和索引中的 Provider 已经一致,就无需重复同步。

- **同步范围**:只对齐 Provider,不修改聊天正文、历史模型或 `threads.updated_at`,不读取或修改 `auth.json`。
- **部分完成**:不代表全部失败,也不会自动全量回滚。查看跳过项、失败阶段和备份,再重试或手动恢复。
- **数据已变化 / 状态待刷新**:手动刷新或重新预览,不要删除锁文件或数据库。
- **文件数与索引数不同**:不一定是故障,以 Provider 分布和同步状态为准。
- **仍无法继续**:若为加密内容或模型兼容问题,请回到原 Provider / 账号,或新建会话;同步不能解决这类问题。
- **存储位置**:以概览实际路径为准。Windows 的 WSL UNC SQLite 路径仅支持诊断;写入需进入对应 WSL 运行 CLI。
### 同步会修改聊天内容或登录信息吗?

备份数量在同一应用内统一管理,各 Codex Home 分别保留;桌面、Web 浏览器与 CLI 不共享偏好。保存数量设置不会立即删除备份。
不会。同步只对齐会话文件和 SQLite 索引中的 Provider,不修改聊天正文、历史模型或会话排序时间,也不读取或修改登录文件 `auth.json`。

### 为什么同步后,旧会话仍无法继续?

Provider 一致只是继续会话的一个条件。请查看 Codex 的具体报错;若涉及加密内容或模型兼容问题,可回到原 Provider / 账号,或新建会话。

### 显示“部分完成”怎么办?

先查看结果或操作日志中的原因。异常会话会跳过并保留关联索引,正常会话继续处理:格式或大小问题需处理数据后重新预览;占用或变化可等会话停止写入后再同步。已完成的修改不会自动全量回滚。

### 同步错了,如何恢复?

在“备份”中选择对应操作前的备份并恢复;CLI 可使用 `codex-provider restore <backup-dir>`。具体步骤见[桌面指南](docs/README_DESKTOP_ZH.md)或 [CLI 指南](docs/README_CLI_ZH.md)。

路径配置与 WSL 用法见 [CLI 指南](docs/README_CLI_ZH.md);同步耗时说明见[工作原理](docs/WORKING_PRINCIPLE_ZH.md)。

## 文档与开发

Expand All @@ -170,7 +178,3 @@ npm run desktop:build
感谢 [@tangquanwei](https://github.com/tangquanwei) 贡献本地 Web UI、聊天记录浏览和多语言文档基础,并通过 [PR #80](https://github.com/Dailin521/codex-provider-sync/pull/80) 带入 v0.5.0;感谢所有参与贡献和问题调查的朋友。

[贡献者](CONTRIBUTORS.md) · [GitHub Contributors](https://github.com/Dailin521/codex-provider-sync/graphs/contributors) · [LINUX DO 社区](https://linux.do/) · [MIT License](LICENSE)

### 大首行会话

Node 版本(Electron、CLI、Web)支持最高 128 MiB 的会话首行元数据(UTF-8 字节,不含换行);超限或格式无效的数据会跳过并记录原因,正常部分继续同步。此限制不是整个聊天记录的大小限制。
12 changes: 1 addition & 11 deletions apps/desktop/src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,17 +269,7 @@ if (!app.requestSingleInstanceLock()) {
onStatus: (status) => {
if (mainWindow && !mainWindow.isDestroyed()) mainWindow.webContents.send(DESKTOP_IPC_CHANNELS.updateEvent, status);
},
supervisor,
hasActiveWatches: () => activeWatchCount > 0,
verifyNoActiveWatches: async () => (
(await ipcRegistration?.verifyNoActiveWatchesForRestart()) === "clear"
),
verifyRecoveryState: () => supervisor!.verifyProfilesSafeForRestart(
profiles.list().map((profile) => ({
profileId: profile.id,
profileRevision: profile.revision
}))
)
supervisor
});
ipcRegistration = registerDesktopIpc({
ipcMain,
Expand Down
22 changes: 0 additions & 22 deletions apps/desktop/src/main/update-policy.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import type { CoreRuntimeSupervisor } from "./runtime-supervisor.js";

export type DesktopUpdateUnavailableReason =
| "not-packaged"
| "not-authorized"
Expand All @@ -20,12 +18,6 @@ export interface DesktopUpdateAvailabilityInput {
configured: boolean;
}

export interface DesktopInstallGateInput {
supervisor: Pick<CoreRuntimeSupervisor, "snapshot">;
hasActiveWatches: boolean;
recoveryVerified: boolean;
}

export function supportedUpdateTarget(platform: NodeJS.Platform, arch: string): boolean {
return (platform === "win32" && arch === "x64")
|| (platform === "darwin" && (arch === "x64" || arch === "arm64"))
Expand All @@ -41,17 +33,3 @@ export function getDesktopUpdateUnavailableReason(
if (!input.configured) return "not-configured";
return null;
}

export function getDesktopInstallBlockedReason(
input: DesktopInstallGateInput
): DesktopInstallBlockedReason | null {
if (input.supervisor.snapshot.recoveryBlocked) {
return "pending-recovery";
}
if (input.supervisor.snapshot.writeInProgress) {
return "write-in-progress";
}
if (input.hasActiveWatches) return "watch-active";
if (!input.recoveryVerified) return "recovery-unverified";
return null;
}
Loading