Skip to content

Commit 498b3d4

Browse files
author
李杰
committed
release: v0.21.2
1 parent 27b4e8f commit 498b3d4

123 files changed

Lines changed: 11985 additions & 2516 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,29 @@ All notable changes to Cockpit Tools will be documented in this file.
66

77
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
88

9+
---
10+
## [0.21.2] - 2026-04-13
11+
12+
### Added
13+
- **Settings now support data backup/import bundles for accounts and app config**: export or import accounts only, config only, or both together; config restore covers groups, instances, wakeup tasks, current-account refresh settings, and Codex model-provider data, while legacy account-only backups remain importable and report when some bindings need remapping or a restart is required.
14+
- **Settings now include a Backup Manager for scheduled local backups**: Cockpit can create one managed backup per day, keep backups in the app data `backups` directory with configurable retention, and let users run a backup immediately or import/delete existing backup files from the same dialog.
15+
- **Codex Session Manager now supports restoring trashed sessions back to their original instances**: restored sessions recover the rollout file, the `session_index.jsonl` entry, and the `state_5.sqlite` thread row together instead of requiring manual file repair.
16+
17+
### Changed
18+
- **Provider account pages now share a unified pagination and filter experience**: page size is configurable per platform, selection and grouping stay consistent across table and grid views, and tag/sort dropdowns auto-flip to remain usable in small windows.
19+
- **Gemini account tables now surface Pro / Flash quota status directly in list view**: quota summaries are visible without switching back to card layout, making remaining capacity easier to scan.
20+
- **Instance account pickers now open in anchored floating menus that stay visible near window edges**: long account lists keep the active option in view, and Trae instance search now also matches display names in addition to email and plan text.
21+
- **Codex account switching now auto-repairs historical session visibility only when the effective provider changes**: after a successful switch, Cockpit compares the provider before and after the change and only then repairs rollout/session metadata together with `state_5.sqlite`.
22+
23+
### Removed
24+
- **Windsurf account onboarding no longer includes email/password login**: the add-account dialog now focuses on OAuth, token, and local JSON import flows.
25+
26+
### Fixed
27+
- **Quota refresh failures now surface explicit warning and empty states across provider account pages**: Cursor, Gemini, GitHub Copilot, Kiro, Qoder, Trae, Windsurf, Zed, and the aggregated Accounts page now persist the last quota-query error, show a visible failure badge/message, and fall back to a clear `No quota data` state instead of silently rendering blank or ambiguous quota panels.
28+
- **Codex account state now stays aligned with the live local OAuth session more reliably**: current-account detection, switch preparation, quota refresh, and wakeup runs reuse newer local auth data and write refreshed tokens back to managed homes, reducing stale-token mismatches.
29+
- **Background auto refresh now runs through a unified scheduler**: quota refresh and current-account refresh jobs across providers are less likely to overlap or double-trigger, improving refresh stability.
30+
- **Trae token refresh now preserves regional auth context when rewriting local auth state**: refreshed sessions keep the host, region, and refresh-expiry metadata needed for follow-up injection flows.
31+
932
---
1033
## [0.21.1] - 2026-04-11
1134

CHANGELOG.zh-CN.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,29 @@
66

77
格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/)
88

9+
---
10+
## [0.21.2] - 2026-04-13
11+
12+
### 新增
13+
- **设置页现已支持账号与应用配置的数据备份/导入包**:可分别导出或导入“仅账号”“仅配置”或“两者一起”;配置恢复覆盖分组、实例、唤醒任务、当前账号刷新设置与 Codex 模型供应商数据,旧版仅账号备份也仍可导入,并会提示未能重映射的绑定或需重启的配置项。
14+
- **设置页现已新增“备份管理”能力,用于定期本地备份**:Cockpit 现可每天自动生成一份受管备份,统一保存在应用数据目录的 `backups` 文件夹中,支持配置保留天数,并可在同一弹框内立即备份、导入已有备份或删除旧备份文件。
15+
- **Codex 会话管理现已支持把废纸篓中的会话恢复回原实例**:恢复时会一并放回 rollout 文件、`session_index.jsonl` 条目和 `state_5.sqlite` 线程记录,不再需要手工补文件。
16+
17+
### 变更
18+
- **各平台账号页现已统一为同一套分页与筛选体验**:每个平台都可单独记住每页数量,表格/卡片视图下的选择与分组行为保持一致,标签和排序下拉在小窗口里也会自动翻转方向避免挡住内容。
19+
- **Gemini 账号表格现已直接显示 Pro / Flash 配额状态**:无需切回卡片视图即可查看配额概览,剩余额度更容易快速扫读。
20+
- **实例页账号选择器现已改为锚定浮层,并在靠近窗口边缘时自动调整展开方向**:长列表会自动滚动到当前选中项,Trae 实例搜索也新增支持按显示名匹配。
21+
- **Codex 切号现已仅在实际 provider 发生变化后才自动修复历史会话可见性**:切号成功后会比较切换前后的 provider,仅在发生变化时才联动修复 rollout/session 元数据与 `state_5.sqlite`
22+
23+
### 移除
24+
- **Windsurf 账号接入现已不再提供邮箱密码登录**:添加账号弹框现在仅保留 OAuth、Token 和本地 JSON 导入三条路径。
25+
26+
### 修复
27+
- **各平台账号页现已明确展示配额查询失败与无数据状态**:Cursor、Gemini、GitHub Copilot、Kiro、Qoder、Trae、Windsurf、Zed 以及聚合账号总览页现在都会持久化最近一次配额查询错误,界面会显示明确的失败提示,并在无有效配额数据时回退到清晰的“暂无配额数据”状态,不再静默渲染空白或含糊的配额区块。
28+
- **Codex 账号状态现已更稳定地与本地 OAuth 登录态保持一致**:当前账号识别、切号准备、配额刷新和唤醒执行都会优先复用更新的本地登录数据,并把刷新后的 token 回写到受管目录,减少旧 token 残留导致的状态不一致。
29+
- **后台自动刷新现已改为统一调度**:各平台的配额刷新与当前账号刷新更不容易互相重叠或重复触发,整体刷新稳定性更好。
30+
- **Trae 刷新 token 时现会保留区域登录上下文并回写到本地认证状态**:后续注入与续期所需的 host、region 和 refresh 过期信息会一并保留。
31+
932
---
1033
## [0.21.1] - 2026-04-11
1134

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "cockpit-tools",
33
"private": true,
4-
"version": "0.21.1",
4+
"version": "0.21.2",
55
"type": "module",
66
"scripts": {
77
"dev": "vite",

src-tauri/Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src-tauri/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "cockpit-tools"
3-
version = "0.21.1"
3+
version = "0.21.2"
44
description = "Cockpit Tools"
55
authors = ["jlcodes"]
66
license = "CC-BY-NC-SA-4.0"

src-tauri/src/commands/codex.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,19 @@ pub async fn switch_codex_account(
5555
account_id: String,
5656
) -> Result<CodexAccount, String> {
5757
let _ = codex_account::prepare_account_for_injection(&account_id).await?;
58+
let codex_home = codex_account::get_codex_home();
59+
let provider_before =
60+
crate::modules::codex_session_visibility::read_history_visibility_provider_for_dir(
61+
&codex_home,
62+
)
63+
.map(Some)
64+
.unwrap_or_else(|error| {
65+
logger::log_warn(&format!(
66+
"切号前读取 Codex provider 失败,跳过自动修复预判: {}",
67+
error
68+
));
69+
None
70+
});
5871

5972
// 切换账号(写入 auth.json)
6073
let account = codex_account::switch_account(&account_id)?;
@@ -74,6 +87,41 @@ pub async fn switch_codex_account(
7487
));
7588
}
7689

90+
let provider_after =
91+
crate::modules::codex_session_visibility::read_history_visibility_provider_for_dir(
92+
&codex_home,
93+
)
94+
.map(Some)
95+
.unwrap_or_else(|error| {
96+
logger::log_warn(&format!(
97+
"切号后读取 Codex provider 失败,跳过自动修复可见性: {}",
98+
error
99+
));
100+
None
101+
});
102+
let should_repair_visibility = match (provider_before.as_deref(), provider_after.as_deref()) {
103+
(Some(before), Some(after)) => before != after,
104+
(None, Some(_)) => true,
105+
_ => false,
106+
};
107+
if should_repair_visibility {
108+
match crate::modules::codex_session_visibility::repair_session_visibility_across_instances()
109+
{
110+
Ok(summary) => {
111+
logger::log_info(&format!(
112+
"Codex 切号后已自动执行历史会话可见性修复: {}",
113+
summary.message
114+
));
115+
}
116+
Err(error) => {
117+
logger::log_warn(&format!(
118+
"Codex 切号成功,但自动修复历史会话可见性失败,请稍后在会话管理中手动补跑: {}",
119+
error
120+
));
121+
}
122+
}
123+
}
124+
77125
let user_config = config::get_user_config();
78126
let mut opencode_updated = false;
79127
if user_config.opencode_auth_overwrite_on_switch {

src-tauri/src/commands/codex_instance.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,19 @@ pub async fn codex_move_sessions_to_trash_across_instances(
272272
modules::codex_session_manager::move_sessions_to_trash_across_instances(session_ids)
273273
}
274274

275+
#[tauri::command]
276+
pub async fn codex_list_trashed_sessions_across_instances(
277+
) -> Result<Vec<modules::codex_session_manager::CodexTrashedSessionRecord>, String> {
278+
modules::codex_session_manager::list_trashed_sessions_across_instances()
279+
}
280+
281+
#[tauri::command]
282+
pub async fn codex_restore_sessions_from_trash_across_instances(
283+
session_ids: Vec<String>,
284+
) -> Result<modules::codex_session_manager::CodexSessionRestoreSummary, String> {
285+
modules::codex_session_manager::restore_sessions_from_trash_across_instances(session_ids)
286+
}
287+
275288
#[tauri::command]
276289
pub async fn codex_create_instance(
277290
name: String,
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
use tauri_plugin_autostart::ManagerExt as _;
2+
3+
use crate::models::InstanceStore;
4+
use crate::modules;
5+
use crate::modules::config::{self, UserConfig};
6+
use crate::modules::websocket;
7+
8+
fn get_app_auto_launch_enabled(app: &tauri::AppHandle) -> Result<bool, String> {
9+
app.autolaunch()
10+
.is_enabled()
11+
.map_err(|err| format!("读取应用自启动状态失败: {}", err))
12+
}
13+
14+
fn apply_app_auto_launch_enabled(app: &tauri::AppHandle, enabled: bool) -> Result<(), String> {
15+
if enabled {
16+
app.autolaunch()
17+
.enable()
18+
.map_err(|err| format!("启用应用自启动失败: {}", err))
19+
} else {
20+
app.autolaunch()
21+
.disable()
22+
.map_err(|err| format!("停用应用自启动失败: {}", err))
23+
}
24+
}
25+
26+
fn load_instance_store_by_platform(platform: &str) -> Result<InstanceStore, String> {
27+
match platform {
28+
"antigravity" => modules::instance::load_instance_store(),
29+
"codex" => modules::codex_instance::load_instance_store(),
30+
"github-copilot" => modules::github_copilot_instance::load_instance_store(),
31+
"windsurf" => modules::windsurf_instance::load_instance_store(),
32+
"kiro" => modules::kiro_instance::load_instance_store(),
33+
"cursor" => modules::cursor_instance::load_instance_store(),
34+
"gemini" => modules::gemini_instance::load_instance_store(),
35+
"codebuddy" => modules::codebuddy_instance::load_instance_store(),
36+
"codebuddy_cn" => modules::codebuddy_cn_instance::load_instance_store(),
37+
"qoder" => modules::qoder_instance::load_instance_store(),
38+
"trae" => modules::trae_instance::load_instance_store(),
39+
"workbuddy" => modules::workbuddy_instance::load_instance_store(),
40+
_ => Err("不支持的实例平台".to_string()),
41+
}
42+
}
43+
44+
fn save_instance_store_by_platform(platform: &str, store: &InstanceStore) -> Result<(), String> {
45+
match platform {
46+
"antigravity" => modules::instance::save_instance_store(store),
47+
"codex" => modules::codex_instance::save_instance_store(store),
48+
"github-copilot" => modules::github_copilot_instance::save_instance_store(store),
49+
"windsurf" => modules::windsurf_instance::save_instance_store(store),
50+
"kiro" => modules::kiro_instance::save_instance_store(store),
51+
"cursor" => modules::cursor_instance::save_instance_store(store),
52+
"gemini" => modules::gemini_instance::save_instance_store(store),
53+
"codebuddy" => modules::codebuddy_instance::save_instance_store(store),
54+
"codebuddy_cn" => modules::codebuddy_cn_instance::save_instance_store(store),
55+
"qoder" => modules::qoder_instance::save_instance_store(store),
56+
"trae" => modules::trae_instance::save_instance_store(store),
57+
"workbuddy" => modules::workbuddy_instance::save_instance_store(store),
58+
_ => Err("不支持的实例平台".to_string()),
59+
}
60+
}
61+
62+
fn sanitize_instance_store(store: &InstanceStore) -> InstanceStore {
63+
let mut next = store.clone();
64+
next.default_settings.last_pid = None;
65+
for instance in &mut next.instances {
66+
instance.last_pid = None;
67+
instance.last_launched_at = None;
68+
}
69+
next
70+
}
71+
72+
#[tauri::command]
73+
pub fn data_transfer_get_user_config() -> Result<UserConfig, String> {
74+
Ok(config::get_user_config())
75+
}
76+
77+
#[tauri::command]
78+
pub fn data_transfer_apply_user_config(
79+
app: tauri::AppHandle,
80+
config: UserConfig,
81+
) -> Result<bool, String> {
82+
let current = config::get_user_config();
83+
let current_app_auto_launch_enabled =
84+
get_app_auto_launch_enabled(&app).unwrap_or(current.app_auto_launch_enabled);
85+
86+
let needs_restart = current.ws_port != config.ws_port
87+
|| current.ws_enabled != config.ws_enabled
88+
|| current.report_enabled != config.report_enabled
89+
|| current.report_port != config.report_port
90+
|| current.report_token != config.report_token;
91+
let language_changed = current.language != config.language;
92+
let app_auto_launch_changed = current_app_auto_launch_enabled != config.app_auto_launch_enabled;
93+
94+
#[cfg(target_os = "macos")]
95+
let hide_dock_icon_changed = current.hide_dock_icon != config.hide_dock_icon;
96+
97+
config::save_user_config(&config)?;
98+
99+
if app_auto_launch_changed {
100+
apply_app_auto_launch_enabled(&app, config.app_auto_launch_enabled)?;
101+
}
102+
103+
if let Err(err) = modules::floating_card_window::apply_floating_card_always_on_top(&app) {
104+
modules::logger::log_warn(&format!("[DataTransfer] 应用悬浮卡片置顶状态失败: {}", err));
105+
}
106+
107+
#[cfg(target_os = "macos")]
108+
if hide_dock_icon_changed {
109+
crate::apply_macos_activation_policy(&app);
110+
}
111+
112+
if language_changed {
113+
let normalized_language = config.language.clone();
114+
websocket::broadcast_language_changed(&normalized_language, "desktop");
115+
modules::sync_settings::write_sync_setting("language", &normalized_language);
116+
if let Err(err) = modules::tray::update_tray_menu(&app) {
117+
modules::logger::log_warn(&format!("[DataTransfer] 语言变更后刷新托盘失败: {}", err));
118+
}
119+
}
120+
121+
Ok(needs_restart)
122+
}
123+
124+
#[tauri::command]
125+
pub fn data_transfer_get_instance_store(platform: String) -> Result<InstanceStore, String> {
126+
load_instance_store_by_platform(platform.trim())
127+
}
128+
129+
#[tauri::command]
130+
pub fn data_transfer_replace_instance_store(
131+
platform: String,
132+
store: InstanceStore,
133+
) -> Result<(), String> {
134+
let sanitized = sanitize_instance_store(&store);
135+
save_instance_store_by_platform(platform.trim(), &sanitized)
136+
}

src-tauri/src/commands/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ pub mod codex;
88
pub mod codex_instance;
99
pub mod cursor;
1010
pub mod cursor_instance;
11+
pub mod data_transfer;
1112
pub mod device;
1213
pub mod gemini;
1314
pub mod gemini_instance;

0 commit comments

Comments
 (0)