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:
Jason
2026-06-12 23:35:01 +08:00
parent 948d762792
commit eab6bfd20c
15 changed files with 1369 additions and 43 deletions
+17 -1
View File
@@ -279,6 +279,18 @@
"message": "Failover is an advanced feature. Please make sure you understand how it works before enabling.\n\nWe recommend configuring provider priorities in the failover queue first.",
"confirm": "I understand, enable"
},
"unifyCodexHistory": {
"title": "Unified Codex session history",
"message": "When enabled, the official subscription and third-party providers share one session history list. Note: resuming an old session across providers may fail because its encrypted_content reasoning cannot be decrypted by another backend.\n\nYou can also migrate your existing official session history into the shared list (originals are backed up to ~/.cc-switch/backups first and can be restored when you turn this off).",
"migrateExisting": "Also migrate existing official session history",
"confirm": "I understand, enable"
},
"unifyCodexHistoryOff": {
"title": "Turn off unified session history",
"message": "After turning this off, the official subscription and third-party providers return to separate history lists. Sessions created while it was on cannot be attributed to a provider, so they stay in the third-party history and the official subscription will not see them.",
"restoreBackup": "Restore the official sessions migrated at enable time back to the official history (exact restore from backup)",
"confirm": "Turn off"
},
"usage": {
"title": "Configure Usage Query",
"message": "Usage query requires a custom script or API parameters. Please make sure you have obtained the necessary information from your provider.\n\nIf unsure how to configure, please consult your provider's documentation first.",
@@ -671,7 +683,11 @@
"preserveCodexOfficialAuthOnSwitch": "Keep official login when switching third-party providers",
"preserveCodexOfficialAuthOnSwitchDescription": "When enabled, you can use the Codex app's official plugins, mobile remote control, and other features while using third-party APIs.",
"unifyCodexSessionHistory": "Unified Codex session history",
"unifyCodexSessionHistoryDescription": "When enabled, the official subscription runs under the shared \"custom\" provider id, so future official sessions appear in the same history list as third-party sessions (existing sessions are not migrated). Note: resuming an old session across providers may fail because its encrypted_content reasoning can only be decrypted by the backend that created it.",
"unifyCodexSessionHistoryDescription": "When enabled, the official subscription runs under the shared \"custom\" provider id so official and third-party sessions appear in one history list, optionally migrating existing official sessions in (backed up first). When turning it off, the migrated sessions can be restored from backup. Note: resuming an old session across providers may fail because its encrypted_content reasoning can only be decrypted by the backend that created it.",
"unifyCodexHistoryRestoreCompleted": "Official session history restored from backup ({{files}} session files, {{rows}} index rows)",
"unifyCodexHistoryRestoreFailed": "Failed to restore official session history, please try again",
"unifyCodexHistoryRestoreNothing": "No restorable migration backup for the current Codex directory",
"unifyCodexHistoryRestoreSkippedToggleOn": "Unified session history was re-enabled; restore skipped",
"appVisibility": {
"title": "Homepage Display",
"description": "Choose which apps to show on the homepage",
+17 -1
View File
@@ -279,6 +279,18 @@
"message": "フェイルオーバーは上級機能です。有効にする前に、その仕組みを理解していることをご確認ください。\n\nフェイルオーバーキューでプロバイダーの優先順位を先に設定することをお勧めします。",
"confirm": "理解しました、有効にする"
},
"unifyCodexHistory": {
"title": "Codex セッション履歴を統一",
"message": "オンにすると、公式サブスクリプションとサードパーティが同じセッション履歴リストを共有します。注意:プロバイダーをまたいで古いセッションを再開すると、encrypted_content を相手のバックエンドが復号できず失敗する場合があります。\n\n既存の公式セッション履歴を共有リストへ移行することもできます(移行前に ~/.cc-switch/backups へ自動バックアップされ、オフにする際に復元を選択できます)。",
"migrateExisting": "既存の公式セッション履歴も移行する",
"confirm": "理解しました、オンにする"
},
"unifyCodexHistoryOff": {
"title": "セッション履歴の統一をオフにする",
"message": "オフにすると、公式サブスクリプションとサードパーティはそれぞれ独立した履歴リストに戻ります。オン期間中に作成されたセッションは提供元を判別できないため、サードパーティの履歴に残り、公式サブスクリプションからは見えなくなります。",
"restoreBackup": "オンにした際に移行した公式セッションを公式履歴へ復元する(バックアップから正確に復元)",
"confirm": "オフにする"
},
"usage": {
"title": "使用量クエリの設定",
"message": "使用量クエリにはカスタムスクリプトまたは API パラメータが必要です。プロバイダーから必要な情報を取得していることをご確認ください。\n\n設定方法が不明な場合は、プロバイダーのドキュメントを先にご確認ください。",
@@ -671,7 +683,11 @@
"preserveCodexOfficialAuthOnSwitch": "サードパーティ切替時に公式ログインを保持",
"preserveCodexOfficialAuthOnSwitchDescription": "オンにすると、サードパーティ API を使用する際に Codex アプリの公式プラグインやスマホからのリモート操作などの機能を利用できます。",
"unifyCodexSessionHistory": "Codex セッション履歴を統一",
"unifyCodexSessionHistoryDescription": "オンにすると、公式サブスクリプションも共有の custom プロバイダー ID で動作し、今後の公式セッションはサードパーティのセッション同じ履歴リストに表示されます既存セッション移行しません)。注意:プロバイダーをまたいで古いセッションを再開すると、encrypted_content の推論内容を相手のバックエンドが復号できず、再開に失敗する場合があります。",
"unifyCodexSessionHistoryDescription": "オンにすると、公式サブスクリプションも共有の custom プロバイダー ID で動作し、公式とサードパーティのセッション同じ履歴リストに表示されます既存の公式セッション移行も選択できます(移行前に自動バックアップ)。オフにする際はバックアップから復元できます。注意:プロバイダーをまたいで古いセッションを再開すると、encrypted_content の推論内容を相手のバックエンドが復号できず、再開に失敗する場合があります。",
"unifyCodexHistoryRestoreCompleted": "バックアップから公式セッション履歴を復元しました(セッションファイル {{files}} 件、インデックス {{rows}} 行)",
"unifyCodexHistoryRestoreFailed": "公式セッション履歴の復元に失敗しました。もう一度お試しください",
"unifyCodexHistoryRestoreNothing": "現在の Codex ディレクトリに復元可能な移行バックアップはありません",
"unifyCodexHistoryRestoreSkippedToggleOn": "統一セッション履歴が再度有効化されたため、復元をスキップしました",
"appVisibility": {
"title": "ホームページ表示",
"description": "ホームページに表示するアプリを選択",
+17 -1
View File
@@ -279,6 +279,18 @@
"message": "故障轉移是一項進階功能,啟用前請確保您已了解其運作原理。\n\n建議先在故障轉移佇列中設定好供應商優先順序。",
"confirm": "我已了解,繼續啟用"
},
"unifyCodexHistory": {
"title": "統一 Codex 會話歷史",
"message": "開啟後,官方訂閱與第三方將共用同一個會話歷史清單。注意:跨供應商繼續舊會話時,可能因對方後端無法解密 encrypted_content 推理內容而失敗。\n\n可選擇同時把現有官方會話歷史遷入共享清單(遷移前自動備份到 ~/.cc-switch/backups,關閉開關時可選擇恢復)。",
"migrateExisting": "同時遷入現有官方會話歷史",
"confirm": "我已了解,繼續開啟"
},
"unifyCodexHistoryOff": {
"title": "關閉統一會話歷史",
"message": "關閉後,官方訂閱與第三方將恢復各自獨立的會話歷史清單。開啟期間產生的會話因無法區分來源,將留在第三方歷史中,官方訂閱將看不到它們。",
"restoreBackup": "把開啟時遷入的官方會話還原回官方歷史(按備份精確還原)",
"confirm": "關閉"
},
"usage": {
"title": "設定用量查詢",
"message": "用量查詢需要設定專用的查詢腳本或 API 參數,請確保您已從供應商處取得相關資訊。\n\n如不確定如何設定,請先查閱供應商文件。",
@@ -671,7 +683,11 @@
"preserveCodexOfficialAuthOnSwitch": "切換第三方時保留官方登入",
"preserveCodexOfficialAuthOnSwitchDescription": "開啟後可以在使用第三方 API 的時候使用 Codex 應用的官方外掛、手機遠端操作等功能",
"unifyCodexSessionHistory": "統一 Codex 會話歷史",
"unifyCodexSessionHistoryDescription": "開啟後,官方訂閱將以共享的 custom 供應商標識執行,未來的官方會話與第三方會話出現在同一歷史清單中(不遷移既有會話。注意:跨供應商繼續舊會話時,對方後端可能無法解密會話中的 encrypted_content 推理內容,導致繼續失敗",
"unifyCodexSessionHistoryDescription": "開啟後,官方訂閱將以共享的 custom 供應商標識執行,官方與第三方會話出現在同一歷史清單中,並可選擇把現有官方會話一併遷入(遷移前自動備份)。關閉開關時可按備份恢復遷入的會話。注意:跨供應商繼續舊會話時,對方後端可能無法解密會話中的 encrypted_content 推理內容,導致繼續失敗",
"unifyCodexHistoryRestoreCompleted": "已按備份還原官方會話歷史({{files}} 個會話檔案、{{rows}} 條索引記錄)",
"unifyCodexHistoryRestoreFailed": "還原官方會話歷史失敗,請重試",
"unifyCodexHistoryRestoreNothing": "目前 Codex 目錄沒有可恢復的遷移備份",
"unifyCodexHistoryRestoreSkippedToggleOn": "統一會話歷史開關已重新開啟,已跳過還原",
"appVisibility": {
"title": "主頁面顯示",
"description": "選擇在主頁面顯示的應用程式",
+17 -1
View File
@@ -279,6 +279,18 @@
"message": "故障转移是一项高级功能,启用前请确保您已了解其工作原理。\n\n建议先在故障转移队列中配置好供应商优先级。",
"confirm": "我已了解,继续启用"
},
"unifyCodexHistory": {
"title": "统一 Codex 会话历史",
"message": "开启后,官方订阅与第三方将共用同一个会话历史列表。注意:跨供应商继续旧会话时,可能因对方后端无法解密 encrypted_content 推理内容而失败。\n\n可选择同时把现有官方会话历史迁入共享列表(迁移前自动备份到 ~/.cc-switch/backups,关闭开关时可选择恢复)。",
"migrateExisting": "同时迁入现有官方会话历史",
"confirm": "我已了解,继续开启"
},
"unifyCodexHistoryOff": {
"title": "关闭统一会话历史",
"message": "关闭后,官方订阅与第三方将恢复各自独立的会话历史列表。开启期间产生的会话因无法区分来源,将留在第三方历史中,官方订阅将看不到它们。",
"restoreBackup": "把开启时迁入的官方会话还原回官方历史(按备份精确还原)",
"confirm": "关闭"
},
"usage": {
"title": "配置用量查询",
"message": "用量查询需要配置专用的查询脚本或 API 参数,请确保您已从供应商处获取相关信息。\n\n如不确定如何配置,请先查阅供应商文档。",
@@ -671,7 +683,11 @@
"preserveCodexOfficialAuthOnSwitch": "切换第三方时保留官方登录",
"preserveCodexOfficialAuthOnSwitchDescription": "开启后可以在使用第三方 API 的时候使用 Codex 应用的官方插件、手机远程操作等功能",
"unifyCodexSessionHistory": "统一 Codex 会话历史",
"unifyCodexSessionHistoryDescription": "开启后,官方订阅将以共享的 custom 供应商标识运行,未来的官方会话与第三方会话出现在同一历史列表中(不迁移既有会话。注意:跨供应商继续旧会话时,对方后端可能无法解密会话中的 encrypted_content 推理内容,导致继续失败",
"unifyCodexSessionHistoryDescription": "开启后,官方订阅将以共享的 custom 供应商标识运行,官方与第三方会话出现在同一历史列表中,并可选择把现有官方会话一并迁入(迁移前自动备份)。关闭开关时可按备份恢复迁入的会话。注意:跨供应商继续旧会话时,对方后端可能无法解密会话中的 encrypted_content 推理内容,导致继续失败",
"unifyCodexHistoryRestoreCompleted": "已按备份还原官方会话历史({{files}} 个会话文件、{{rows}} 条索引记录)",
"unifyCodexHistoryRestoreFailed": "还原官方会话历史失败,请重试",
"unifyCodexHistoryRestoreNothing": "当前 Codex 目录没有可恢复的迁移备份",
"unifyCodexHistoryRestoreSkippedToggleOn": "统一会话历史开关已重新开启,已跳过还原",
"appVisibility": {
"title": "主页面显示",
"description": "选择在主页面显示的应用",