mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-08-02 18:41:35 +08:00
feat(codex): add opt-in migration and ledger-based restore for unified session history
- Enable dialog gains a checkbox (default off) to migrate existing official sessions from the built-in "openai" bucket into the shared "custom" bucket, with per-generation backups; failed migrations retry at startup - Disable dialog offers a precise restore driven by the backup ledger: only sessions recorded as "openai" in backups are flipped back, and sessions created while the toggle was on are never touched - Completion marker and backup generations are bound to the canonical Codex config dir; migrate/restore serialize on an op lock and the marker is written conditionally inside the settings write lock - save_settings rolls back the toggle and fails the save when the live rewrite fails; migration additionally requires the live config to actually route to the shared bucket (skips with live_not_unified so refused injection or proxy takeover can't split history) - Restore refuses to run while the toggle is (re-)enabled and reports nothing_to_restore instead of a zero-count success; local migration markers are now backend-owned in merge_settings_for_save so stale frontend payloads can't resurrect them - Settings autosave reverts optimistic form state on failure so a failed toggle change can't be replayed by an unrelated save - ConfirmDialog supports an optional checkbox; all four locales updated
This commit is contained in:
@@ -287,6 +287,10 @@ pub struct LocalMigrations {
|
||||
Option<CodexThirdPartyHistoryProviderBucketMigration>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub codex_provider_template_v1: Option<CodexProviderTemplateMigration>,
|
||||
/// 统一会话开关的官方历史迁移标记。开关关闭时会被清除,
|
||||
/// 这样重新开启能把"关闭期间"落入 openai 桶的官方会话补迁进来。
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub codex_official_history_unify_v1: Option<CodexOfficialHistoryUnifyMigration>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
@@ -312,6 +316,21 @@ pub struct CodexProviderTemplateMigration {
|
||||
pub migrated_provider_ids: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CodexOfficialHistoryUnifyMigration {
|
||||
pub completed_at: String,
|
||||
pub target_provider_id: String,
|
||||
#[serde(default)]
|
||||
pub migrated_jsonl_files: usize,
|
||||
#[serde(default)]
|
||||
pub migrated_state_rows: usize,
|
||||
/// 迁移时的规范化 Codex 目录。标记只对同一目录生效:
|
||||
/// 切换 codex_config_dir 后旧标记不会挡住新目录的迁移。
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub codex_config_dir: Option<String>,
|
||||
}
|
||||
|
||||
/// 应用设置结构
|
||||
///
|
||||
/// 存储设备级别设置,保存在本地 `~/.cc-switch/settings.json`,不随数据库同步。
|
||||
@@ -358,11 +377,15 @@ pub struct AppSettings {
|
||||
#[serde(default)]
|
||||
pub preserve_codex_official_auth_on_switch: bool,
|
||||
/// Run official Codex providers under the shared "custom" model_provider id
|
||||
/// so future official sessions share one resume-history bucket with
|
||||
/// third-party providers. Forward-only: existing sessions are not migrated.
|
||||
/// Opt-in: defaults to false.
|
||||
/// so official sessions share one resume-history bucket with third-party
|
||||
/// providers. Opt-in: defaults to false.
|
||||
#[serde(default)]
|
||||
pub unify_codex_session_history: bool,
|
||||
/// User opted in (via the enable dialog checkbox) to migrate existing
|
||||
/// official sessions ("openai" bucket) into the shared bucket. Persisted so
|
||||
/// a failed migration retries at startup; cleared when the toggle turns off.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub unify_codex_migrate_existing: Option<bool>,
|
||||
/// User has confirmed the failover toggle first-run notice
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub failover_confirmed: Option<bool>,
|
||||
@@ -482,6 +505,7 @@ impl Default for AppSettings {
|
||||
enable_failover_toggle: false,
|
||||
preserve_codex_official_auth_on_switch: false,
|
||||
unify_codex_session_history: false,
|
||||
unify_codex_migrate_existing: None,
|
||||
failover_confirmed: None,
|
||||
first_run_notice_confirmed: None,
|
||||
common_config_confirmed: None,
|
||||
@@ -766,6 +790,56 @@ pub fn mark_codex_provider_template_migrated(
|
||||
})
|
||||
}
|
||||
|
||||
/// 统一会话迁移标记是否覆盖指定目录。标记里没记目录(不应出现的旧格式)
|
||||
/// 视为不匹配——重跑迁移是幂等的,宁可重迁也不漏迁。
|
||||
pub fn is_codex_official_history_unify_migrated_for_dir(codex_dir: &str) -> bool {
|
||||
get_settings()
|
||||
.local_migrations
|
||||
.as_ref()
|
||||
.and_then(|migrations| migrations.codex_official_history_unify_v1.as_ref())
|
||||
.is_some_and(|migration| migration.codex_config_dir.as_deref() == Some(codex_dir))
|
||||
}
|
||||
|
||||
/// 条件写入迁移完成标记:仅当此刻开关仍开启且迁移意愿仍在时才写。
|
||||
/// 检查与写入在 settings 写锁内原子完成,与关闭开关路径
|
||||
/// (`update_settings` / 清标记)串行,消除"迁移线程复查开关后、写标记前
|
||||
/// 用户恰好关闭开关"的竞态窗口。返回是否实际写入。
|
||||
pub fn mark_codex_official_history_unify_migrated_if_enabled(
|
||||
migration: CodexOfficialHistoryUnifyMigration,
|
||||
) -> Result<bool, AppError> {
|
||||
let mut written = false;
|
||||
mutate_settings(|settings| {
|
||||
if settings.unify_codex_session_history
|
||||
&& settings.unify_codex_migrate_existing.unwrap_or(false)
|
||||
{
|
||||
settings
|
||||
.local_migrations
|
||||
.get_or_insert_with(Default::default)
|
||||
.codex_official_history_unify_v1 = Some(migration);
|
||||
written = true;
|
||||
}
|
||||
})?;
|
||||
Ok(written)
|
||||
}
|
||||
|
||||
pub fn clear_codex_official_history_unify_migration() -> Result<(), AppError> {
|
||||
mutate_settings(|settings| {
|
||||
if let Some(migrations) = settings.local_migrations.as_mut() {
|
||||
migrations.codex_official_history_unify_v1 = None;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn unify_codex_migrate_existing_requested() -> bool {
|
||||
get_settings().unify_codex_migrate_existing.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn clear_codex_unify_migrate_existing() -> Result<(), AppError> {
|
||||
mutate_settings(|settings| {
|
||||
settings.unify_codex_migrate_existing = None;
|
||||
})
|
||||
}
|
||||
|
||||
/// 从文件重新加载设置到内存缓存
|
||||
/// 用于导入配置等场景,确保内存缓存与文件同步
|
||||
pub fn reload_settings() -> Result<(), AppError> {
|
||||
|
||||
Reference in New Issue
Block a user