diff --git a/CHANGELOG.md b/CHANGELOG.md index 31eedc1..d9e1d6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,12 @@ 本文件记录面向用户和集成方的重要变化。完整的发布叙事、升级说明和下载入口见对应版本的中文发布说明;实现证据和测试门禁见技术发布说明。 -## [Unreleased] - ## [1.0.3] - 2026-09-15 +- 已下载更新由用户决定安装:存储配置、写操作、Watch 和恢复状态不再阻止“重启并安装”。 + +- 每天首次启动的自动更新检查从 15 秒提前到 5 秒;当天后续启动仍不重复检查。 + - 修复非法 UTF-8 被静默替换、数组 payload 误报同步成功、深层 JSON 导致整次准备中止:现在逐文件跳过并保留关联索引,日志显示具体原因。 - Node Sync/Switch/Watch 逐条跳过问题会话并保留关联或不确定 SQLite 行,正常数据继续;预览冻结候选与排除集合,写后收窄恢复范围。 diff --git a/README.md b/README.md index 41ea00a..3badaf8 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,5 @@
-非法 UTF-8、数组 payload 或超出首行处理能力的数据会跳过并显示具体原因,关联索引保留;请处理后重新预览。不会自动转码或增加固定嵌套层数限制。 - -问题会话会被跳过,其关联索引保留原样;正常会话继续同步/切换。有跳过会显示“部分完成”,在预览、结果和操作日志可查看原因及本机完整路径(最多 200 项)。处理问题后重新预览即可纳入;全部历史跳过时,切换仍会备份并更新配置。数据库/目录/备份等全局故障仍停止,已写入时保留备份。诊断导出移除路径和索引标识。 - # codex-provider-sync ### 切换 Provider 后,帮助 Codex 旧会话重新可用 @@ -25,11 +21,15 @@ **不保证跨 Provider / 账号的旧会话一定能继续或压缩**,也不处理登录、认证或加密内容。信息已对齐时,无需重复同步。

- Provider 元数据同步示意:保持当前配置不变,将会话文件与 SQLite 索引从 Provider A 对齐到 Provider B + Provider 元数据同步示意:保持当前配置不变,将会话文件与 SQLite 索引从 Provider A 对齐到 Provider B

+### 什么时候需要它 + +按你切换 Provider 的方式,选择对应操作: + - **已用 CCSwitch 等工具切换**:同步到当前配置中的 Provider。 -- **希望在本工具中切换**:修改配置后,同步会话文件和索引中的 Provider。 +- **希望在本工具中切换**:选择目标 Provider;工具会更新配置,再同步会话文件和索引中的 Provider。 - **Provider 信息已经一致**:不必重复同步;继续失败时应查看 Codex 的具体报错。 ## 核心功能 @@ -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 版本独立发布。 ## 日常使用 @@ -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["业务用例
状态 / 同步 / 切换 / 备份与恢复
历史 / 诊断与修复 / Watch"] - Runtime["OperationRuntime
计划校验 / 并发协调 / 进度与取消"] - Storage["存储端口
Config / Sessions / State DB / Global State"] - Backup["UndoBackup / RestoreRecovery
备份与恢复基础设施"] - Facade --> UseCases - UseCases --> Runtime - UseCases --> Storage - UseCases --> Backup + Interface["统一操作入口"] + Operations["状态 / 同步 / 切换
Watch / 修复 / 恢复"] + Runtime["计划校验 / 并发控制
进度与取消"] + 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 `。具体步骤见[桌面指南](docs/README_DESKTOP_ZH.md)或 [CLI 指南](docs/README_CLI_ZH.md)。 + +路径配置与 WSL 用法见 [CLI 指南](docs/README_CLI_ZH.md);同步耗时说明见[工作原理](docs/WORKING_PRINCIPLE_ZH.md)。 ## 文档与开发 @@ -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 字节,不含换行);超限或格式无效的数据会跳过并记录原因,正常部分继续同步。此限制不是整个聊天记录的大小限制。 diff --git a/apps/desktop/src/main/index.ts b/apps/desktop/src/main/index.ts index 8adbd10..d179c5a 100644 --- a/apps/desktop/src/main/index.ts +++ b/apps/desktop/src/main/index.ts @@ -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, diff --git a/apps/desktop/src/main/update-policy.ts b/apps/desktop/src/main/update-policy.ts index 38c8bc0..c7c0766 100644 --- a/apps/desktop/src/main/update-policy.ts +++ b/apps/desktop/src/main/update-policy.ts @@ -1,5 +1,3 @@ -import type { CoreRuntimeSupervisor } from "./runtime-supervisor.js"; - export type DesktopUpdateUnavailableReason = | "not-packaged" | "not-authorized" @@ -20,12 +18,6 @@ export interface DesktopUpdateAvailabilityInput { configured: boolean; } -export interface DesktopInstallGateInput { - supervisor: Pick; - hasActiveWatches: boolean; - recoveryVerified: boolean; -} - export function supportedUpdateTarget(platform: NodeJS.Platform, arch: string): boolean { return (platform === "win32" && arch === "x64") || (platform === "darwin" && (arch === "x64" || arch === "arm64")) @@ -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; -} diff --git a/apps/desktop/src/main/updater.ts b/apps/desktop/src/main/updater.ts index 23f76b8..483eb32 100644 --- a/apps/desktop/src/main/updater.ts +++ b/apps/desktop/src/main/updater.ts @@ -3,7 +3,6 @@ import type { DesktopUpdateStatus } from "../shared/update-types.js"; import { - getDesktopInstallBlockedReason, getDesktopUpdateUnavailableReason } from "./update-policy.js"; import type { CoreRuntimeSupervisor } from "./runtime-supervisor.js"; @@ -27,8 +26,6 @@ export interface DesktopUpdaterPort { quitAndInstall(isSilent?: boolean, isForceRunAfter?: boolean): void; } -export type DesktopRecoveryVerification = "clear" | "blocked" | "unverifiable"; - export interface DesktopUpdateControllerOptions { ignoredVersion?: string | null; saveIgnoredVersion?(version: string | null): Promise; @@ -47,10 +44,7 @@ export interface DesktopUpdateControllerOptions { appVersion: string; releaseAuthorized: boolean; configured: boolean; - supervisor: Pick; - hasActiveWatches(): boolean; - verifyNoActiveWatches(): Promise; - verifyRecoveryState(): Promise; + supervisor: Pick; beforeInstall?(): Promise; createPort?: () => Promise; setTimeoutImpl?: typeof setTimeout; @@ -113,10 +107,7 @@ export class DesktopUpdateController { readonly #appVersion: string; readonly #manualUpdates: DesktopUpdateControllerOptions["manualUpdates"]; readonly #onStatus: DesktopUpdateControllerOptions["onStatus"]; - readonly #supervisor: Pick; - readonly #hasActiveWatches: () => boolean; - readonly #verifyNoActiveWatches: () => Promise; - readonly #verifyRecoveryState: () => Promise; + readonly #supervisor: Pick; readonly #beforeInstall: () => Promise; readonly #createPort: () => Promise; readonly #setTimeout: typeof setTimeout; @@ -127,7 +118,6 @@ export class DesktopUpdateController { #reason: DesktopUpdateReason | undefined; #version: string | undefined; #progressPercent: number | undefined; - #recoveryVerification: "unknown" | "clear" | "blocked" = "unknown"; #restartPending = false; #releaseFailedInstall: (() => void) | null = null; #port: DesktopUpdaterPort | null = null; @@ -147,9 +137,6 @@ export class DesktopUpdateController { this.#appVersion = options.appVersion; this.#onStatus = options.onStatus; this.#supervisor = options.supervisor; - this.#hasActiveWatches = options.hasActiveWatches; - this.#verifyNoActiveWatches = options.verifyNoActiveWatches; - this.#verifyRecoveryState = options.verifyRecoveryState; this.#beforeInstall = options.beforeInstall ?? (async () => {}); this.#createPort = options.createPort ?? (() => createProductionUpdaterPort({ allowPrerelease: options.appVersion.includes("-") @@ -181,21 +168,10 @@ export class DesktopUpdateController { ...(this.#progressPercent !== undefined ? { progressPercent: this.#progressPercent } : {}) }; if (this.#state !== "downloaded") return status; - const blocked = this.#recoveryVerification === "blocked" - ? "pending-recovery" - : getDesktopInstallBlockedReason({ - supervisor: this.#supervisor, - hasActiveWatches: this.#hasActiveWatches(), - recoveryVerified: this.#recoveryVerification === "clear" - }); - return { - ...status, - installAllowed: blocked === null, - ...(blocked ? { installBlockedReason: blocked } : {}) - }; + return { ...status, installAllowed: true }; } - scheduleInitialCheck(delayMs = 15_000): void { + scheduleInitialCheck(delayMs = 5_000): void { if (this.#disposed || this.#unavailableReason || !this.#claimStartupCheck || this.#startupScheduled) return; this.#startupScheduled = true; const timer = this.#setTimeout(() => { @@ -231,7 +207,6 @@ export class DesktopUpdateController { this.#reason = undefined; this.#version = undefined; this.#progressPercent = undefined; - this.#recoveryVerification = "unknown"; this.#notify(); try { if (this.#manualUpdates) { @@ -308,7 +283,6 @@ export class DesktopUpdateController { this.#state = "downloaded"; this.#progressPercent = 100; } - if (this.#state === "downloaded") await this.#refreshRecoveryVerification(); } catch { this.#fail("download-failed"); } @@ -328,38 +302,18 @@ export class DesktopUpdateController { if (this.#installPromise) return this.#installPromise; const pending = (async () => { const restartLease = this.#supervisor.tryBeginRestartInstall(); - if (!restartLease) { - this.#recoveryVerification = "unknown"; - return this.status; - } this.#restartPending = true; let retainRestartGate = false; try { - await restartLease.waitForWrites(); - if (this.#hasActiveWatches()) { - let noActiveWatches = false; - try { - noActiveWatches = await this.#verifyNoActiveWatches(); - } catch {} - if (!noActiveWatches) return this.status; - } - this.#recoveryVerification = "unknown"; - const recoveryVerification = await this.#refreshRecoveryVerification(); - if (this.#immediateInstallBlock() || recoveryVerification !== "clear") { - return this.status; - } + // Installation is the user's decision. Storage checks and write draining + // belong to Core operations / normal shutdown, never update admission. this.#state = "installing"; this.#notify(); const port = await this.#ensurePort(); await this.#beforeInstall(); - if (this.#immediateInstallBlock()) { - this.#state = "downloaded"; - this.#recoveryVerification = "unknown"; - return this.status; - } this.#releaseFailedInstall = () => { this.#releaseFailedInstall = null; - restartLease.release(); + restartLease?.release(); this.#restartPending = false; }; port.quitAndInstall(false, true); @@ -370,7 +324,7 @@ export class DesktopUpdateController { } finally { if (!retainRestartGate) { this.#releaseFailedInstall = null; - restartLease.release(); + restartLease?.release(); this.#restartPending = false; } } @@ -397,33 +351,6 @@ export class DesktopUpdateController { this.#listeners.clear(); } - #immediateInstallBlock(): boolean { - return this.#supervisor.snapshot.recoveryBlocked - || this.#supervisor.snapshot.writeInProgress - || this.#hasActiveWatches(); - } - - async #refreshRecoveryVerification(): Promise<"unknown" | "clear" | "blocked"> { - if (this.#immediateInstallBlock()) { - this.#recoveryVerification = this.#supervisor.snapshot.recoveryBlocked - ? "blocked" - : "unknown"; - return this.#recoveryVerification; - } - try { - const result = await this.#verifyRecoveryState(); - this.#recoveryVerification = result === "clear" - ? "clear" - : result === "blocked" - ? "blocked" - : "unknown"; - } catch { - this.#recoveryVerification = "unknown"; - } - this.#notify(); - return this.#recoveryVerification; - } - async #ensurePort(): Promise { if (this.#port) return this.#port; if (!this.#portPromise) { @@ -461,7 +388,6 @@ export class DesktopUpdateController { this.#reason = undefined; this.#version = version; this.#progressPercent = undefined; - this.#recoveryVerification = "unknown"; }); bind("update-not-available", () => { if (this.#restartPending) return; @@ -469,7 +395,6 @@ export class DesktopUpdateController { this.#reason = undefined; this.#version = undefined; this.#progressPercent = undefined; - this.#recoveryVerification = "unknown"; }); bind("download-progress", (progress) => { if (this.#state !== "downloading") return; @@ -487,7 +412,6 @@ export class DesktopUpdateController { this.#reason = undefined; this.#version = version; this.#progressPercent = 100; - void this.#refreshRecoveryVerification(); }); bind("error", () => { if (this.#state === "installing" && this.#releaseFailedInstall) { @@ -505,7 +429,6 @@ export class DesktopUpdateController { this.#reason = reason; this.#version = undefined; this.#progressPercent = undefined; - this.#recoveryVerification = "unknown"; this.#notify(); } diff --git a/apps/desktop/tests/update-policy.test.mjs b/apps/desktop/tests/update-policy.test.mjs index 6227a55..29ceae2 100644 --- a/apps/desktop/tests/update-policy.test.mjs +++ b/apps/desktop/tests/update-policy.test.mjs @@ -2,25 +2,10 @@ import assert from "node:assert/strict"; import test from "node:test"; import { - getDesktopInstallBlockedReason, getDesktopUpdateUnavailableReason, supportedUpdateTarget } from "../dist/main/update-policy.js"; -function blocked(overrides = {}) { - return getDesktopInstallBlockedReason({ - hasActiveWatches: false, - recoveryVerified: true, - supervisor: { - snapshot: { - recoveryBlocked: false, - writeInProgress: false - } - }, - ...overrides - }); -} - function unavailable(overrides = {}) { return getDesktopUpdateUnavailableReason({ isPackaged: false, @@ -32,18 +17,6 @@ function unavailable(overrides = {}) { }); } -test("update install policy prioritizes recovery, writes, Watch and unverifiable state", () => { - assert.equal(blocked({ - supervisor: { snapshot: { recoveryBlocked: true, writeInProgress: true } } - }), "pending-recovery"); - assert.equal(blocked({ - supervisor: { snapshot: { recoveryBlocked: false, writeInProgress: true } } - }), "write-in-progress"); - assert.equal(blocked({ hasActiveWatches: true }), "watch-active"); - assert.equal(blocked({ recoveryVerified: false }), "recovery-unverified"); - assert.equal(blocked(), null); -}); - test("update policy is unavailable until a supported packaged channel is configured", () => { assert.equal(unavailable(), "not-packaged"); assert.equal(unavailable({ isPackaged: true, platform: "freebsd" }), "unsupported-target"); diff --git a/apps/desktop/tests/updater.test.mjs b/apps/desktop/tests/updater.test.mjs index 921ceb0..4405ef8 100644 --- a/apps/desktop/tests/updater.test.mjs +++ b/apps/desktop/tests/updater.test.mjs @@ -127,66 +127,54 @@ test("updater exposes a redacted Main-only check, download and install state mac assert.equal(port.listenerCount("update-available"), 0); }); -test("updater blocks install for writes, Watch, recovery and unverifiable preflight", async () => { - for (const scenario of ["write", "watch", "blocked", "unverifiable"]) { - const { controller, port, snapshot, state } = fixture(); +test("downloaded updates install without consulting writes, Watch or recovery", async () => { + for (const scenario of ["write", "watch", "blocked", "unverifiable", "failed-query"]) { + let checks = 0; + const forbidden = () => { checks++; throw new Error("storage must not be queried"); }; + const { controller, port, snapshot, state } = fixture({ + hasActiveWatches: forbidden, + verifyNoActiveWatches: forbidden, + verifyRecoveryState: forbidden + }); + snapshot.writeInProgress = scenario === "write"; + snapshot.recoveryBlocked = scenario === "blocked"; + state.watches = scenario === "watch"; + state.verification = scenario; + state.waitForWrites = forbidden; await controller.check(); - await controller.download(); - if (scenario === "write") snapshot.writeInProgress = true; - if (scenario === "watch") state.watches = true; - if (scenario === "blocked") state.verification = "blocked"; - if (scenario === "unverifiable") state.verification = "unverifiable"; + const downloaded = await controller.download(); + assert.equal(downloaded.installAllowed, true, scenario); + assert.equal(downloaded.installBlockedReason, undefined, scenario); const result = await controller.install(); - assert.equal(result.state, "downloaded", scenario); - assert.equal(result.installAllowed, false, scenario); - assert.equal(result.installBlockedReason, scenario === "write" - ? "write-in-progress" - : scenario === "watch" - ? "watch-active" - : scenario === "blocked" - ? "pending-recovery" - : "recovery-unverified"); - assert.equal(controller.restartPending, false, scenario); - assert.equal(port.installs, 0, scenario); + assert.equal(result.state, "installing", scenario); + assert.equal(port.installs, 1, scenario); + assert.equal(checks, 0, scenario); controller.dispose(); } }); -test("updater closes admission, drains an already admitted Watch, and reopens without installing", async () => { - const { controller, port, snapshot, state } = fixture(); +test("unavailable runtime lease does not veto an explicit installation", async () => { + const { controller, port } = fixture({ supervisor: { tryBeginRestartInstall: () => null } }); await controller.check(); await controller.download(); - let releaseWrite; - state.waitForWrites = () => new Promise((resolve) => { releaseWrite = resolve; }); - snapshot.writeInProgress = true; - const installing = controller.install(); - await new Promise((resolve) => setImmediate(resolve)); - assert.equal(controller.restartPending, true); - assert.equal(state.gateClosed, true); - assert.equal(port.installs, 0); - state.watches = true; - snapshot.writeInProgress = false; - releaseWrite(); - const result = await installing; - assert.equal(result.state, "downloaded"); - assert.equal(result.installBlockedReason, "watch-active"); - assert.equal(port.installs, 0); - assert.equal(controller.restartPending, false); - assert.equal(state.gateClosed, false); - assert.equal(state.gateReleases, 1); + assert.equal((await controller.install()).state, "installing"); + assert.equal(port.installs, 1); + controller.dispose(); }); -test("updater rechecks an autonomously stopped Watch after closing restart admission", async () => { - const { controller, port, state } = fixture(); +test("repeated install clicks call the installer once", async () => { + let resume; + const { controller, port } = fixture({ beforeInstall: () => new Promise(resolve => { resume = resolve; }) }); await controller.check(); await controller.download(); - state.watches = true; - state.watchVerification = "clear"; - const result = await controller.install(); - assert.equal(result.state, "installing"); - assert.equal(state.watchVerificationCalls, 1); - assert.equal(state.watches, false); + const first = controller.install(); + await new Promise(resolve => setImmediate(resolve)); + await controller.install(); + resume(); + await first; + await controller.install(); assert.equal(port.installs, 1); + controller.dispose(); }); test("updater reopens write admission when the installer fails synchronously", async () => { @@ -382,21 +370,22 @@ test("daily startup check runs once, notifies only for a newer version and leave claimStartupCheck: async () => { if (claimed) return false; claimed = true; return true; }, onStartupUpdateAvailable: async status => { notices.push(status.version); }, manualUpdates: { check: async () => { checks++; return { version: "1.1.0" }; }, openDownloadPage: async () => {} }, - setTimeoutImpl: callback => { callbacks.push(callback); return { unref() {} }; } + setTimeoutImpl: (callback, delay) => { callbacks.push({ callback, delay }); return { unref() {} }; } }; - const first = fixture(options).controller; - first.scheduleInitialCheck(); - first.scheduleInitialCheck(); - assert.equal(callbacks.length, 1); - callbacks.shift()(); + const first = fixture(options).controller; + first.scheduleInitialCheck(); + first.scheduleInitialCheck(); + assert.equal(callbacks.length, 1); + assert.equal(callbacks[0].delay, 5_000); + callbacks.shift().callback(); for (let i = 0; i < 5; i++) await new Promise(resolve => setImmediate(resolve)); assert.equal(checks, 1); assert.deepEqual(notices, ["1.1.0"]); first.scheduleInitialCheck(); assert.equal(callbacks.length, 0); - const second = fixture(options).controller; - second.scheduleInitialCheck(); - callbacks.shift()(); + const second = fixture(options).controller; + second.scheduleInitialCheck(); + callbacks.shift().callback(); for (let i = 0; i < 5; i++) await new Promise(resolve => setImmediate(resolve)); assert.equal(checks, 1); await second.check(); diff --git a/docs/README_CLI_ZH.md b/docs/README_CLI_ZH.md index 883b0d4..e584a15 100644 --- a/docs/README_CLI_ZH.md +++ b/docs/README_CLI_ZH.md @@ -1,9 +1,5 @@ # CLI 使用指南 -非法 UTF-8、数组 payload 或超出首行处理能力的数据会跳过并显示具体原因,关联索引保留;请处理后重新预览。不会自动转码或增加固定嵌套层数限制。 - -问题会话会被跳过,其关联索引保留原样;正常会话继续同步/切换。有跳过会显示“部分完成”,在预览、结果和操作日志可查看原因及本机完整路径(最多 200 项)。处理问题后重新预览即可纳入;全部历史跳过时,切换仍会备份并更新配置。数据库/目录/备份等全局故障仍停止,已写入时保留备份。诊断导出移除路径和索引标识。 - 适用于 V1 Node Core。CLI、Web 和 Electron 使用同一套同步业务;CLI 不启动 Electron,也不依赖桌面安装。npm 安装得到的是已发布版本,仓库 V1 的新增能力以本页和实际安装版本的 `codex-provider help` 为准。 ## 安装与检查 @@ -148,13 +144,19 @@ Windows 新 Sync/Switch 结果可带 `fileUpdateTiming`,包含复制、落盘 ## 遇到失败 +非法 UTF-8、数组 payload 或超出首行处理能力的数据会跳过并显示具体原因,关联索引保留;请处理后重新预览。不会自动转码或增加固定嵌套层数限制。 + +问题会话会被跳过,其关联索引保留原样;正常会话继续同步/切换。有跳过会显示“部分完成”,在预览、结果和操作日志可查看原因及本机完整路径(最多 200 项)。处理问题后重新预览即可纳入;全部历史跳过时,切换仍会备份并更新配置。数据库/目录/备份等全局故障仍停止,已写入时保留备份。诊断导出移除路径和索引标识。 + +Node CLI 支持最高 128 MiB UTF-8 字节的会话首行元数据(不含换行)。固定数据问题的原因码包括 `metadata-too-large`(超限)、`metadata-invalid-utf8`(非法编码)、`metadata-invalid`(格式无效)和 `metadata-too-complex`(超出处理能力)。问题会话跳过,正常部分继续;JSON 部分完成退出 3。处理数据后重新运行同步命令,会生成新计划。 + Status 发现已确认失效的锁时只作提示、不删除文件,后续正常写命令会重新验证并回收。若为 `LOCK_UNVERIFIABLE`,先结束其他同步/恢复操作后再检查;仍无法验证时保留锁并提交脱敏诊断,不要强制删除。 新记录中,同步内部错误会尽可能附带失败阶段和底层错误码;使用 `--json` 可在 `error.details.failureStage/causeCode` 查看,stderr 也会输出对应诊断行。反馈问题时附上这些字段和 operationId 即可,不要上传配置、聊天正文或认证文件。旧错误记录不会自动补齐细节。 - **Provider 未定义**:先修复配置;Sync 不会擅自切回 OpenAI。 - **数据已变化/计划过期**:重新检查并运行命令;不要重放旧 planId。 -- **会话占用/部分完成**:结束相关写入后再次同步;重试只处理仍未对齐的目标。若要撤销,选择受管备份恢复。 +- **会话占用**:结束相关写入后再次同步;重试只处理仍未对齐的目标。若要撤销,选择受管备份恢复。 - **SQLite busy**:预检阶段 busy 会在修改前停止;写入后才发生 busy 则可能 partial,以结果为准。 - **恢复未完成**:保留备份和 journal,在桌面/Web 查看恢复状态;不要删除保护文件。 - **历史可见但无法继续**:可能需要原 Provider/账号才能解密,不能用重复 Sync 或 Repair 解密。 @@ -162,7 +164,3 @@ Status 发现已确认失效的锁时只作提示、不删除文件,后续正 普通写入不会自动全量回滚。当前 CLI 的 Sync/Switch/Repair/Restore 没有提供受控取消入口;请等待终态结果,不要用 Ctrl+C 或强制结束进程代替恢复流程。JSON 的 130 是已取消结果的映射,不保证终端中断会输出该对象。Watch 和 Web 单独处理 Ctrl+C/SIGTERM,按其清理流程停止。 [返回首页](../README.md) · [工作原理](WORKING_PRINCIPLE_ZH.md) · [精确 CLI 合同](architecture/contracts/CLI_CONTRACT_ZH.md) - -### 大首行会话 - -Node CLI 支持最高 128 MiB UTF-8 字节的会话首行元数据(不含换行)。超限或无效分别以 metadata-too-large / metadata-invalid 原因跳过,正常部分继续;JSON 部分完成退出 3。处理原因后重新预览同步。 diff --git a/docs/README_DESKTOP_EN.md b/docs/README_DESKTOP_EN.md index 949dd49..8091da4 100644 --- a/docs/README_DESKTOP_EN.md +++ b/docs/README_DESKTOP_EN.md @@ -1,9 +1,5 @@ # Codex Provider Sync Desktop -Invalid UTF-8, array payloads, and metadata exceeding processing capacity are skipped with specific reasons; associated index rows stay unchanged. Resolve the data issue and preview again. No automatic transcoding or fixed nesting limit is introduced. - -Node Sync/Switch/Watch skip isolated invalid, oversized, unreadable or changed sessions and preserve their associated index rows. Healthy data continues; partial results show counts and up to 200 local details. A new preview can include repaired data. Switch can still back up and change configuration when all history is skipped. Global storage/database/backup failures stop the operation. Diagnostic exports remove local paths and row identifiers. - V1 Electron is the primary desktop interface for aligning Provider metadata in local Codex session files and the chat index, helping reuse sessions affected by Provider mismatches after switching. Sync does not guarantee cross-provider decryption or continuation. Build targets are Windows, macOS, and Linux; available downloads depend on actual Release assets. Local builds do not imply public release, signing, or an enabled update channel. ## First use @@ -51,6 +47,12 @@ Overview's **How to speed up sync** is optional guidance, not a mode switch. Ren Backups, flushing and file modification-time restoration remain enabled. Prepare shares bounded header facts within that request; Apply checks files afresh. Message timestamps and thread-index update times are not changed. See [working principles](WORKING_PRINCIPLE_ZH.md). +## Problem sessions and large headers + +Node Electron supports first-line metadata up to **128 MiB** of UTF-8 content, excluding line endings; this is not a limit on the entire conversation file. + +When one session has an invalid encoding, format or header size, the app skips it, preserves its associated index rows and reports why in preview, results and operation logs. Healthy sessions continue to sync or switch. Fix the data and prepare again to include it. Repeatedly closing Codex cannot fix a permanent format error; global storage, database and backup failures still stop the operation. + ## Backups and Restore Set retention only in **Backups / Restore → Backup settings**, default **2**. Sync, Switch, Repair and automatic sync share this app preference, with a separate pool per Codex Home—not a quota per operation. @@ -123,7 +125,7 @@ Record renumbering, history display-index rebuilding and encrypted-content modif - **Not verified / Refresh needed:** retry the status read. This is not evidence of an active write; do not delete lock files. - **Sessions currently in use:** Codex writer-held sessions in this Home, including idle/child/already-aligned chats. Currently available for the Windows writer mechanism; unavailable observations show Unknown, not zero. It is not a count of replies being generated. - **Sessions to skip this time:** blocked targets for this sync, separate from session activity. -- **Partial:** inspect the failed stage, error and backup, then prepare/retry or manually Restore. Ordinary writes do not automatically roll back everything. +- **Partial:** inspect the reason and backup. Fix invalid/oversized metadata before preparing again; retry locked/changing files after writing stops, or manually Restore. Ordinary writes do not automatically roll back everything. - **Final Provider check incomplete:** manually refresh Overview rather than assuming alignment. - **Old chat cannot continue:** return to its original Provider/account or start a new chat when encryption is incompatible. @@ -141,9 +143,9 @@ The UI supports Chinese/English, system/light/dark themes, keyboard navigation, **Settings → Updates** checks for higher stable Electron versions, not Legacy .NET EXEs. -- The first launch each local date checks once and only announces an available update. Later launches do not check again, nor does continuous running schedule a new-day check. Manual retry remains available; no automatic download/install. +- The first launch each local date checks about five seconds after startup and only announces an available update. Later launches do not check again, nor does continuous running schedule a new-day check. Manual retry remains available; no automatic download/install. - Portable/local packaged builds open the official download page. Close the app and extract the complete new version into a new directory. -- Installer builds with an enabled channel can download on request, then restart on confirmation. Writes, Watch and unresolved recovery block installation. +- Installer builds with an enabled channel can download on request, then restart on confirmation. Installation is the user's decision; storage state, writes, Watch and unresolved recovery do not disable it. - **Legacy .NET's single-EXE updater cannot directly upgrade to Electron.** Initial migration needs the full new package. An NSIS installer must not masquerade as the old EXE; online cross-version upgrade validation is separate. - Updating the program does not replace Codex data. Actual release notes determine availability, signing and update support. @@ -166,7 +168,3 @@ npm run desktop:test:e2e ``` [Home](../README.md) · [CLI guide (Chinese)](README_CLI_ZH.md) · [Current Core architecture](architecture/NODE_CORE_ARCHITECTURE_ZH.md) - -### Large session metadata - -Electron supports first-line metadata up to 128 MiB, excluding line endings. Oversized or invalid headers are skipped with distinct reasons while healthy sessions continue; resolve the cause and prepare again to include them. diff --git a/docs/README_DESKTOP_ZH.md b/docs/README_DESKTOP_ZH.md index 9d57fd6..f9d4a61 100644 --- a/docs/README_DESKTOP_ZH.md +++ b/docs/README_DESKTOP_ZH.md @@ -1,9 +1,5 @@ # Codex Provider Sync 桌面版 -非法 UTF-8、数组 payload 或超出首行处理能力的数据会跳过并显示具体原因,关联索引保留;请处理后重新预览。不会自动转码或增加固定嵌套层数限制。 - -问题会话会被跳过,其关联索引保留原样;正常会话继续同步/切换。有跳过会显示“部分完成”,在预览、结果和操作日志可查看原因及本机完整路径(最多 200 项)。处理问题后重新预览即可纳入;全部历史跳过时,切换仍会备份并更新配置。数据库/目录/备份等全局故障仍停止,已写入时保留备份。诊断导出移除路径和索引标识。 - V1 Electron 是面向用户的主桌面界面,用于在切换 Provider 后对齐本地会话文件和聊天索引,帮助因 Provider 信息不一致而无法正常使用的旧会话重新可用。同步不保证跨 Provider 解密或继续。构建目标为 Windows、macOS 和 Linux;可下载平台以实际 Release 资产为准。本地构建不代表已经公开发布、签名或启用更新通道。 ## 第一次使用 @@ -51,6 +47,12 @@ Provider 同步的业务扫描只解析首行。安全、唯一可定位且 JSON 备份、落盘和文件修改时间恢复不会为提速取消。预览复用同轮首行事实,执行仍重新检查;不会改动消息时间或线程索引的更新时间。[详细工作原理](WORKING_PRINCIPLE_ZH.md#4-provider-的两种落盘方式) +## 问题会话与大首行 + +Node Electron 桌面支持最高 **128 MiB** 的会话首行元数据(UTF-8 字节,不含换行);聊天正文总大小不受这个首行上限限制。 + +首行的编码、格式或大小有问题时,工具会跳过该会话、保留关联索引并在预览、结果和操作日志显示原因;正常会话继续同步或切换。处理问题后重新预览即可纳入。反复关闭 Codex 并重试不能解决固定的格式问题;数据库、目录、备份等全局故障仍会停止操作。 + ## 备份与恢复 备份数量只在“备份 / 恢复 → 备份设置”修改,默认 **2 份**。同步、切换、专项修复和自动同步共用此设置,每个 Codex Home 分别保留,不是每种操作各保留 2 份。 @@ -133,7 +135,7 @@ Provider 同步的业务扫描只解析首行。安全、唯一可定位且 JSON | 尚未验证 / 状态待刷新 | 不代表正在同步;点击重新读取,核验前暂不能修改,不要删除锁文件 | | 正在使用的会话 | 当前 Home 中 Codex writer 持有的会话,包括等待输入、子会话及已对齐会话;不是生成回复数。目前支持 Windows writer 观察,无法读取时显示未知 | | 本次将跳过的会话 | 本次同步写入受阻的目标,与“正在使用”不是同一个计数 | -| 部分完成 | 查看失败阶段、错误码和备份,返回重新预览/同步,或手动恢复;应用不自动全量回滚 | +| 部分完成 | 查看原因和备份:格式/大小问题处理后重新预览,占用/变化待停止写入后重试;也可手动恢复,应用不自动全量回滚 | | 最终 Provider 检查未完成 | 回概览手动刷新,不把未核验说成已同步 | | 旧会话无法继续 | 可能需要原 Provider/账号解密;可回到原环境或新建会话 | @@ -155,9 +157,9 @@ Watch 需在“设置”主动开启;当前状态按存储配置显示。指 “设置 → 更新”可检查更高的正式 Electron 版本,不把旧 .NET 单 EXE 当成新版。 -- 每个本地日期的首次启动检查一次,发现新版才弹窗;当天后续启动不自动再查,连续运行跨日也不另设检查。失败后可手动重试,不自动下载或安装。 +- 每个本地日期的首次启动后约 5 秒检查一次,发现新版才弹窗;当天后续启动不自动再查,连续运行跨日也不另设检查。失败后可手动重试,不自动下载或安装。 - 便携版/本地打包版打开官方下载页。退出后解压完整新版到新目录,不要只替换一个 EXE。 -- 已启用正式更新通道的安装版可按需下载,再确认重启安装;写操作、自动同步或未完成恢复会阻止安装。 +- 已启用正式更新通道的安装版可按需下载,再确认重启安装;安装由用户决定,存储状态、写操作、自动同步或未完成恢复不会禁用安装按钮。 - 设置更新区和新版弹窗可选“不再提醒此版本”;重启后保留,下个新版仍提醒。仍可手动检查/更新,也可在设置中恢复该版本提醒。 - 1.0.3 支持大首行及问题会话跳过。受影响的 1.0.1 安装版无法正常检查更新,请从官方下载页手动安装新版一次,详见[升级说明](release-notes/v1.0.3-zh.md)。线上跨版本下载安装仍待独立验收。 - **旧 Windows .NET 的单 EXE 自更新不能直接升级到 Electron。** 首次迁移需安装/解压完整新版;不把 NSIS 安装器冒充旧 EXE,不承诺已经完成线上跨版本升级验证。 @@ -182,7 +184,3 @@ npm run desktop:test:e2e ``` [返回首页](../README.md) · [CLI 指南](README_CLI_ZH.md) · [当前核心架构](architecture/NODE_CORE_ARCHITECTURE_ZH.md) - -### 大首行会话 - -Node Electron 桌面支持最高 128 MiB 的会话首行元数据;超限或格式无效的数据会跳过,记录原因并继续处理正常部分。请处理首行问题后再同步,反复关闭 Codex 并重试不能解决固定的格式问题。聊天正文总大小不受这个首行上限限制。 diff --git a/docs/README_EN.md b/docs/README_EN.md index e8a1d6b..da54ced 100644 --- a/docs/README_EN.md +++ b/docs/README_EN.md @@ -1,9 +1,5 @@
-Invalid UTF-8, array payloads, and metadata exceeding processing capacity are skipped with specific reasons; associated index rows stay unchanged. Resolve the data issue and preview again. No automatic transcoding or fixed nesting limit is introduced. - -Node Sync/Switch/Watch skip isolated invalid, oversized, unreadable or changed sessions and preserve their associated index rows. Healthy data continues; partial results show counts and up to 200 local details. A new preview can include repaired data. Switch can still back up and change configuration when all history is skipped. Global storage/database/backup failures stop the operation. Diagnostic exports remove local paths and row identifiers. - # codex-provider-sync ### Help reuse existing Codex sessions after switching providers @@ -17,25 +13,42 @@ Node Sync/Switch/Watch skip isolated invalid, oversized, unreadable or changed s
+

+ Sync session files and the SQLite index from Provider A to Provider B while keeping the current configuration unchanged +

+ +## What it solves + After a Provider switch, existing sessions may still reference the previous Provider. This tool aligns **Provider metadata in session files and the SQLite chat index** with the current configuration. **It does not guarantee cross-provider/account continuation or compaction**, handle authentication, or decrypt content. Already aligned sessions do not need another sync. +### When do I need it? + +- **You already switched with CCSwitch or another tool:** sync to the Provider in the current configuration. +- **You want to switch in this tool:** choose the target Provider; the tool updates configuration, then synchronizes session files and the index. +- **Provider metadata already matches:** no further sync is needed; check Codex's specific error if a session still fails. + +## Core features + +- **Sync and switch:** preview or sync immediately; enable Watch when needed. +- **Backups and Restore:** back up before changes, then restore or clean up backups when needed. +- **Chats and logs:** browse sessions by project and inspect results and timings. +- **Storage and repair:** choose data locations and run diagnostics or focused repair when needed. + ## Download for Windows -**Windows x64**. No Node.js installation required. +**Windows x64**, no Node.js required. Unsigned; extract the entire portable ZIP. [Download the latest stable release: installer / portable ZIP, release notes and checksums](https://github.com/Dailin521/codex-provider-sync/releases/latest). -Extract the entire portable folder; do not copy only the EXE. - -This release is unsigned and requires manual installation for updates. The old .NET updater cannot migrate to Electron; download the full new package. macOS/Linux Electron packages are not yet published. CLI / Web npm versions are released independently and do not match the desktop version automatically. +macOS/Linux Electron packages are not yet published. CLI / Web npm versions are released independently. ## Everyday use 1. Open Overview and check the **Provider, storage paths and sync status**. 2. After switching with CCSwitch or another tool, choose **Preview sync**, or **Sync now** to execute immediately. -3. Inspect the result. If partially completed, end the relevant session activity and retry; use **Backups / Restore** to undo changes. +3. Inspect the result. If partially completed, check the reported reasons before retrying; use **Backups / Restore** to undo changes. To change the configuration in this app, use **Switch Provider separately** at the bottom of Overview. It **changes configuration and synchronizes historical Provider metadata**, not historical models. Custom Providers must already be defined in `config.toml`. @@ -67,13 +80,74 @@ codex-provider sync CLI write commands execute directly. See the [CLI guide (Chinese)](README_CLI_ZH.md) for switching, restore, Watch, paths and JSON exit codes. Check your installed version's `--help` for available commands. -## Before using +## One core, three entry points + +Windows desktop, Local Web and CLI use the same sync, switch, backup and restore logic. The entry point changes how you operate it, not the sync result. + +```mermaid +flowchart LR + subgraph Entry["Entry points"] + Desktop["Windows desktop"] + Web["Local Web"] + CLI["CLI / scripts / WSL"] + end + + subgraph Core["Shared Node Core"] + Interface["Unified operation entry"] + Operations["Status / sync / switch
Watch / repair / restore"] + Runtime["Plan checks / concurrency
progress and cancellation"] + Interface --> Operations --> Runtime + end + + Desktop --> Interface + Web --> Interface + CLI --> Operations + Runtime --> Config["Codex configuration"] + Runtime --> Sessions["Session files"] + Runtime --> SQLite["SQLite index"] + Operations --> Backup["Backups and Restore"] +``` + +- **Desktop:** everyday double-click use. +- **Local Web:** browser-based, suitable for cross-platform environments. +- **CLI:** scripts, automation and WSL. + +Ordinary sync aligns Provider only. Focused repair and Restore require an explicit choice. The old .NET Windows/macOS apps remain compatibility implementations. + +## How sync writes, and what affects speed + +Sync parses only each session's first metadata line and aligns Provider in the session file and SQLite index; chat content remains unchanged. + +- **Eligible for in-place writing, including equal Provider byte lengths:** changes Provider directly without creating a full replacement copy. +- **Other valid first lines:** updates the first line, streams the unchanged body into a new file, then replaces the original. -- **Scope:** Sync changes Provider metadata, not message bodies, historical models or `threads.updated_at`. It does not read or modify `auth.json`. -- **Performance:** Eligible equal-byte-length Providers update in place. Other valid headers use streamed replacement with byte-identical bodies. Backups, flush checks and timestamp restoration remain; no manual fast mode is needed. -- **Partial results:** Not everything failed, and there is no automatic whole-operation rollback. Inspect skipped items, the failed stage and backup before retrying or restoring. -- **Still unable to continue:** For encrypted-content or model compatibility errors, return to the original Provider/account or start a new session. Sync cannot fix these. -- **Storage:** Check the actual paths in Overview. Windows WSL UNC SQLite paths are diagnostic-only; run writes inside the corresponding WSL distribution. +The tool selects this automatically. There is no speed switch, and Provider names do not need matching lengths. + +Speed mainly depends on the number of sessions that need changing. Different Provider lengths and large history files require body copying; backups, pre-write checks, flushing and timestamp restoration also take time. Operation logs show the timing details. + +## Frequently asked questions + +### I already switched with CCSwitch. What should I do? + +Confirm the intended Provider in Overview, then sync. If session files and the index already match, no further sync is needed. + +### Does sync change chats or login information? + +No. It only aligns Provider metadata in session files and SQLite. It does not modify message bodies, historical models or thread ordering timestamps, or read or modify `auth.json`. + +### Why can an old session still fail to continue? + +Check the specific Codex error. For encrypted-content or model compatibility problems, return to the original Provider/account or start a new session. + +### What does partial completion mean? + +Inspect the result or operation log. Problem sessions and their associated index rows remain unchanged while healthy sessions continue. Fix invalid or oversized metadata before preparing again; for locked or changing files, retry after the session stops writing. Completed changes are not automatically rolled back. + +### How do I undo a mistaken sync? + +Choose the backup from before the operation in Backups / Restore, or run `codex-provider restore `. See the [desktop guide](README_DESKTOP_EN.md) for details. + +For storage paths and WSL, see the [CLI guide (Chinese)](README_CLI_ZH.md). For read/write behavior and performance, see [how it works (Chinese)](WORKING_PRINCIPLE_ZH.md). ## Documentation @@ -88,7 +162,3 @@ CLI, Web and Electron share Node Core; installing the CLI does not install Elect Thanks to [@tangquanwei](https://github.com/tangquanwei) for the Local Web UI, history browsing and multilingual documentation foundation, brought into v0.5.0 through [PR #80](https://github.com/Dailin521/codex-provider-sync/pull/80), and to everyone contributing code, documentation and issue investigation. [Contributors](../CONTRIBUTORS.md) · [GitHub Contributors](https://github.com/Dailin521/codex-provider-sync/graphs/contributors) · [LINUX DO community](https://linux.do/) · [MIT License](../LICENSE) - -### Large session metadata - -Node Electron, CLI and Web support first-line metadata up to 128 MiB of UTF-8 content, excluding line endings. Oversized or invalid headers are skipped with distinct reasons while healthy sessions continue; resolve the cause and prepare again to include them. This is not a limit on the entire conversation file. diff --git a/docs/README_JA.md b/docs/README_JA.md index cb562da..33bc8c9 100644 --- a/docs/README_JA.md +++ b/docs/README_JA.md @@ -1,7 +1,5 @@
-Node の Sync/Switch/Watch は、形式不正・上限超過・読み取り不可・変更済みのセッションを個別にスキップし、関連する索引行を保持します。正常なデータの処理は続行し、部分完了の件数と最大 200 件のローカル詳細を表示します。修正したデータは新しいプレビューで取り込めます。履歴をすべてスキップしても、Switch はバックアップと設定変更を実行します。ストレージ・データベース・バックアップ全体の障害は処理を停止します。診断エクスポートからはローカルパスと行識別子を除去します。 - # codex-provider-sync ### Provider 切り替え後に Codex の既存セッションを再利用するためのツール @@ -16,183 +14,152 @@ Node の Sync/Switch/Watch は、形式不正・上限超過・読み取り不
+

+ 現在の設定を維持し、セッションファイルと SQLite インデックスを Provider A から Provider B に同期 +

+ ## 解決すること -**セッションが表示されていても、現在の Provider で続行できるとは限りません。** `model_provider` の切り替え後も、セッションファイルと SQLite インデックスに以前の Provider が残る場合があります。本ツールはその情報を現在の設定に揃え、Provider の不一致で利用できない既存セッションの再利用を支援します。履歴一覧の再表示ではなく、切り替え後の再利用が目的です。異なる Provider 間での続行や compact を保証するものではなく、暗号化内容やモデルの互換性は別の問題です。 +Provider を切り替えた後も、既存セッションには以前の Provider が記録されていることがあります。本ツールは**セッションファイルと SQLite チャットインデックスの Provider 情報**を現在の設定に揃え、メタデータの不一致を解消します。 -このツールはセッションファイルと SQLite インデックスの Provider 情報を揃えます。実際の変更前にバックアップを作成し、既定で最新 2 件を保持します。変更がなければバックアップは作成しません。ログイン、アカウント切り替え、復号やメッセージの再構築は行わず、`auth.json` も読み取り・変更しません。 +**Provider やアカウントをまたいだ継続・compact を保証するものではなく**、ログイン、認証、暗号化内容は扱いません。すでに情報が揃っている場合、再同期は不要です。 -| Provider 情報 | 同期前の例 | 同期後 | -| --- | --- | --- | -| 現在の設定 | Provider B | Provider B(変更なし) | -| セッションファイル | Provider A | Provider B | -| SQLite チャットインデックス | Provider A | Provider B | +### いつ使いますか? -### 同期が必要なのはいつですか? +- **CCSwitch などで切り替え済み:** 現在の設定にある Provider へ同期します。 +- **このツールで切り替える:** 対象 Provider を選ぶと、設定を更新してからセッションファイルとインデックスを同期します。 +- **Provider 情報がすでに一致している:** 再同期は不要です。継続できない場合は Codex の具体的なエラーを確認します。 -- **通常のケース:**公式 OpenAI とカスタム中継先を切り替える場合。公式 OpenAI は常に `openai` を使用するため Provider ID が変わり、履歴の同期が必要です。 -- **既存履歴で ID が混在している場合:**旧セッションに異なる Provider ID が記録されているため、現在の Provider に揃える必要があります。 -- **同期が不要なケース:**同じ Provider ID を共有するカスタム中継先だけを切り替える場合、または CCSwitch などがすでに履歴を同期している場合です。 +## 主な機能 -## クイックスタート +- **同期と切り替え:** プレビューまたは即時同期。必要に応じて Watch を有効化します。 +- **バックアップと復元:** 変更前にバックアップし、復元または整理できます。 +- **チャットとログ:** プロジェクトごとにセッションを確認し、結果と所要時間を見られます。 +- **保存先と修復:** 保存先を指定し、必要なときだけ診断や個別修復を実行します。 -> [Windows x64 Electron v1.0.0](https://github.com/Dailin521/codex-provider-sync/releases/tag/v1.0.0) を公開済みです。未署名で、更新は手動インストールです。旧 .NET 更新ボタンでは移行できません。macOS/Linux Electron パッケージは未公開です。CLI / Web の npm バージョンは独立して公開され、デスクトップ版とは一致しません。[ダウンロードと簡潔なガイド(英語)](README_EN.md#download-for-windows)。 +## Windows デスクトップ版をダウンロード -| 利用場面 | 推奨する入口 | -| --- | --- | -| Windows デスクトップ | V1 Electron・[ユーザーガイド(英語)](README_DESKTOP_EN.md)・[公開済みファイル](https://github.com/Dailin521/codex-provider-sync/releases) | -| macOS / Linux デスクトップ | [ビルドとプラットフォームガイド(英語)](README_DESKTOP_EN.md)。提供状況は各バージョンの公開済みファイルによります。 | -| ブラウザ UI またはクロスプラットフォーム利用 | [ローカル Web UI(CLI が必要)](#ローカル-web-ui) | -| スクリプト、CI、または WSL | [CLI](#cli) | +**Windows x64**、Node.js は不要です。未署名のため、ポータブル版は ZIP 全体を展開してください。 -### デスクトップ版 +[最新版をダウンロード:インストーラー / ポータブル ZIP、リリースノートとチェックサム](https://github.com/Dailin521/codex-provider-sync/releases/latest) -提供された V1 の完全なプログラムフォルダー、または [Releases](https://github.com/Dailin521/codex-provider-sync/releases) に実際に掲載された対応パッケージを使用します。Electron の EXE だけをコピーしないでください。 +macOS/Linux 向け Electron パッケージは未公開です。CLI / Web の npm 版は別途公開されます。 -1. Overview で現在の Provider と同期状態を確認します。 -2. 外部ツールで切り替えた場合は **Preview sync** で確認するか、**Sync now** で直ちに同期します。 -3. 本アプリで変更する場合は **Switch Provider separately** で Provider とルートモデル方針を選び、プレビュー後に確認します。設定変更後に履歴 Provider も同期されます。 +## 日常的な使い方 -チャットはプロジェクトごとに主セッションと折りたたまれた子タスクを表示し、右クリックで ID や再開コマンドをコピーできます。データは初回または明示的な操作後に読み込み、バックグラウンドで定期更新しません。Watch は手動で有効化します。更新確認はその日の初回起動時または手動のみで、自動インストールしません。 +1. Overview を開き、**Provider、保存先、同期状態**を確認します。 +2. CCSwitch などで Provider を切り替えた場合は、**Preview sync** で影響を確認するか、**Sync now** ですぐ実行します。 +3. 結果を確認します。partial の場合は占用中のセッションを終了して再試行し、取り消す場合は **Backups / Restore** を開きます。 -バックアップ保持数は Backups / Restore で一元管理し、同じアプリの各操作で共有します。保存先 Home ごとに保持し、Desktop・ブラウザ・CLI 間で設定は共有しません。保存だけでは削除せず、復元に必要な保護対象は上限を超えて残る場合があります。 +**Switch Provider separately** は設定を変更して履歴 Provider を同期しますが、履歴モデルは変更しません。カスタム Provider は事前に設定しておく必要があります。 -Windows の新しい操作ログはコピー、フラッシュ、置換、一時ファイル削除、タイムスタンプ復元の時間を表示します。旧ログの値は補いません。旧 .NET の単一 EXE 更新では Electron に直接移行できず、完全な新パッケージが必要です。 +変更前に自動バックアップし、既定では最新 **2 件**を保持します。変更が不要な場合はバックアップを作成しません。 -[デスクトップ版ガイド(英語)](README_DESKTOP_EN.md) +[デスクトップ完全ガイド(英語)](README_DESKTOP_EN.md) · [旧版移行説明(中国語)](release-notes/v1.0.0-zh.md) -### ローカル Web UI +## ローカル Web UI -ローカル Web UI は CLI に含まれています。Node.js `16.20.2+` をインストールし、本プロジェクトの公式 npm パッケージをインストールして起動します。 +Node.js `16.20.2+` をインストールしてから、公開済みの CLI / Web npm パッケージを実行します。 ```bash npm install -g @dailin521/codex-provider-sync codex-provider web ``` -よく使うオプション: +既定では `127.0.0.1:8791` のみで待ち受け、ブラウザーでペアリングします。別デバイスからの利用は [Web と SSH のガイド(中国語)](README_WEB_UI_ZH.md)を参照してください。 + +## CLI ```bash -codex-provider web --no-open # ブラウザを自動で開かない -codex-provider web --port 8792 # ポートを指定する -codex-provider web --reset-access # ブラウザを再ペアリングする +codex-provider status +codex-provider sync ``` -Web UI はデフォルトで `127.0.0.1` のみで待ち受け、ブラウザを自動で開いてペアリングします。保存先はサーバー管理の Profile で指定します。Sync now はクリックで実行を許可し、内部で Prepare/Apply を続けて行います。Preview sync、Switch、Repair、Restore はプレビュー確認後に実行します。Web にはデスクトップ操作ログやアプリ更新はありません。 +CLI の書き込みコマンドは直接実行されます。切り替え、復元、Watch、パス、JSON 終了コードは [CLI ガイド(中国語)](README_CLI_ZH.md)を参照し、利用可能なコマンドはインストール済みバージョンの `--help` を確認してください。 -#### Provider 切り替え後に履歴を同期する +## 1 つのコア、3 つの入口 -1. CCSwitch など普段使用しているツールで Provider を切り替えます。 -2. Overview で現在の Provider と同期状態を確認します。 -3. Preview sync で確認後に実行するか、Sync now をクリックします。 -4. 結果が partial の場合は、使用中のセッションを終了して再試行します。スキップされた記録を更新済みとして扱わないでください。 +Windows デスクトップ版、Local Web、CLI は、同じ同期・切り替え・バックアップ・復元ロジックを使用します。入口は操作方法だけを変え、同期結果は変えません。 -> **注意:** メタデータ同期は Provider の不一致を解消するもので、利用できない原因をすべて修復するものではありません。Provider をまたいで旧セッションを続行すると、切り替え先のバックエンドが `encrypted_content` の推論内容を復号できず、続行や compact に失敗する場合があります。 +```mermaid +flowchart LR + subgraph Entry["入口"] + Desktop["Windows デスクトップ"] + Web["Local Web"] + CLI["CLI / スクリプト / WSL"] + end + subgraph Core["共有 Node Core"] + Interface["統一操作入口"] + Operations["状態 / 同期 / 切り替え
Watch / 修復 / 復元"] + Runtime["計画検証 / 同時実行制御
進捗とキャンセル"] + Interface --> Operations --> Runtime + end + Desktop --> Interface + Web --> Interface + CLI --> Operations + Runtime --> Config["Codex 設定"] + Runtime --> Sessions["セッションファイル"] + Runtime --> SQLite["SQLite インデックス"] + Operations --> Backup["バックアップと復元"] +``` -[Web UI の詳細(中国語)](README_WEB_UI_ZH.md) +- **デスクトップ版:** 日常的なダブルクリック利用。 +- **Local Web:** ブラウザーで操作するクロスプラットフォーム環境向け。 +- **CLI:** スクリプト、自動化、WSL 向け。 -### CLI +通常の同期は Provider だけを揃えます。個別修復と復元は明示的に選択します。旧 .NET Windows/macOS 版は互換実装として維持されています。 -CLI は Node.js `16.20.2+` をサポートします。Node.js のインストール後、本プロジェクトの公式 npm パッケージをインストールします。 +## 同期の書き込みと速度 -```bash -npm install -g @dailin521/codex-provider-sync -codex-provider status -codex-provider sync -``` +同期は各セッションの先頭メタデータ行だけを解析し、セッションファイルと SQLite の Provider を揃えます。会話本文は変更しません。 -| コマンド | 用途 | -| --- | --- | -| `codex-provider status` | Provider、rollout、SQLite の状態を確認する | -| `codex-provider sync` | 現在の Provider に同期する | -| `codex-provider switch ` | Provider を切り替えてから同期する | -| `codex-provider diagnostics` | 明示的な読み取り専用の完全診断 | -| `codex-provider repair ` | 選択したメタデータだけを修復する | -| `codex-provider prune-backups --keep N` | 古い管理対象バックアップを削除する | -| `codex-provider restore ` | バックアップを復元する | -| `codex-provider watch` | 設定と SQLite の変更を監視する | +- **Provider のバイト長が同じ場合を含む、原位置書き込みの条件を満たす場合:** Provider を直接更新し、置換用の完全コピーを作成しません。 +- **その他の有効な先頭行:** 先頭行を更新し、本文をストリーミングで新しいファイルへコピーしてから置き換えます。 -`switch` は対象 Provider に `model` があればルートモデルへ反映し、なければ現在値を保持します。`--keep-root-model` は現在値を保持、`--model ` は明示的に指定します。どの方法も履歴モデルを変更しません。 +この方式は自動選択されます。高速化オプションは不要で、Provider 名の長さを揃える必要もありません。 -`sync` は config のルート `model_provider` を使用し、未指定時は `openai` です。カスタム Provider は事前定義が必要です。同期時のスキャンでは先頭行だけを解析します。安全な ID の JSON リテラルが UTF-8 バイト長で等しく一意に特定できれば、その位置を直接更新します。それ以外の有効な先頭行は一時ファイルへストリームコピーして置換し、本文のバイト列を保ちます。失敗したインプレース更新を強制的な全ファイル置換へ切り替えません。タイムスタンプ復元も維持します。固定長の名前に揃える必要はなく、`--fast` や `sync --provider` はありません。 +速度は主に更新対象のセッション数で決まります。Provider の長さが異なり履歴ファイルが大きい場合は本文コピーが必要です。バックアップ、書き込み前検証、保存、タイムスタンプ復元にも時間がかかります。操作ログで各段階の時間を確認できます。 -モデル、cwd、userEvent、workspaceRoots の変更は明示的な Repair に分離されています。workspaceRoots は cwd を含みます。Diagnostics は手動の読み取り専用検査で、失敗した Sync から自動実行しません。暗号化内容の変更、記録番号の変更、履歴表示インデックスの再構築は提供しません。 +## よくある質問 -CLI の書き込みコマンドは対話確認なしで実行します。プレビューが必要なら Desktop/Web を使ってください。有限コマンドの `--json` は stdout に単一結果、stderr に進捗を出し、partial は終了コード 3 です。Watch/Web はこの JSON モードに非対応です。[CLI ガイド(中国語)](README_CLI_ZH.md) +### CCSwitch で切り替え済みですが、何をすればよいですか? -SQLite Home の解決順序: `--sqlite-home` → `config.toml` ルートの `sqlite_home` → `CODEX_SQLITE_HOME` → `/sqlite`。デフォルトレイアウトだけが `/state_5.sqlite` にフォールバックします。 +このツールを開き、現在の Provider が目的のものか確認して同期します。セッションファイルとインデックスの Provider がすでに一致していれば不要です。 -## 現在のアーキテクチャ +### 同期で会話内容やログイン情報は変わりますか? -```mermaid -flowchart LR - CLI["CLI"] --> Adapter["public-api compatibility adapter"] - Adapter --> UseCases["Shared Node Core use cases"] - Web["Web UI"] --> Host["HttpCoreClient / Local Web Host"] - Host --> Facade["CoreFacade"] - Electron["Electron UI"] --> IPC["DesktopCoreClient / narrow IPC"] - IPC --> Utility["Utility Process"] - Utility --> Facade - Facade --> UseCases - UseCases --> Storage["Node storage ports"] - Storage --> Files["config / sessions / SQLite / backups"] - Legacy["Legacy .NET desktop"] --> DotNet["Separate .NET Core"] - DotNet --> Files -``` +変わりません。会話ファイルと SQLite インデックスの Provider だけを揃えます。会話本文、履歴モデル、セッション順序の時間、`auth.json` は読み取りも変更もしません。 + +### 同期後も古いセッションを続けられないのはなぜですか? -- Web UI は `HttpCoreClient → /api/core → CoreFacade`、CLI は `src/public-api.js` 互換アダプターを通して同じ Node Core ユースケースを呼び出します。 -- `V1` の Electron 主デスクトップは `DesktopCoreClient → 制限された Preload/Main IPC → Utility Process → Node Core` を使用し、Renderer から Node、任意パス、汎用 IPC にはアクセスできません。 -- Windows GUI は Application 層を通じて .NET Core を呼び出し、macOS GUI は現在 .NET Core を直接呼び出します。 -- .NET は独立した Legacy 実装です。旧版のモデル修復、journal、自動全体ロールバックを新しい Node Core の動作として扱わないでください。 +Provider 一致は継続条件の一つです。Codex の具体的なエラーを確認してください。暗号化内容やモデル互換性の問題なら、元の Provider / アカウントへ戻るか、新しいセッションを作成します。 -V1 は Electron を主デスクトップアプリとして提供します。.NET Windows/macOS 実装は引き続きビルド・テストされる互換版として保持します。 +### 「部分完了」と表示された場合は? -## 安全上の境界 +結果または操作ログの理由を確認します。異常なセッションは関連インデックスを保持したままスキップし、正常なセッションは続行します。形式やサイズの問題はデータを直してから再プレビューし、占用または変更されたファイルは書き込み停止後に再同期します。完了済みの変更は自動で全体ロールバックされません。 -- Sync/Switch/Repair の実際の変更前に `/backups_state/provider-sync/` へバックアップし、既定で最新 2 件を保持します。no-op では作成しません。 -- 通常の書き込みは Home ロックと SQLite トランザクションを使い、ファイル間の journal や自動全体ロールバックを行いません。変更後の失敗は partial として、新しいプレビューで確認して再試行するか、CLI コマンドを再実行します。取り消したい場合は手動で Restore します。Restore だけが独立したスナップショット、journal、補償処理を保持します。 -- メッセージ本文、セッションタイトル、認証情報、`auth.json`、`updated_at` は変更しません。 -- SQLite が使用中の場合は、Codex、Codex App、app-server を閉じてから再試行してください。 -- アクティブなセッションが rollout をロックしている場合、他のファイルは続行します。セッション終了後にもう一度同期してください。 -- Provider またはアカウントをまたいで旧セッションを続行すると、切り替え先のバックエンドが `encrypted_content` を復号できず、続行や compact に失敗する場合があります。その場合は元の Provider/アカウントに戻すか、新しいセッションを開始してください。 -- Windows から WSL UNC SQLite Home に直接書き込むことはできません。WSL に入り、Linux パスで CLI を実行してください。 +### 間違えて同期した場合、どう戻せますか? -## ドキュメント +**Backups / Restore** で操作前のバックアップを選んで復元します。CLI では `codex-provider restore ` を使えます。 -- [AI / Agent ガイド](../AGENTS.md) -- [Legacy .NET Windows GUI(中国語)](README_GUI_ZH.md) -- [現在の Core アーキテクチャ(中国語)](architecture/NODE_CORE_ARCHITECTURE_ZH.md) -- [V1 Electron デスクトップ版ガイド(英語)](README_DESKTOP_EN.md) -- [Web UI(中国語)](README_WEB_UI_ZH.md) -- [中文](../README.md) · [English](README_EN.md) · 日本語 · [한국어](README_KO.md) -- [macOS GUI: 中文](README_MAC_GUI_ZH.md) · [English](README_MAC_GUI_EN.md) -- [仕組み(中国語)](WORKING_PRINCIPLE_ZH.md) · [変更履歴](../CHANGELOG.md) · [コントリビューションガイド](../CONTRIBUTING.md) +パス設定と WSL は [CLI ガイド(中国語)](README_CLI_ZH.md)、同期の読書きと速度は[仕組み(中国語)](WORKING_PRINCIPLE_ZH.md)を参照してください。 -## 開発 +## ドキュメントと開発 -ワークスペース、Web、Electron の構築は Node 24 を使用します。インストール済みのルート CLI は Node 16.20.2 以降に対応します。現在の Core ガイドを先に読み、Provider I/O の不変条件を維持してください。 +- [文書索引(中国語)](README_ZH.md) · [変更履歴](../CHANGELOG.md) · [Issue を報告](https://github.com/Dailin521/codex-provider-sync/issues) +- [仕組み(中国語)](WORKING_PRINCIPLE_ZH.md) · [現在の Node Core アーキテクチャ(中国語)](architecture/NODE_CORE_ARCHITECTURE_ZH.md) +- [コントリビューションとビルド](../CONTRIBUTING.md) · [移行とリリースゲート](migration/VNEXT_MIGRATION_EXECUTION_INDEX_ZH.md) · [AI / Agent ガイド](../AGENTS.md) ```bash npm ci npm run architecture:check -npm run web:build -npm run web:start npm test -dotnet test desktop/CodexProviderSync.Core.Tests/CodexProviderSync.Core.Tests.csproj +npm run web:build +npm run desktop:build ``` -メンテナーは、Windows GUI の Release とは独立して CLI/Web パッケージを公開できます。[npm 公開ガイド(中国語)](NPM_PUBLISHING.md)を参照してください。 - -## 謝辞 - -ローカル Web UI を提案・実装し、履歴閲覧機能と多言語ドキュメントの基盤を提供するとともに、[PR #80](https://github.com/Dailin521/codex-provider-sync/pull/80) を通じて v0.5.0 に導入した [@tangquanwei](https://github.com/tangquanwei)、そしてコード、ドキュメント、テスト、問題調査に貢献したすべての方に感謝します。 - -[コントリビューター一覧](../CONTRIBUTORS.md) · [GitHub Contributors](https://github.com/Dailin521/codex-provider-sync/graphs/contributors) - -## License - -MIT +## 謝辞とライセンス -### 大きなセッションメタデータ +ローカル Web UI、履歴閲覧、多言語ドキュメントの基盤を提供し、[PR #80](https://github.com/Dailin521/codex-provider-sync/pull/80)を通じて v0.5.0 に導入した [@tangquanwei](https://github.com/tangquanwei)、およびコード、文書、テスト、問題調査に貢献した皆さんに感謝します。 -Node 版の Electron・CLI・Web は、改行を除く UTF-8 で最大 128 MiB の先頭行メタデータに対応します。上限超過や形式不正のデータは理由を記録してスキップし、正常なセッションの同期を続行します。原因を解消した後、新しいプレビューで再同期してください。会話ファイル全体のサイズ制限ではありません。 +[コントリビューター](../CONTRIBUTORS.md) · [GitHub Contributors](https://github.com/Dailin521/codex-provider-sync/graphs/contributors) · [LINUX DO](https://linux.do/) · [MIT License](../LICENSE) diff --git a/docs/README_KO.md b/docs/README_KO.md index 99e7cc5..eb92028 100644 --- a/docs/README_KO.md +++ b/docs/README_KO.md @@ -1,7 +1,5 @@
-Node Sync/Switch/Watch는 형식 오류, 크기 초과, 읽기 불가 또는 변경된 세션을 개별적으로 건너뛰고 관련 인덱스 행을 유지합니다. 정상 데이터 처리는 계속하며 부분 완료 건수와 최대 200개의 로컬 상세 정보를 표시합니다. 수정한 데이터는 새 미리보기에서 포함할 수 있습니다. 모든 기록을 건너뛰어도 Switch는 백업과 설정 변경을 수행합니다. 저장소·데이터베이스·백업 전체의 오류가 발생하면 중단합니다. 진단 내보내기에서는 로컬 경로와 행 식별자를 제거합니다. - # codex-provider-sync ### Provider 전환 후 기존 Codex 세션을 다시 사용할 수 있도록 돕습니다 @@ -16,183 +14,152 @@ Node Sync/Switch/Watch는 형식 오류, 크기 초과, 읽기 불가 또는 변
+

+ 현재 설정을 유지하면서 세션 파일과 SQLite 인덱스를 Provider A에서 Provider B로 동기화 +

+ ## 해결하는 문제 -**세션이 보여도 현재 Provider로 계속 사용할 수 있다는 뜻은 아닙니다.** `model_provider`를 전환한 뒤에도 세션 파일과 SQLite 인덱스에 이전 Provider가 남아 있을 수 있습니다. 이 도구는 해당 정보를 현재 설정에 맞춰 Provider 불일치로 사용할 수 없는 기존 세션의 재사용을 돕습니다. 기록 목록을 다시 표시하는 것이 아니라 전환 후 세션을 다시 사용하는 데 초점을 둡니다. 서로 다른 Provider 간의 대화 계속이나 compact를 보장하지 않으며 암호화된 내용과 모델 호환성은 별도 문제입니다. +Provider를 전환한 뒤에도 기존 세션에는 이전 Provider가 기록되어 있을 수 있습니다. 이 도구는 **세션 파일과 SQLite 채팅 인덱스의 Provider 정보**를 현재 설정에 맞춰 메타데이터 불일치를 해결합니다. -이 도구는 세션 파일과 SQLite 인덱스의 Provider 정보를 맞춥니다. 실제 변경 전에 백업을 만들고 기본적으로 최신 2개를 유지하며, 변경이 없으면 백업을 만들지 않습니다. 로그인, 계정 전환, 복호화나 메시지 재구성을 하지 않으며 `auth.json`을 읽거나 수정하지 않습니다. +**Provider나 계정 간 세션 계속 또는 compact를 보장하지 않으며**, 로그인, 인증, 암호화된 내용은 처리하지 않습니다. 정보가 이미 일치하면 다시 동기화할 필요가 없습니다. -| Provider 정보 | 동기화 전 예시 | 동기화 후 | -| --- | --- | --- | -| 현재 설정 | Provider B | Provider B (변경 없음) | -| 세션 파일 | Provider A | Provider B | -| SQLite 채팅 인덱스 | Provider A | Provider B | +### 언제 사용하나요? -### 언제 동기화가 필요한가요? +- **CCSwitch 등으로 이미 전환한 경우:** 현재 설정의 Provider로 동기화합니다. +- **이 도구에서 전환하려는 경우:** 대상 Provider를 선택하면 설정을 변경한 뒤 세션 파일과 인덱스를 동기화합니다. +- **Provider 정보가 이미 일치하는 경우:** 다시 동기화할 필요가 없습니다. 계속할 수 없다면 Codex의 구체적인 오류를 확인하세요. -- **일반적인 경우:** 공식 OpenAI와 사용자 지정 릴레이 사이에서 전환합니다. 공식 OpenAI는 항상 `openai`를 사용하므로 Provider ID가 바뀌며 기록을 동기화해야 합니다. -- **기존 기록에 ID가 섞인 경우:** 이전 세션에 서로 다른 Provider ID가 기록되어 있으므로 현재 Provider에 맞춰야 합니다. -- **동기화가 필요 없는 경우:** 같은 Provider ID를 공유하는 사용자 지정 릴레이 사이에서만 전환하거나 CCSwitch 같은 도구가 이미 기록을 동기화한 경우입니다. +## 주요 기능 -## 빠른 시작 +- **동기화와 전환:** 미리보거나 바로 동기화하고, 필요할 때 Watch를 켭니다. +- **백업과 복원:** 변경 전 자동 백업을 만들고 복원하거나 정리할 수 있습니다. +- **채팅과 로그:** 프로젝트별 세션, 작업 결과와 소요 시간을 확인합니다. +- **저장 위치와 복구:** 저장 위치를 지정하고 필요할 때만 진단이나 개별 복구를 실행합니다. -> [Windows x64 Electron v1.0.0](https://github.com/Dailin521/codex-provider-sync/releases/tag/v1.0.0)이 공개되었습니다. 미서명 버전이며 업데이트는 수동 설치입니다. 기존 .NET 업데이트 버튼으로는 이전할 수 없습니다. macOS/Linux Electron 패키지는 아직 미공개입니다. CLI / Web npm 버전은 독립적으로 배포되므로 데스크톱 버전과 같지 않습니다. [다운로드 및 간단한 가이드(영문)](README_EN.md#download-for-windows). +## Windows 데스크톱 앱 다운로드 -| 상황 | 권장 방법 | -| --- | --- | -| Windows 데스크톱 | V1 Electron / [사용자 가이드(영문)](README_DESKTOP_EN.md) / [공개된 파일](https://github.com/Dailin521/codex-provider-sync/releases) | -| macOS / Linux 데스크톱 | [빌드 및 플랫폼 가이드(영문)](README_DESKTOP_EN.md). 지원 여부는 해당 버전에서 실제 공개한 파일에 따릅니다. | -| 브라우저 UI 또는 크로스 플랫폼 사용 | [로컬 Web UI (CLI 필요)](#로컬-web-ui) | -| 스크립트, CI 또는 WSL | [CLI](#cli) | +**Windows x64**, Node.js가 필요하지 않습니다. 미서명 버전이며 포터블 ZIP은 전체를 압축 해제하세요. -### 데스크톱 앱 +[최신 정식 버전 다운로드: 설치 프로그램 / 포터블 ZIP, 릴리스 정보와 체크섬](https://github.com/Dailin521/codex-provider-sync/releases/latest) -제공된 V1 전체 프로그램 폴더 또는 [Releases](https://github.com/Dailin521/codex-provider-sync/releases)에 실제로 올라온 해당 플랫폼 패키지를 사용하세요. Electron EXE만 따로 복사하지 마세요. +macOS/Linux Electron 패키지는 아직 공개되지 않았습니다. CLI / Web npm 버전은 별도로 배포됩니다. -1. Overview에서 현재 Provider와 동기화 상태를 확인합니다. -2. 외부 도구로 전환했다면 **Preview sync**으로 먼저 확인하거나 **Sync now**로 바로 동기화합니다. -3. 앱에서 전환하려면 **Switch Provider separately**에서 Provider와 루트 모델 방식을 선택하고 미리보기 후 확인합니다. 설정 변경 후 기록의 Provider도 동기화합니다. +## 일상 사용 -채팅은 프로젝트별로 주 세션과 접힌 하위 작업을 표시하며, 우클릭으로 세션 ID나 재개 명령을 복사할 수 있습니다. 데이터는 최초 로드 또는 명시적 작업에 따라 갱신하며 백그라운드 폴링을 하지 않습니다. Watch는 직접 활성화해야 합니다. 업데이트는 매일 첫 실행 시 한 번 또는 수동으로 확인하며 자동 설치하지 않습니다. +1. Overview를 열어 **Provider, 저장 경로, 동기화 상태**를 확인합니다. +2. CCSwitch 등으로 Provider를 전환했다면 **Preview sync**로 영향을 확인하거나 **Sync now**로 바로 실행합니다. +3. 결과를 확인합니다. partial이면 점유 중인 세션을 종료한 뒤 다시 시도하고, 되돌리려면 **Backups / Restore**로 이동합니다. -백업 수는 Backups / Restore에서 통합 관리합니다. 같은 앱의 작업들이 설정을 공유하지만 Home별로 따로 보관하며 Desktop·브라우저·CLI 간 설정은 공유하지 않습니다. 설정 저장만으로 삭제하지 않고, 복원에 필요한 보호된 백업은 한도를 넘을 수 있습니다. +**Switch Provider separately**는 설정을 변경하고 기록 Provider를 동기화하지만 기록 모델은 바꾸지 않습니다. 사용자 지정 Provider는 미리 설정해야 합니다. -새 Windows 작업 로그에는 복사, 디스크 플러시, 교체, 임시 파일 정리, 타임스탬프 복원 시간이 표시됩니다. 이전 로그의 값은 추정하지 않습니다. 기존 .NET 단일 EXE 업데이트로 Electron에 바로 이전할 수 없으며 전체 새 패키지가 필요합니다. +변경 전 자동으로 백업하며 기본적으로 최근 **2개**를 유지합니다. 변경할 내용이 없으면 백업을 만들지 않습니다. -[데스크톱 앱 가이드(영문)](README_DESKTOP_EN.md) +[데스크톱 전체 가이드(영문)](README_DESKTOP_EN.md) · [구버전 마이그레이션 안내(중국어)](release-notes/v1.0.0-zh.md) -### 로컬 Web UI +## 로컬 Web UI -로컬 Web UI는 CLI에 포함되어 있습니다. Node.js `16.20.2+`를 설치한 다음 이 프로젝트의 공식 npm 패키지를 설치하고 실행하세요. +Node.js `16.20.2+`를 설치한 뒤 공개된 CLI / Web npm 패키지를 실행합니다. ```bash npm install -g @dailin521/codex-provider-sync codex-provider web ``` -자주 쓰는 옵션: +기본값으로 `127.0.0.1:8791`에서만 수신하고 브라우저에서 페어링합니다. 다른 기기에서 사용하는 방법은 [Web 및 SSH 가이드(중국어)](README_WEB_UI_ZH.md)를 참조하세요. + +## CLI ```bash -codex-provider web --no-open # 브라우저를 자동으로 열지 않음 -codex-provider web --port 8792 # 포트 지정 -codex-provider web --reset-access # 브라우저 재페어링 +codex-provider status +codex-provider sync ``` -Web UI는 기본적으로 `127.0.0.1`에서만 수신하며, 브라우저를 자동으로 열어 페어링을 진행합니다. 저장 경로는 서버 관리 Profile에서 지정합니다. Sync now는 클릭 자체로 실행이 승인되며, 내부에서 Prepare/Apply를 연속 수행합니다. Preview sync, Switch, Repair, Restore는 미리보기 후 확인이 필요합니다. Web에는 데스크톱 작업 로그와 앱 업데이트가 없습니다. +CLI 쓰기 명령은 바로 실행됩니다. 전환, 복원, Watch, 경로, JSON 종료 코드는 [CLI 가이드(중국어)](README_CLI_ZH.md)를 참고하고, 사용 가능한 명령은 설치된 버전의 `--help`로 확인하세요. -#### Provider 전환 후 기록 동기화 +## 하나의 코어, 세 개의 진입점 -1. CCSwitch 등 평소 사용하는 도구로 Provider를 전환합니다. -2. Overview에서 현재 Provider와 동기화 상태를 확인합니다. -3. Preview sync으로 확인 후 실행하거나 Sync now를 클릭합니다. -4. partial 결과라면 사용 중인 세션을 종료하고 다시 동기화합니다. 건너뛴 기록을 업데이트 완료로 간주하지 마세요. +Windows 데스크톱 앱, Local Web, CLI는 같은 동기화·전환·백업·복원 로직을 사용합니다. 진입점은 조작 방식만 바꾸고 동기화 결과는 바꾸지 않습니다. -> **주의:** 메타데이터 동기화는 Provider 불일치를 해결하며 세션을 사용할 수 없는 모든 원인을 복구하지는 않습니다. Provider를 바꾼 뒤 이전 세션을 계속하면 대상 백엔드가 `encrypted_content`의 추론 내용을 복호화하지 못해 대화 계속 또는 compact가 실패할 수 있습니다. +```mermaid +flowchart LR + subgraph Entry["진입점"] + Desktop["Windows 데스크톱"] + Web["Local Web"] + CLI["CLI / 스크립트 / WSL"] + end + subgraph Core["공유 Node Core"] + Interface["통합 작업 진입점"] + Operations["상태 / 동기화 / 전환
Watch / 복구 / 복원"] + Runtime["계획 검증 / 동시 실행 제어
진행 및 취소"] + Interface --> Operations --> Runtime + end + Desktop --> Interface + Web --> Interface + CLI --> Operations + Runtime --> Config["Codex 설정"] + Runtime --> Sessions["세션 파일"] + Runtime --> SQLite["SQLite 인덱스"] + Operations --> Backup["백업 및 복원"] +``` -[Web UI 전체 안내 (중국어)](README_WEB_UI_ZH.md) +- **데스크톱 앱:** 일상적인 더블클릭 사용. +- **Local Web:** 브라우저에서 조작하는 크로스플랫폼 환경. +- **CLI:** 스크립트, 자동화, WSL. -### CLI +일반 동기화는 Provider만 맞춥니다. 개별 복구와 복원은 명시적으로 선택해야 합니다. 구형 .NET Windows/macOS 앱은 호환 구현으로 유지합니다. -CLI는 Node.js `16.20.2+`를 지원합니다. Node.js를 설치한 후 이 프로젝트의 공식 npm 패키지를 설치합니다. +## 동기화의 쓰기 방식과 속도 -```bash -npm install -g @dailin521/codex-provider-sync -codex-provider status -codex-provider sync -``` +동기화는 각 세션의 첫 메타데이터 줄만 해석하고 세션 파일과 SQLite의 Provider를 맞춥니다. 대화 본문은 바꾸지 않습니다. -| 명령 | 용도 | -| --- | --- | -| `codex-provider status` | Provider, rollout, SQLite 상태 확인 | -| `codex-provider sync` | 현재 Provider로 동기화 | -| `codex-provider switch ` | Provider 전환 후 동기화 | -| `codex-provider diagnostics` | 명시적인 전체 읽기 전용 검사 | -| `codex-provider repair ` | 선택한 메타데이터만 수정 | -| `codex-provider prune-backups --keep N` | 오래된 관리형 백업 정리 | -| `codex-provider restore ` | 백업 복원 | -| `codex-provider watch` | 설정과 SQLite 변경 감시 | +- **Provider 바이트 길이가 같은 경우를 포함해 제자리 쓰기 조건을 만족하는 경우:** Provider를 직접 변경하고 전체 대체 파일을 만들지 않습니다. +- **그 밖의 유효한 첫 줄:** 첫 줄을 변경한 뒤 본문을 새 파일에 스트리밍 복사하고 원본을 교체합니다. -`switch`는 대상 Provider에 `model`이 있으면 루트 모델에 반영하고, 없으면 현재 값을 유지합니다. `--keep-root-model`은 현재 값 유지, `--model `은 명시적 지정입니다. 어느 방식도 기록의 모델을 변경하지 않습니다. +이 방식은 자동으로 선택됩니다. 가속 옵션이 필요 없고 Provider 이름 길이를 맞출 필요도 없습니다. -`sync`는 config의 루트 `model_provider`를 사용하며 없으면 `openai`를 사용합니다. 사용자 지정 Provider는 미리 정의되어 있어야 합니다. 동기화 스캔은 첫 줄만 파싱합니다. 안전한 ID의 JSON 리터럴이 UTF-8 바이트 길이가 같고 유일하게 식별되면 해당 위치만 갱신합니다. 그 외의 유효한 첫 줄은 임시 파일에 스트림 복사 후 교체하며 본문 바이트는 유지합니다. 제자리 쓰기 실패를 강제 전체 파일 교체로 우회하지 않습니다. 타임스탬프 복원도 유지합니다. 고정 길이 이름으로 맞출 필요가 없고 `--fast`나 `sync --provider`는 없습니다. +속도는 주로 변경할 세션 수에 따라 달라집니다. Provider 길이가 다르고 기록 파일이 크면 본문 복사가 필요합니다. 백업, 쓰기 전 확인, 저장, 타임스탬프 복원에도 시간이 걸립니다. 작업 로그에서 단계별 시간을 확인할 수 있습니다. -모델, cwd, userEvent, workspaceRoots 변경은 명시적인 Repair로 분리됩니다. workspaceRoots는 cwd를 포함합니다. Diagnostics는 수동 읽기 전용 검사이며 Sync 실패 후 자동 실행되지 않습니다. 암호화 내용 변경, 기록 번호 변경, 기록 표시 인덱스 재구축은 제공하지 않습니다. +## 자주 묻는 질문 -CLI 쓰기 명령은 대화형 확인 없이 실행됩니다. 미리보기가 필요하면 Desktop/Web을 사용하세요. 장기 실행이 아닌 명령의 `--json`은 stdout에 단일 결과, stderr에 진행 정보를 내보내며 partial은 종료 코드 3입니다. Watch/Web은 이 JSON 모드를 지원하지 않습니다. [CLI 가이드(중국어)](README_CLI_ZH.md) +### CCSwitch로 이미 전환했습니다. 무엇을 해야 하나요? -SQLite Home 해석 순서: `--sqlite-home` → `config.toml` 루트의 `sqlite_home` → `CODEX_SQLITE_HOME` → `/sqlite`. 기본 레이아웃에서만 `/state_5.sqlite`로 대체합니다. +도구를 열어 현재 Provider가 원하는 값인지 확인한 뒤 동기화합니다. 세션 파일과 인덱스의 Provider가 이미 일치하면 다시 동기화할 필요가 없습니다. -## 현재 아키텍처 +### 동기화가 대화 내용이나 로그인 정보를 바꾸나요? -```mermaid -flowchart LR - CLI["CLI"] --> Adapter["public-api compatibility adapter"] - Adapter --> UseCases["Shared Node Core use cases"] - Web["Web UI"] --> Host["HttpCoreClient / Local Web Host"] - Host --> Facade["CoreFacade"] - Electron["Electron UI"] --> IPC["DesktopCoreClient / narrow IPC"] - IPC --> Utility["Utility Process"] - Utility --> Facade - Facade --> UseCases - UseCases --> Storage["Node storage ports"] - Storage --> Files["config / sessions / SQLite / backups"] - Legacy["Legacy .NET desktop"] --> DotNet["Separate .NET Core"] - DotNet --> Files -``` +아닙니다. 세션 파일과 SQLite 인덱스의 Provider만 맞춥니다. 대화 본문, 기록 모델, 세션 정렬 시간, `auth.json`은 읽거나 수정하지 않습니다. + +### 동기화 후에도 이전 세션을 계속할 수 없는 이유는 무엇인가요? -- Web UI는 `HttpCoreClient → /api/core → CoreFacade`를 사용하고, CLI는 `src/public-api.js` 호환 어댑터를 통해 같은 Node Core 유스케이스를 호출합니다. -- `V1` Electron 주 데스크톱은 `DesktopCoreClient → 제한된 Preload/Main IPC → Utility Process → Node Core` 경로를 사용하며 Renderer에서는 Node, 임의 경로 또는 범용 IPC에 접근할 수 없습니다. -- Windows GUI는 Application 계층을 통해 .NET Core를 호출하고, macOS GUI는 현재 .NET Core를 직접 호출합니다. -- .NET은 별도의 Legacy 구현입니다. 이전 버전의 모델 수정, journal, 자동 전체 롤백을 새 Node Core의 동작으로 간주하지 마세요. +Provider 일치는 계속 사용하기 위한 조건 중 하나입니다. Codex의 구체적인 오류를 확인하세요. 암호화된 내용 또는 모델 호환성 문제라면 원래 Provider/계정으로 돌아가거나 새 세션을 만드세요. -V1은 Electron을 기본 데스크톱 앱으로 제공합니다. .NET Windows/macOS 구현은 계속 빌드하고 테스트하는 호환 버전으로 유지됩니다. +### “부분 완료”가 표시되면 어떻게 하나요? -## 안전 범위 +결과나 작업 로그의 이유를 확인합니다. 문제가 있는 세션은 연결된 인덱스를 유지한 채 건너뛰고 정상 세션은 계속 처리합니다. 형식이나 크기 문제는 데이터를 고친 뒤 다시 미리보기하고, 점유되었거나 변경된 파일은 쓰기가 끝난 뒤 다시 동기화합니다. 완료된 변경은 자동으로 전체 롤백되지 않습니다. -- Sync/Switch/Repair의 실제 변경 전에 `/backups_state/provider-sync/`에 백업하고 기본적으로 최신 2개를 유지합니다. no-op에서는 만들지 않습니다. -- 일반 쓰기는 Home 잠금과 SQLite 트랜잭션을 사용하며 파일 간 journal이나 자동 전체 롤백을 하지 않습니다. 변경 후 실패는 partial로 보고합니다. 새 미리보기로 확인 후 다시 시도하거나 CLI 명령을 다시 실행하세요. 변경을 되돌리려면 수동 Restore를 사용합니다. Restore만 별도 스냅샷, journal, 보상 처리를 유지합니다. -- 메시지 본문, 세션 제목, 인증 정보, `auth.json`, `updated_at`은 수정하지 않습니다. -- SQLite가 사용 중이면 Codex, Codex App, app-server를 닫은 뒤 다시 시도하세요. -- 활성 세션이 rollout을 잠그면 나머지 파일은 계속 처리합니다. 세션 종료 후 다시 동기화하면 됩니다. -- Provider 또는 계정을 바꾼 뒤 이전 세션을 계속하면 대상 백엔드가 `encrypted_content`를 복호화하지 못해 대화 계속 또는 compact가 실패할 수 있습니다. 이 경우 원래 Provider/계정으로 돌아가거나 새 세션을 시작하세요. -- Windows에서는 WSL UNC SQLite Home에 직접 쓸 수 없습니다. WSL에서 Linux 경로로 CLI를 실행하세요. +### 잘못 동기화했으면 어떻게 복원하나요? -## 문서 +**Backups / Restore**에서 작업 전 백업을 선택해 복원합니다. CLI에서는 `codex-provider restore `를 사용합니다. -- [AI / Agent 가이드](../AGENTS.md) -- [Legacy .NET Windows GUI (중국어)](README_GUI_ZH.md) -- [현재 Core 아키텍처(중국어)](architecture/NODE_CORE_ARCHITECTURE_ZH.md) -- [V1 Electron 데스크톱 앱 가이드(영어)](README_DESKTOP_EN.md) -- [Web UI (중국어)](README_WEB_UI_ZH.md) -- [中文](../README.md) · [English](README_EN.md) · [日本語](README_JA.md) -- [macOS GUI: 中文](README_MAC_GUI_ZH.md) · [English](README_MAC_GUI_EN.md) -- [작동 원리 (중국어)](WORKING_PRINCIPLE_ZH.md) · [변경 이력](../CHANGELOG.md) · [기여 안내](../CONTRIBUTING.md) +경로 설정과 WSL은 [CLI 가이드(중국어)](README_CLI_ZH.md), 동기화 읽기/쓰기와 속도는 [작동 원리(중국어)](WORKING_PRINCIPLE_ZH.md)를 참조하세요. -## 개발 +## 문서와 개발 -워크스페이스, Web, Electron 빌드는 Node 24를 사용합니다. 설치된 루트 CLI는 Node 16.20.2 이상을 지원합니다. 현재 Core 가이드를 먼저 읽고 Provider I/O 불변 조건을 유지하세요. +- [문서 색인(중국어)](README_ZH.md) · [변경 이력](../CHANGELOG.md) · [Issue 신고](https://github.com/Dailin521/codex-provider-sync/issues) +- [작동 원리(중국어)](WORKING_PRINCIPLE_ZH.md) · [현재 Node Core 아키텍처(중국어)](architecture/NODE_CORE_ARCHITECTURE_ZH.md) +- [기여 및 빌드](../CONTRIBUTING.md) · [마이그레이션과 릴리스 게이트](migration/VNEXT_MIGRATION_EXECUTION_INDEX_ZH.md) · [AI / Agent 가이드](../AGENTS.md) ```bash npm ci npm run architecture:check -npm run web:build -npm run web:start npm test -dotnet test desktop/CodexProviderSync.Core.Tests/CodexProviderSync.Core.Tests.csproj +npm run web:build +npm run desktop:build ``` -유지관리자는 Windows GUI Release와 별도로 CLI/Web 패키지를 게시할 수 있습니다. [npm 게시 안내(중국어)](NPM_PUBLISHING.md)를 참조하세요. - -## 감사의 말 - -로컬 Web UI를 제안하고 구현했으며, 채팅 기록 탐색과 다국어 문서의 기반을 기여하고 [PR #80](https://github.com/Dailin521/codex-provider-sync/pull/80)을 통해 v0.5.0에 도입한 [@tangquanwei](https://github.com/tangquanwei), 그리고 코드, 문서, 테스트와 문제 조사에 기여한 모든 분께 감사드립니다. - -[기여자 목록](../CONTRIBUTORS.md) · [GitHub Contributors](https://github.com/Dailin521/codex-provider-sync/graphs/contributors) - -## License - -MIT +## 감사의 말과 라이선스 -### 큰 세션 메타데이터 +로컬 Web UI, 기록 탐색과 다국어 문서 기반을 제공하고 [PR #80](https://github.com/Dailin521/codex-provider-sync/pull/80)을 통해 v0.5.0에 도입한 [@tangquanwei](https://github.com/tangquanwei), 그리고 코드, 문서, 테스트와 문제 조사에 기여한 모든 분께 감사드립니다. -Node 기반 Electron, CLI, Web은 줄바꿈을 제외한 UTF-8 기준 최대 128 MiB의 첫 줄 메타데이터를 지원합니다. 크기 초과나 잘못된 형식의 데이터는 원인을 기록하고 건너뛰며, 정상 세션의 동기화는 계속합니다. 원인을 해결한 후 새 미리보기로 다시 동기화하세요. 대화 파일 전체의 크기 제한은 아닙니다. +[기여자](../CONTRIBUTORS.md) · [GitHub Contributors](https://github.com/Dailin521/codex-provider-sync/graphs/contributors) · [LINUX DO](https://linux.do/) · [MIT License](../LICENSE) diff --git a/docs/README_WEB_UI_ZH.md b/docs/README_WEB_UI_ZH.md index 03fa322..9077fb2 100644 --- a/docs/README_WEB_UI_ZH.md +++ b/docs/README_WEB_UI_ZH.md @@ -1,9 +1,5 @@ # Web 版使用说明 -非法 UTF-8、数组 payload 或超出首行处理能力的数据会跳过并显示具体原因,关联索引保留;请处理后重新预览。不会自动转码或增加固定嵌套层数限制。 - -问题会话会被跳过,其关联索引保留原样;正常会话继续同步/切换。有跳过会显示“部分完成”,在预览、结果和操作日志可查看原因及本机完整路径(最多 200 项)。处理问题后重新预览即可纳入;全部历史跳过时,切换仍会备份并更新配置。数据库/目录/备份等全局故障仍停止,已写入时保留备份。诊断导出移除路径和索引标识。 - Web 版是 CLI 提供的本地浏览器界面,复用与桌面版相同的 Node 同步核心。它只在本机运行,不需要把 Codex 数据上传到远程服务;不提供桌面端的操作日志、原生目录选择器和应用更新。 本页说明 V1 界面。npm 安装得到的是已发布版本,未发布的 V1 功能需要对应本地构建;下载和版本状态以实际发布说明为准。 @@ -105,12 +101,14 @@ codex-provider web --no-open ## 注意事项 +非法 UTF-8、数组 payload 或超出首行处理能力的数据会跳过并显示具体原因,关联索引保留;请处理后重新预览。不会自动转码或增加固定嵌套层数限制。 + +问题会话会被跳过,其关联索引保留原样;正常会话继续同步/切换。有跳过会显示“部分完成”,在预览、结果和操作日志可查看原因及本机完整路径(最多 200 项)。处理问题后重新预览即可纳入;全部历史跳过时,切换仍会备份并更新配置。数据库/目录/备份等全局故障仍停止,已写入时保留备份。诊断导出移除路径和索引标识。 + +Local Web 与 Electron 共用 Node Core,支持最高 128 MiB 的会话首行元数据。超限或格式无效的数据会跳过并记录原因,正常部分继续同步。该上限不限制整个聊天记录的大小。 + 建议在同步或恢复前结束正在使用相关数据的 Codex 会话。同步中仍在使用的会话可能暂时跳过,其他内容会继续处理;这时结果会显示为“部分完成”,请在会话结束后再次同步。Restore 无法安全执行时会阻止恢复或进入恢复处理,不能套用普通同步的跳过语义。如果本地聊天索引 busy,请停止相关 Codex 写入后重试。 普通 Sync/Switch/Repair 写入后失败不自动全量回滚;查看结果和备份后选择重试或手动恢复。状态待刷新不等于工具正在写入,不要通过删除锁文件解决。 [返回首页](../README.md) · [CLI 指南](README_CLI_ZH.md) · [工作原理](WORKING_PRINCIPLE_ZH.md) - -### 大首行会话 - -Local Web 与 Electron 共用 Node Core,支持最高 128 MiB 的会话首行元数据。超限或格式无效的数据会跳过并记录原因,正常部分继续同步。该上限不限制整个聊天记录的大小。 diff --git a/docs/README_ZH.md b/docs/README_ZH.md index 9e14056..327df90 100644 --- a/docs/README_ZH.md +++ b/docs/README_ZH.md @@ -4,6 +4,7 @@ ## 用户指南 +- [常见问题(FAQ)](../README.md#常见问题faq):切换后同步、部分完成与恢复 - Electron 桌面版:[中文](README_DESKTOP_ZH.md) · [English](README_DESKTOP_EN.md) - [Web UI:启动、配对、同步与 SSH](README_WEB_UI_ZH.md) - [CLI:命令、模型策略、路径、备份与 JSON](README_CLI_ZH.md) diff --git a/docs/VNEXT_ELECTRON_NODE_ARCHITECTURE_ZH.md b/docs/VNEXT_ELECTRON_NODE_ARCHITECTURE_ZH.md index a08fcba..13502ad 100644 --- a/docs/VNEXT_ELECTRON_NODE_ARCHITECTURE_ZH.md +++ b/docs/VNEXT_ELECTRON_NODE_ARCHITECTURE_ZH.md @@ -1971,7 +1971,7 @@ SHA256SUMS.txt ### 26.3 自动更新 -启动检查按用户确认调整为每天首次启动一次(本地日期,延迟15秒,持久化去重),失败或无新版静默;只有更高版本才弹窗提示。没有循环检查,持续运行跨日也不自动触发;手动按钮保留,不自动下载或安装。旧Windows .NET单EXE更新不能直接迁移到Electron多文件产物,首次需手动安装或另做过渡升级器。 +启动检查按用户确认调整为每天首次启动一次(本地日期,延迟5秒,持久化去重),失败或无新版静默;只有更高版本才弹窗提示。没有循环检查,持续运行跨日也不自动触发;手动按钮保留,不自动下载或安装。旧Windows .NET单EXE更新不能直接迁移到Electron多文件产物,首次需手动安装或另做过渡升级器。 V1本地交付补充见[ADR-0020](adr/0020-desktop-manual-release-check.md):设置显示当前版本及检查入口。便携/未授权本地打包版采用手动公开版本检查与官方下载页,不启动自动安装;授权安装版保留electron-updater。检查/下载错误可见、进度事件推送,无轮询。实现更新能力不等同于已发布更新通道。 diff --git a/docs/adr/0014-npm-workspace-and-dependency-boundaries.md b/docs/adr/0014-npm-workspace-and-dependency-boundaries.md index fee4dbe..f7021c3 100644 --- a/docs/adr/0014-npm-workspace-and-dependency-boundaries.md +++ b/docs/adr/0014-npm-workspace-and-dependency-boundaries.md @@ -1,5 +1,8 @@ # vNext/ADR-0014:npm Workspace、根发布包与依赖边界 +> 2026-09-15 修订:[ADR-0046](0046-user-directed-update-install.md) 取消本文件历史条款中的更新安装前存储、写操作、Watch 与恢复状态拦截;已下载更新由用户决定安装。 + + - Status: Accepted - Date: 2026-08-25 - Amended: 2026-09-03 (Node Core ownership transfer and compatibility-adapter boundary) diff --git a/docs/adr/0020-desktop-manual-release-check.md b/docs/adr/0020-desktop-manual-release-check.md index 36e6d7d..96cb7ae 100644 --- a/docs/adr/0020-desktop-manual-release-check.md +++ b/docs/adr/0020-desktop-manual-release-check.md @@ -1,5 +1,8 @@ # ADR-0020:桌面更新入口与便携版查更 +> 2026-09-15 修订:[ADR-0046](0046-user-directed-update-install.md) 取消安装前的存储、写操作、Watch 与恢复状态拦截,已下载更新由用户决定安装。 + + - Status: Accepted - Date: 2026-09-04 - Scope: 用户要求参考旧版更新体验;本地实现,不授权发布、签名或更新通道上线。 @@ -10,7 +13,7 @@ - 已配置且已获发布授权的安装版继续使用 Main 的 electron-updater;Windows 需要同目录 NSIS uninstaller,Linux 需要 AppImage,否则走手动方式。不能仅凭应用版本不为0判定更新通道已配置,必须存在 app-update.yml。 - 便携/本地打包版新增独立手动查更,不再把整个更新入口隐藏。检查固定公开仓库 Releases 的最多最近30项,不需要token;仅考虑非draft、非prerelease、版本更高且有当前平台Electron工件的版本,不把Legacy .NET EXE视为新版。 -- 手动查更15秒截止、响应最多2MiB;网络/API失败显示失败,不能显示为最新。按用户确认,每天首次启动后延迟15秒检查一次;按本地日期在userData中独占创建标记,先记录再检查,当天失败也不自动重试,跨进程重启不重复。持续运行跨日不另设定时检查,手动检查不限次数。标记不可写时跳过自动检查。 +- 手动查更15秒截止、响应最多2MiB;网络/API失败显示失败,不能显示为最新。按用户确认,每天首次启动后延迟5秒检查一次;按本地日期在userData中独占创建标记,先记录再检查,当天失败也不自动重试,跨进程重启不重复。持续运行跨日不另设定时检查,手动检查不限次数。标记不可写时跳过自动检查。 - 手动模式的“打开官方下载页”只由 Main 打开固定 GitHub Releases 页面。它不下载/执行安装器、不覆盖程序文件、不触发重启;页面说明需用户手动替换。未授权候选仍不能创建 electron-updater port 或自动安装。 - 状态schema v2兼容增加可选 currentVersion/mode(manual)。新增只读更新事件推送用于下载百分比,不增加轮询。Renderer只得到版本/阶段/百分比/错误码,不得到下载路径、任意URL、release notes或凭据。 - 下载/安装仍由用户触发;现有写入、Watch及恢复状态检查继续生效。错误清除不再适用的version/progress,保证错误状态能通过Preload校验并显示。 diff --git a/docs/adr/0042-windows-updater-and-version-reminders.md b/docs/adr/0042-windows-updater-and-version-reminders.md index 81a09e3..c694115 100644 --- a/docs/adr/0042-windows-updater-and-version-reminders.md +++ b/docs/adr/0042-windows-updater-and-version-reminders.md @@ -1,5 +1,8 @@ # ADR-0042:Windows 安装版更新与按版本提醒 +> 2026-09-15 修订:[ADR-0046](0046-user-directed-update-install.md) 取消本文件历史条款中的更新安装前存储、写操作、Watch 与恢复状态拦截;已下载更新由用户决定安装。 + + - Status: Accepted - Date: 2026-09-11 - Scope: Desktop Host、共享设置界面、显式 Windows 发布渠道;Core/CLI/Provider I/O 不变。 diff --git a/docs/adr/0046-user-directed-update-install.md b/docs/adr/0046-user-directed-update-install.md new file mode 100644 index 0000000..3983776 --- /dev/null +++ b/docs/adr/0046-user-directed-update-install.md @@ -0,0 +1,32 @@ +# ADR-0046:更新安装由用户决定 + +- Status: Accepted +- Date: 2026-09-15 +- Scope: Electron Main 更新器与共享设置页。 + +## 原因 + +已下载的更新会因存储 Profile 状态无法验证而被禁用,用户也无法再次点击安装。安装应用不应以会话数据、Watch 或恢复状态检查通过为前提。维护者明确要求取消这些安装限制。 + +## 决策 + +- 已下载的安装版更新始终提供“重启并安装”。用户点击后直接调用安装器,不刷新 Profile Status,不查询 Watch,不检查 pending recovery 或写操作,不等待安装前写队列排空。 +- Main 的 restart lease 只用于安装开始后阻止新增业务操作;无法取得 lease 也不否决安装。保留正常退出清理、重复点击去重和安装器失败处理,失败时释放已取得的 lease。 +- 设置页不因写操作、恢复状态、Watch 或 Status 读取失败禁用安装按钮。下载完成前和安装进行中仍按更新状态显示操作。 +- 保留现有安装版/便携版渠道、包下载与完整性校验、用户显式安装、窄 IPC 和错误脱敏。Core 的同步、备份、恢复合同不变。 +- Host Update schema v2 不变;旧 `installBlockedReason` 字段保留解析兼容,新 Main 不再生成存储状态拦截原因。 + +本决策取代 ADR-0014、ADR-0020、ADR-0042 中将写操作、Watch、Profile 恢复复核用作更新安装前置条件的条款。 + +## 验证 + +`updater.test.mjs` 覆盖写操作、Watch、恢复阻塞、不可验证/查询失败、不可取得 lease 时仍安装,且不调用存储检查或写队列等待;覆盖重复点击和同步/异步安装失败。`settings-updates.vitest.tsx` 覆盖对应状态下安装按钮可用。 + +旧版二进制保留旧行为,需手动运行新版安装包完成首次迁移;本地源码修改不意味着新版已发布或真实跨版本升级已经验收。 + +### 本地验证(2026-09-15) + +- `npm run workspaces:check` 通过;更新器/策略/真实模块定向测试 23 项通过,设置页含四类状态的安装回归通过。 +- `npm run desktop:build`、`npm run desktop:verify-production-bundle` 通过。 +- `TEMP/TMP=D:\Temp` 下 `npm run desktop:test:e2e:production` 六项通过,包括真实 SQLite、Sync → Restore、128 MiB 首行与混合问题数据。隐藏窗口截图触发既有重试后通过;未改截图逻辑。 +- 本次未运行真实安装器升级、跨平台 CI 或发布;当前安装版未被修改。 diff --git a/docs/architecture/contracts/CORE_EXTERNAL_BEHAVIOR_ZH.md b/docs/architecture/contracts/CORE_EXTERNAL_BEHAVIOR_ZH.md index 66f1f0e..11fc8da 100644 --- a/docs/architecture/contracts/CORE_EXTERNAL_BEHAVIOR_ZH.md +++ b/docs/architecture/contracts/CORE_EXTERNAL_BEHAVIOR_ZH.md @@ -1024,7 +1024,7 @@ V1/C3 已实现上述边界;`runSync/runSwitch/runRepair/runRestore/runWatch` ### 16.7 C8 Electron Restore / Watch / Diagnostics / Update 候选边界 -ADR-0042 补充:`stable-updater` 显式启用 Windows 安装版应用内下载/重启安装,便携版保持手动。新增 Host `updates.setReminder({schemaVersion:1,version,ignored})` 窄 IPC,版本只用于当前版本提醒偏好,不指定下载/安装目标;schema v2 可选 `reminderIgnored`。Main 原子保存偏好,设置页和新版弹窗共用;手动检查/更新仍可用,下个新版继续提醒,保存失败不能假报成功。详见 [ADR-0042](../../adr/0042-windows-updater-and-version-reminders.md)。CoreFacade 与既有安装守卫不变。 +ADR-0042 补充:`stable-updater` 显式启用 Windows 安装版应用内下载/重启安装,便携版保持手动。新增 Host `updates.setReminder({schemaVersion:1,version,ignored})` 窄 IPC,版本只用于当前版本提醒偏好,不指定下载/安装目标;schema v2 可选 `reminderIgnored`。Main 原子保存偏好,设置页和新版弹窗共用;手动检查/更新仍可用,下个新版继续提醒,保存失败不能假报成功。详见 [ADR-0042](../../adr/0042-windows-updater-and-version-reminders.md)。CoreFacade 不变;安装前置条件由 ADR-0046 修订。 - DesktopCoreClient、Preload、Main IPC、Supervisor 与 Utility 只按精确方法组增加 `prepareRestore/applyRestore`、`pruneBackups/startWatch/stopWatch/getWatchStatus`。Main 持有 Restore Plan 与 Watch ID;Renderer 只提交 profile、受管 backupId、Restore options、keepCount 或有限 Watch 输入。 - Recovery Required 时,Sync/Switch/startWatch 继续阻断;Restore 与 Prune 可作为 recovery-safe 操作进入 Core,stop/get Watch status 仍可用。Restore Apply 属于 cancellable write lifecycle,完成后使 Supervisor 的 Status preflight 失效并重新读取。 @@ -1032,7 +1032,7 @@ ADR-0042 补充:`stable-updater` 显式启用 Windows 安装版应用内下载 - Watch 每次 apply 都重新 Prepare/Apply 并获取 Codex Home 锁;SQLite 竞争由该次原生事务裁决。已 Prepare 的人工 Plan 具有优先级;Watch 合并重复事件并等待人工 intent 释放或过期,只运行一次 follow-up。首次遇到 `RECOVERY_REQUIRED/PENDING_TRANSACTION` 即停止,不继续自动写。同一物理 Codex Home 只能有一个 active/pending Watch;停止后释放 scope,终态历史有界。 - Diagnostics Renderer 请求严格只有 `{schemaVersion:1, profile}`。输出目标由 Main 原生文件选择器产生并转换为 5 分钟、单次消费的随机 capability;最多保留 32 个未消费 capability,同一规范化目标只能被一个 capability 保留,写入前还必须按父目录 realpath 拒绝指向同一物理 ZIP 的并发路径别名。过期、显式 revoke、消费成功或失败都会释放目标 reservation;同 token 并发导出最多一方成功。token 和目标路径不跨 Renderer。ZIP 条目固定且再次执行共享 Diagnostics DTO exact validation,排除 `auth.json`、凭据、token、路径、rollout/DB、消息正文与 `encrypted_content`。 - Update 只由 Main 的 `electron-updater` controller 管理,固定使用打包 metadata 中的 GitHub provider;不得调用 `setFeedURL`,Renderer 不得提交 URL、channel、路径、版本、silent/force 参数或接收 release notes、下载 URL、缓存路径和原始异常。Preload 仅暴露无参数的 `getStatus/check/download/install`,响应为脱敏 schema v2 状态。 -- `autoDownload` 与 `autoInstallOnAppQuit` 均关闭。检查、下载或更新错误不得改变 Core 结果。安装意图必须在 Supervisor 内同步关闭 restart gate,将已经入场但尚处于 preflight/dispatch 前的写请求计入 admission,并等待这些请求排空;此后新的 Sync/Switch/Restore/Prune/startWatch 立即返回 busy,只有 `getWatchStatus` 仍为只读。排空后,Main 必须通过既有 Utility `getWatchStatus` 重新核对其持有的 Watch ownership,清除已自动停止的缓存;查询失败或仍有 active Watch 时 fail closed。随后还必须确认 update 已下载、无写操作,并对全部已知 Profile 强制刷新 Status、证明无 pending recovery,最后才可调用 `quitAndInstall`;任一 Profile 无法验证、installer 抛错或安装未启动时均 fail closed 并重新开放 gate。 +- ADR-0046 修订:`autoDownload` 与 `autoInstallOnAppQuit` 均关闭。下载完成后安装由用户决定,不以写操作、Watch、pending recovery 或全部 Profile Status 复核为前提。Main 不执行安装前存储扫描或等待写队列排空;restart lease 只阻止安装开始后的新业务操作,无法取得 lease 不否决安装。保留正常退出清理、重复点击去重及安装器失败后的 lease 释放。Host Update schema v2 不变,旧拦截原因字段仅保留兼容解析。详见 [ADR-0046](../../adr/0046-user-directed-update-install.md)。 - C8 只接入受控状态机和门禁。只有 Main 编译期 `releaseAuthorized=true`、packaged、受支持目标且版本/通道已配置时才允许创建 updater port、排定检查或执行任何网络/安装动作;缺省及所有未授权候选固定为 `disabled/not-authorized`。C9 候选显式注入 `releaseAuthorized=false`;签名、Update metadata、跨版本 packaged smoke,以及覆盖外部 CLI/Web/Watch 的跨运行时 maintenance lease 仍属于发布前门禁,未获得发布授权前不得把通道描述为已上线。 ### 16.8 C9 Electron 候选产物与 CI 边界 @@ -1054,9 +1054,9 @@ ADR-0040:Prepare/普通写内部失败可携带白名单 `failureStage/causeCo - ADR-0027:保留数量仅在“备份与恢复”设置,范围 1–1000、默认 2;Host 持久化一份应用偏好,各 Codex Home 分别应用,不按操作类型分池。共享 UI 的 Sync/Switch/Repair 表单不再带 keepCount,由 App 在 Prepare 时注入,Watch 在启动时注入。保存只查本 Core 全部 Watch 并写 Host 偏好,不清理备份;活动 Watch/读取失败/持久化失败不保存。手动清理采用已保存值,草稿未保存时不允许清理;高级“删除全部可清理备份”仍在确认后调用 keep=0,自动策略不能设为 0。Restore 快照/journal/受保护备份及 CLI `--keep` 不变。 -- 更新检查启动策略补充:每天首次启动后延迟15秒尝试一次,使用userData本地日历日期标记跨启动去重;失败也消耗当天自动检查资格,持久化失败则跳过自动检查,手动检查不限。只有发现新版才由Main显示原生提示,不自动下载或安装;日常Status/Diagnostics/History仍无后台刷新。启动检查记操作日志,更新推送由UI根级接收。 +- 更新检查启动策略补充:每天首次启动后延迟5秒尝试一次,使用userData本地日历日期标记跨启动去重;失败也消耗当天自动检查资格,持久化失败则跳过自动检查,手动检查不限。只有发现新版才由Main显示原生提示,不自动下载或安装;日常Status/Diagnostics/History仍无后台刷新。启动检查记操作日志,更新推送由UI根级接收。 -- ADR-0020补充16.7:便携/未授权本地打包版允许用户手动检查固定公开GitHub Releases(15秒/2MiB/最多30项),只筛选较新正式版及当前平台Electron资产;不实例化electron-updater、不自动下载/安装。下载按钮在此模式仅由Main打开固定官方版本页。授权安装版继续既有安装门禁;currentVersion/mode为schema v2可选字段,Preload增加只读subscribe更新状态,UI接收推送而非轮询。检查失败明确显示且可重试;不更改发布授权。 +- ADR-0020补充16.7:便携/未授权本地打包版允许用户手动检查固定公开GitHub Releases(15秒/2MiB/最多30项),只筛选较新正式版及当前平台Electron资产;不实例化electron-updater、不自动下载/安装。下载按钮在此模式仅由Main打开固定官方版本页。授权安装版按 ADR-0046 由用户决定安装;currentVersion/mode为schema v2可选字段,Preload增加只读subscribe更新状态,UI接收推送而非轮询。检查失败明确显示且可重试;不更改发布授权。 - Overview 新增“直接同步”,点击即授权执行,无二次确认;内部连续调用 prepareSync/applySync,Apply 仍仅传 schemaVersion/planId。保留“预览同步”。Prepare 阶段的取消/失败不进入 Apply;运行中阻止重复提交与 Profile 切换,沿用进度、取消、结果及默认 2 份备份。 - Desktop 完整诊断独立请求预算为 15 分钟,不沿用普通只读的 30 秒。UI 显示扫描中、失败、重试和成功时间;重试失败时保留并明确标记上次成功结果。仍按 Profile/revision 隔离、无后台扫描。 diff --git a/docs/migration/BEHAVIOR_FIXTURES_ZH.md b/docs/migration/BEHAVIOR_FIXTURES_ZH.md index 2336805..e7cefed 100644 --- a/docs/migration/BEHAVIOR_FIXTURES_ZH.md +++ b/docs/migration/BEHAVIOR_FIXTURES_ZH.md @@ -181,7 +181,7 @@ V1 本地交互补充 fixture:`packages/app-ui/tests/direct-sync.vitest.tsx` | `restore-v2-windows-path-alias` | 同一 Windows 物理 source 分别以 8.3 短路径、长路径与 junction 创建/恢复 journal | Node↔.NET 两个方向均把 Prepare/Apply/journal 绑定到稳定物理 source,另一运行时可用另一别名恢复;无法证明时 fail closed,不以 lexical 字符串、旧式 backupId 或换接后的当前目标放行 | | `restore-v2-reparse-swap` | snapshot/apply 后把 rollout 或其父目录换成指向 Home 外的 junction/symlink,再进入 compensation/ack | 每个 mutation/ack 边界重新验证物理 Home 与 reparse segment;外部目标字节不变,journal 进入 `recovery-required`,不得按相同内容 hash 误确认 | | `restore-v2-ack-reconciliation` | `committed-pending-ack` 已持久化但 API acknowledgement/observer 失败 | 不把已提交 Restore 报为可回滚失败;重新读取 journal 与目标 Hash 后收敛到 `completed` 或 `recovery-required` | -| `desktop-update-install-gate` | Update 已下载,同时注入已入场但未 dispatch 的 write、后续 write、自动停止/active Watch、pending recovery、无法验证 Profile、installer 异常或安装竞争 | 未授权候选的 check/download/install/timer 均不创建 updater port;获授权路径先关闭 gate、排空写、由 Main 重新查询 Utility Watch 状态并刷新全部 Profile。任一条件不安全或查询/installer 失败都不退出并重新开放 gate。IPC 为 null-only,DTO 不含 URL/path/release notes/raw error | +| `desktop-update-install-gate` | Update 已下载,注入写操作、active Watch、pending recovery、无法验证 Profile、不可取得 lease、重复点击与 installer 异常 | ADR-0046:下载后允许安装;不调用存储检查或等待写队列,以上状态均不否决用户安装。重复点击仅安装一次,installer 同步/异步失败释放 lease;保留渠道隔离、null-only IPC 与 DTO 脱敏。测试:`updater.test.mjs`、`settings-updates.vitest.tsx` | | `desktop-diagnostics-capability-bound` | Renderer 重复请求诊断导出、复用 token、并发消费、选择相同目标或以路径别名指向同一物理 ZIP | 目标仅由 Main 选择;5 分钟 TTL、最多 32 个 pending、规范化目标独占 reservation,写入时按父目录 realpath 拒绝物理目标并发、单次消费;过期/revoke/完成后释放且 ZIP 无路径/凭据/正文 | | `bidirectional-backup-roundtrip` | Node Backup→.NET Restore;.NET Backup→Node Restore | 两个方向恢复到等价语义状态;正文和不应变化字段逐字节一致;Metadata v1/v2 兼容边界明确 | | `historical-tag-produced-backup-restore` | 从冻结 commit 的 `v0.2.9`/`v0.4.1` tag 源构建历史 .NET Core,真实产生 synthetic metadata v1/v2 backup,再由当前 Node Restore | config/rollout/SQLite 恢复;tag commit、metadata/tree Hash 与 synthetic-only 声明进入 CI artifact。证据等级仅为 repository-tag-source,不等于 hosted formal Release binary 或真实用户数据 | diff --git a/docs/release-notes/v1.0.3-zh.md b/docs/release-notes/v1.0.3-zh.md index 605e73e..d94fa09 100644 --- a/docs/release-notes/v1.0.3-zh.md +++ b/docs/release-notes/v1.0.3-zh.md @@ -1,49 +1,35 @@ - + -# v1.0.3:问题会话跳过与大首行同步修复 +# v1.0.3:会话同步与更新体验改进 -修复一条异常会话导致整次同步或切换中止的问题。支持最高 128 MiB 的会话首行元数据;首行无效、超限、无法读取或占用时,跳过该文件并显示实际原因,让正常会话继续处理。 +此版本让 Provider 切换后的历史会话同步更可靠,也让桌面版更新更直接。 -## 本版变化 +## 会话同步 -- 普通和归档会话均逐文件处理,跳过文件的关联 SQLite 索引保留原样;无法确认归属的索引保守保留。 -- 修复非法 UTF-8 被静默替换、数组 payload 误报成功,以及首行序列化或语义比较超出处理能力导致整次中止的问题。不设固定 JSON 嵌套层数限制。 -- 修复首行恰好达到大小上限且使用 CRLF 换行时的误判。等字节长度 Provider 继续原地更新,其他合法首行使用流式复制并保留正文。 -- 有跳过时显示“部分完成”,分别统计成功、跳过及未确认数量;本机明细最多展示 200 项路径和原因,诊断导出单独脱敏。 -- 全部历史跳过且没有其他写目标时,同步不创建无用备份;切换仍备份并更新配置,明确显示历史成功 0 条。预览中排除的数据修好后,需重新预览才能纳入。 +- 支持最高 **128 MiB** 的会话首行元数据。 +- 单个会话的首行编码、格式或大小有问题时,跳过该会话并显示原因;正常会话继续同步或切换。 +- 问题会话关联的 SQLite 索引保持不变。修复数据后重新预览即可纳入。 +- 修复非法 UTF-8、数组形式的 `payload`、复杂 JSON 以及 CRLF 边界导致的错误处理。 +- Provider 字节等长时继续原地更新;其他有效会话使用流式复制,聊天正文保持不变。 -## 📦 下载 +## 桌面版更新 -- 安装版:`CodexProviderSync-1.0.3-windows-x64-setup.exe`。 -- 便携版:`CodexProviderSync-1.0.3-windows-x64-portable.zip`,完整解压后运行 `Codex Provider Sync.exe`。 -- [官方 Release](https://github.com/Dailin521/codex-provider-sync/releases/tag/v1.0.3) 提供 SHA-256、SBOM 和容器验收文件。 +- 每天首次启动后约 **5 秒**自动检查一次更新;也可随时手动检查。 +- 下载完成后是否重启安装由你决定。存储状态、同步写入、Watch 或恢复状态不会再禁用安装按钮。 +- 更新不会替换 Codex 的会话、配置或备份数据。 -本版未签名,Windows 可能显示 SmartScreen 提示,请核对官方来源和 SHA-256。本次仅发布 Windows x64 Electron;不发布 npm、macOS/Linux 或 Legacy 安装包。 +## 使用提示 -## ⬆️ 升级说明 +- “部分完成”表示正常会话已经继续处理。格式或大小问题请处理数据后重新预览;会话仍被占用或文件发生变化时,停止写入后再同步。 +- Provider 信息一致后仍无法继续的旧会话,请查看 Codex 的具体报错;加密内容或模型兼容问题可能需要回到原 Provider / 账号,或新建会话。 +- Windows x64 提供安装版和便携 ZIP。便携版请完整解压后运行。 -1.0.2 安装版可检查更新,由用户确认下载及重启安装,也可手动安装。1.0.1 的更新检查故障需要手动升级。便携版仍手动下载并完整解压;旧 .NET 单 EXE 更新器不能直接迁移至 Electron。 +## 下载 -保留现有 Codex 数据、配置和备份,无需删除数据库或锁文件。同步后查看跳过明细:格式或编码问题需处理后重新预览,占用或变化可在会话结束后再同步。 +- 安装版:`CodexProviderSync-1.0.3-windows-x64-setup.exe` +- 便携版:`CodexProviderSync-1.0.3-windows-x64-portable.zip` +- [官方 Release](https://github.com/Dailin521/codex-provider-sync/releases/tag/v1.0.3) 提供校验文件。 -## 🛡 安全保障 +本版为未签名 Windows x64 Electron 包。macOS/Linux Electron、npm 和 Legacy 包独立发布。 -不读取 `auth.json` 或凭据;Provider 同步不解析聊天正文,不修改 `updated_at` 或 `encrypted_content`。实际写入先备份,默认保留 2 份受管备份,可手动回滚。 - -目录无法完整枚举、路径安全、配置或存储变化、数据库、备份、磁盘写满和写入结果不确定仍会停止操作;已有写入时保留备份并报告部分完成。只有明确未写入的文件才从恢复范围排除,SQLite 恢复仍采用整库快照。 - -## ⚠️ 重要说明 - -跳过不代表问题文件已被修复,也不代表全部历史已对齐。超过 128 MiB、BOM/UTF-16 等不支持的首行仍需先处理。元数据对齐不能保证跨 Provider 的会话继续或 compact,也不能解决原账号加密内容的解密限制。 - -Windows 的 WSL UNC SQLite 路径仍仅限诊断,应在对应 WSL 环境内操作。 - -## 🔍 验证结果 - -本地验证覆盖混合问题数据、关联索引保留、预览冻结、写前变化、备份恢复范围、日志重启及导出脱敏;近 128 MiB 真文件完成 Status、原地同步、流式切换和恢复回归。生产 Electron 覆盖大首行及混合数据 Sync → Restore。 - -发布门禁要求最终源码跨平台 CI、Node 16 根包兼容,以及实际安装版/便携版容器启动、SQLite、隔离数据同步恢复、正常退出和适用的卸载验收全部通过。实际源码 SHA、产物哈希及容器结果见 Release 审核附件。 - -公开更新源检查不等于线上跨版本下载、重启升级和数据保持验收,后者尚未完成独立验证。未新增签名、真实 WSL 或其他平台人工验收。 - -[使用说明](../README_DESKTOP_ZH.md) · [更新日志](../../CHANGELOG.md) +[使用说明](../../README.md) · [桌面指南](../README_DESKTOP_ZH.md) · [更新日志](../../CHANGELOG.md) diff --git a/images/README/provider-metadata-sync-flow-en.png b/images/README/provider-metadata-sync-flow-en.png new file mode 100644 index 0000000..77fd619 Binary files /dev/null and b/images/README/provider-metadata-sync-flow-en.png differ diff --git a/images/README/provider-metadata-sync-flow-ja.png b/images/README/provider-metadata-sync-flow-ja.png new file mode 100644 index 0000000..48b63c9 Binary files /dev/null and b/images/README/provider-metadata-sync-flow-ja.png differ diff --git a/images/README/provider-metadata-sync-flow-ko.png b/images/README/provider-metadata-sync-flow-ko.png new file mode 100644 index 0000000..2839b49 Binary files /dev/null and b/images/README/provider-metadata-sync-flow-ko.png differ diff --git a/images/README/provider-metadata-sync-flow-zh.png b/images/README/provider-metadata-sync-flow-zh.png new file mode 100644 index 0000000..2717803 Binary files /dev/null and b/images/README/provider-metadata-sync-flow-zh.png differ diff --git a/packages/app-ui/src/features/settings/SettingsPage.tsx b/packages/app-ui/src/features/settings/SettingsPage.tsx index 39f2646..7085117 100644 --- a/packages/app-ui/src/features/settings/SettingsPage.tsx +++ b/packages/app-ui/src/features/settings/SettingsPage.tsx @@ -163,7 +163,7 @@ export function SettingsPage({ props, profile, capabilities, recoveryBlocked, wr {reminder.isError ?

{t("settings.updateReminderFailed")}

: null} {update.data && ["idle", "not-available", "error", "available"].includes(update.data.state) && props.host.checkForUpdates ? : null} {update.data?.state === "available" && props.host.downloadUpdate ? : null} - {update.data?.state === "downloaded" && props.host.installUpdate ? : null} + {update.data?.state === "downloaded" && props.host.installUpdate ? : null} {update.data?.version && ["available", "downloaded"].includes(update.data.state) && props.host.setUpdateReminder ? : null}
diff --git a/packages/app-ui/tests/settings-updates.vitest.tsx b/packages/app-ui/tests/settings-updates.vitest.tsx index c8bff12..b611367 100644 --- a/packages/app-ui/tests/settings-updates.vitest.tsx +++ b/packages/app-ui/tests/settings-updates.vitest.tsx @@ -6,11 +6,30 @@ import { AppUi } from "../src/App.js"; import type { HostClient, HostUpdateStatus } from "../src/types.js"; import { statusFor } from "./helpers/app-fixtures.js"; -function mount(host: Partial) { - render( statusFor() })} capabilities={{ watch: false }} host={{ listProfiles: async () => [{ id: "default", name: "Default", revision: "profile-r1" }], ...host }} initialLocale="en" initialTheme="system" preferences={{ getLocale: () => "en", setLocale: () => {}, getTheme: () => "system", setTheme: () => {} }} />); +function mount(host: Partial, core = new MockCoreClient({ getStatus: async () => statusFor() }), watch = false) { + render( [{ id: "default", name: "Default", revision: "profile-r1" }], ...host }} initialLocale="en" initialTheme="system" preferences={{ getLocale: () => "en", setLocale: () => {}, getTheme: () => "system", setTheme: () => {} }} />); } describe("Desktop updates in Settings", () => { + it.each(["write", "recovery", "watch", "status-error"])("allows explicit installation during %s", async (scenario) => { + const user = userEvent.setup(); + const status = statusFor(); + if (scenario === "write") status.operationInProgress = { operationId: "held-operation", operation: "sync", actor: "external" }; + if (scenario === "recovery") status.pendingRecovery = true; + const core = new MockCoreClient({ + getStatus: async () => { if (scenario === "status-error") throw { code: "INTERNAL_ERROR" }; return status; }, + getWatchStatus: async () => ({ schemaVersion: 1, watchId: "watch-default", status: "running", startedAt: "2026-09-15T00:00:00.000Z", stoppedAt: null, stopReason: null, includeStateDb: true, once: false }) + }); + const install = vi.fn(async (): Promise => ({ state: "installing", installAllowed: false })); + mount({ getUpdateStatus: async () => ({ state: "downloaded", version: "1.0.4", installAllowed: true }), installUpdate: install }, core, scenario === "watch"); + await user.click(await screen.findByRole("button", { name: "Settings", exact: true })); + if (scenario === "watch") await screen.findByRole("button", { name: "Disable automatic sync" }); + const button = await screen.findByRole("button", { name: "Restart and install" }); + expect(button).toBeEnabled(); + await user.click(button); + await waitFor(() => expect(install).toHaveBeenCalledOnce()); + }); + it("ignores/restores only the displayed version and keeps explicit installer download/install usable", async () => { const user = userEvent.setup(); let status: HostUpdateStatus = { currentVersion: "1.0.1", state: "available", version: "1.0.2", installAllowed: false };