Skip to content

Commit b12c2c6

Browse files
author
李杰
committed
release: v0.22.0
1 parent dcdeda2 commit b12c2c6

44 files changed

Lines changed: 8003 additions & 111 deletions

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: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,23 @@ 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.22.0] - 2026-04-18
11+
12+
### Added
13+
- **Merged upstream workspace/CLI changes from PR #490 (`dcdeda2`, based on `ca5aade`)**: the repository is migrated to a Cargo Workspace, introduces `cockpit-core` as shared Rust logic, and initializes `cockpit-cli` with the first account list/switch flow for Cursor and Gemini.
14+
- **Codex now includes full local API Service management on the account pages**: an inline service card plus a dedicated panel can manage collection members, API key visibility/reset, service port, direct activate/test actions, and account collection edits in one workflow.
15+
- **API Service now records and displays usage metrics for totals and per-account views**: request counts, token usage (input/output/cache/reasoning), average latency, and success rate are all visible in the service panel.
16+
17+
### Changed
18+
- **Integrated Codex account/export adjustments from `5a2d970`**: Codex import now preserves `auth_file_plan_type` (`prolite`/`promax`) from file metadata and uses it in plan badges (`PRO 5x` / `PRO 20x`); `sub2api` export payload now includes `exported_at`, `type/version`, `proxies`, and per-account `concurrency/priority`.
19+
- **Codex instance binding now supports a dedicated API Service target (`__api_service__`)**: account pickers, instance search, and Codex instance labels now recognize and display API Service mode consistently.
20+
- **Starting a Codex instance in API Service mode now applies the switch on the real profile directory**: startup uses the same persisted on-disk path as normal instance switching, and triggers history-visibility repair when the effective provider changes.
21+
- **Activating API Service from Codex accounts now syncs default runtime pointers**: Cockpit clears the default current-account pointer and updates the default Codex instance binding to API Service mode.
22+
- **Cockpit startup now auto-restores saved local API gateway runtime state**: previously enabled API Service settings are resumed without manual reactivation.
23+
- **Codex quota error messaging for network failures is now normalized**: manual-refresh hints no longer expose raw backend error details.
24+
- **New API Service locale keys are now fully synchronized across all supported locales**: `zh-CN`, `en-US`, `en`, and the remaining non-English locale packs all ship with matching keys.
25+
926
---
1027
## [0.21.4] - 2026-04-16
1128

CHANGELOG.zh-CN.md

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

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

9+
---
10+
## [0.22.0] - 2026-04-18
11+
12+
### 新增
13+
- **已合入上游工作区/CLI 改造(PR #490`dcdeda2`,基于 `ca5aade`**:仓库已迁移为 Cargo Workspace,新增 `cockpit-core` 共享 Rust 逻辑层,并初始化 `cockpit-cli`(首版支持 Cursor / Gemini 账号列出与切换)。
14+
- **Codex 账号页现已补齐本地 API 服务全量管理能力**:提供内联服务卡片与独立服务面板,可在同一流程完成集合成员维护、密钥显示/重置、服务端口设置、直接启动/测试,以及成员集合编辑。
15+
- **API 服务现已记录并展示总量与按账号统计**:服务面板可直接查看请求数、Token 使用(输入/输出/缓存/思考)、平均延迟与成功率。
16+
17+
### 变更
18+
- **已并入 `5a2d970` 的 Codex 调整**:导入时会保留 `auth_file_plan_type``prolite`/`promax`)并用于套餐徽标展示(`PRO 5x` / `PRO 20x`);`sub2api` 导出结构新增 `exported_at``type/version``proxies` 及账号级 `concurrency/priority` 字段。
19+
- **Codex 实例绑定现已支持独立 API 服务目标(`__api_service__`**:账号选择器、实例搜索与实例列表标签现已统一识别并展示 API 服务模式。
20+
- **API 服务模式下启动 Codex 实例现已写入真实实例目录配置**:启动链路与普通实例切换一致走真实落盘路径,并在 provider 实际变化时自动触发历史可见性修复。
21+
- **从 Codex 账号页激活 API 服务时,默认运行态指针现已同步**:会清空默认 current account 指针,并把默认 Codex 实例绑定更新为 API 服务模式。
22+
- **Cockpit 启动时现会自动恢复已保存的本地 API 网关运行态**:已启用的 API 服务配置无需每次手动重新激活。
23+
- **Codex 配额网络失败提示现已统一收敛**:手动刷新提示不再直接回显后端原始错误详情。
24+
- **API 服务新增文案现已覆盖全部支持语言并保持 key 对齐**`zh-CN``en-US``en` 以及其余非英语语言包均已同步补齐。
25+
926
---
1027
## [0.21.4] - 2026-04-16
1128

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.

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.4",
4+
"version": "0.22.0",
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.4"
3+
version = "0.22.0"
44
description = "Cockpit Tools"
55
authors = ["jlcodes"]
66
license = "CC-BY-NC-SA-4.0"

src-tauri/src/commands/codex.rs

Lines changed: 153 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
use crate::models::codex::{
22
CodexAccount, CodexApiProviderMode, CodexQuickConfig, CodexQuota, CodexTokens,
33
};
4+
use crate::models::codex_local_access::{
5+
CodexLocalAccessRoutingStrategy, CodexLocalAccessState,
6+
};
47
use crate::modules::{
5-
codex_account, codex_oauth, codex_quota, codex_wakeup, codex_wakeup_scheduler, config, logger,
6-
openclaw_auth, opencode_auth, process,
8+
codex_account, codex_local_access, codex_oauth, codex_quota, codex_wakeup,
9+
codex_wakeup_scheduler, config, logger, openclaw_auth, opencode_auth, process,
710
};
811
use std::sync::atomic::{AtomicBool, Ordering};
912
use tauri::AppHandle;
@@ -672,3 +675,151 @@ pub async fn save_codex_model_providers(data: String) -> Result<(), String> {
672675
let path = dir.join(CODEX_MODEL_PROVIDERS_FILE);
673676
std::fs::write(&path, data).map_err(|e| format!("Failed to write codex model providers: {}", e))
674677
}
678+
679+
#[tauri::command]
680+
pub async fn codex_local_access_get_state() -> Result<CodexLocalAccessState, String> {
681+
codex_local_access::get_local_access_state().await
682+
}
683+
684+
#[tauri::command]
685+
pub async fn codex_local_access_save_accounts(
686+
account_ids: Vec<String>,
687+
) -> Result<CodexLocalAccessState, String> {
688+
codex_local_access::save_local_access_accounts(account_ids).await
689+
}
690+
691+
#[tauri::command]
692+
pub async fn codex_local_access_remove_account(
693+
account_id: String,
694+
) -> Result<CodexLocalAccessState, String> {
695+
codex_local_access::remove_local_access_account(&account_id).await
696+
}
697+
698+
#[tauri::command]
699+
pub async fn codex_local_access_rotate_api_key() -> Result<CodexLocalAccessState, String> {
700+
codex_local_access::rotate_local_access_api_key().await
701+
}
702+
703+
#[tauri::command]
704+
pub async fn codex_local_access_clear_stats() -> Result<CodexLocalAccessState, String> {
705+
codex_local_access::clear_local_access_stats().await
706+
}
707+
708+
#[tauri::command]
709+
pub async fn codex_local_access_update_port(
710+
port: u16,
711+
) -> Result<CodexLocalAccessState, String> {
712+
codex_local_access::update_local_access_port(port).await
713+
}
714+
715+
#[tauri::command]
716+
pub async fn codex_local_access_update_routing_strategy(
717+
strategy: CodexLocalAccessRoutingStrategy,
718+
) -> Result<CodexLocalAccessState, String> {
719+
codex_local_access::update_local_access_routing_strategy(strategy).await
720+
}
721+
722+
#[tauri::command]
723+
pub async fn codex_local_access_set_enabled(
724+
enabled: bool,
725+
) -> Result<CodexLocalAccessState, String> {
726+
codex_local_access::set_local_access_enabled(enabled).await
727+
}
728+
729+
#[tauri::command]
730+
pub async fn codex_local_access_activate(app: AppHandle) -> Result<CodexLocalAccessState, String> {
731+
let codex_home = codex_account::get_codex_home();
732+
let provider_before =
733+
crate::modules::codex_session_visibility::read_history_visibility_provider_for_dir(
734+
&codex_home,
735+
)
736+
.map(Some)
737+
.unwrap_or_else(|error| {
738+
logger::log_warn(&format!(
739+
"切换 API 服务前读取 Codex provider 失败,跳过自动修复预判: {}",
740+
error
741+
));
742+
None
743+
});
744+
let state = codex_local_access::activate_local_access_for_dir(&codex_home).await?;
745+
746+
let mut index = codex_account::load_account_index();
747+
index.current_account_id = None;
748+
codex_account::save_account_index(&index)?;
749+
750+
if let Err(e) = crate::modules::codex_instance::update_default_settings(
751+
Some(Some(
752+
crate::modules::codex_instance::CODEX_API_SERVICE_BIND_ACCOUNT_ID.to_string(),
753+
)),
754+
None,
755+
Some(false),
756+
None,
757+
) {
758+
logger::log_warn(&format!("更新 Codex 默认实例为 API 服务模式失败: {}", e));
759+
} else {
760+
logger::log_info("已同步更新 Codex 默认实例为 API 服务模式");
761+
}
762+
763+
let provider_after =
764+
crate::modules::codex_session_visibility::read_history_visibility_provider_for_dir(
765+
&codex_home,
766+
)
767+
.map(Some)
768+
.unwrap_or_else(|error| {
769+
logger::log_warn(&format!(
770+
"切换 API 服务后读取 Codex provider 失败,跳过自动修复可见性: {}",
771+
error
772+
));
773+
None
774+
});
775+
let should_repair_visibility = match (provider_before.as_deref(), provider_after.as_deref()) {
776+
(Some(before), Some(after)) => before != after,
777+
(None, Some(_)) => true,
778+
_ => false,
779+
};
780+
if should_repair_visibility {
781+
match crate::modules::codex_session_visibility::repair_session_visibility_across_instances()
782+
{
783+
Ok(summary) => {
784+
logger::log_info(&format!(
785+
"切换 API 服务后已自动执行历史会话可见性修复: {}",
786+
summary.message
787+
));
788+
}
789+
Err(error) => {
790+
logger::log_warn(&format!(
791+
"API 服务切换成功,但自动修复历史会话可见性失败,请稍后在会话管理中手动补跑: {}",
792+
error
793+
));
794+
}
795+
}
796+
}
797+
798+
let user_config = config::get_user_config();
799+
logger::log_info("API 服务启动模式下跳过 OpenCode / OpenClaw OAuth 同步");
800+
801+
if user_config.codex_launch_on_switch {
802+
#[cfg(target_os = "macos")]
803+
if process::is_codex_running() {
804+
logger::log_info("检测到 Codex 正在运行,将按默认实例 PID 逻辑重启");
805+
}
806+
match crate::commands::codex_instance::codex_start_instance("__default__".to_string()).await
807+
{
808+
Ok(_) => {}
809+
Err(e) => {
810+
logger::log_warn(&format!("Codex 启动失败: {}", e));
811+
if e.starts_with("APP_PATH_NOT_FOUND:") {
812+
let _ = app.emit(
813+
"app:path_missing",
814+
serde_json::json!({ "app": "codex", "retry": { "kind": "default" } }),
815+
);
816+
}
817+
}
818+
}
819+
} else {
820+
logger::log_info("已关闭切换 Codex 时自动启动 Codex App");
821+
}
822+
823+
let _ = crate::modules::tray::update_tray_menu(&app);
824+
Ok(state)
825+
}

src-tauri/src/commands/codex_instance.rs

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,63 @@ fn resolve_local_account_id() -> Option<String> {
8080
Some(account.id)
8181
}
8282

83+
async fn inject_bound_account_to_profile(
84+
profile_dir: &Path,
85+
bind_account_id: &str,
86+
) -> Result<(), String> {
87+
if modules::codex_instance::is_api_service_bind_account_id(bind_account_id) {
88+
let provider_before =
89+
modules::codex_session_visibility::read_history_visibility_provider_for_dir(profile_dir)
90+
.map(Some)
91+
.unwrap_or_else(|error| {
92+
modules::logger::log_warn(&format!(
93+
"实例切换 API 服务前读取 provider 失败,跳过自动修复预判: {}",
94+
error
95+
));
96+
None
97+
});
98+
99+
modules::codex_local_access::activate_local_access_for_dir(profile_dir).await?;
100+
101+
let provider_after =
102+
modules::codex_session_visibility::read_history_visibility_provider_for_dir(profile_dir)
103+
.map(Some)
104+
.unwrap_or_else(|error| {
105+
modules::logger::log_warn(&format!(
106+
"实例切换 API 服务后读取 provider 失败,跳过自动修复可见性: {}",
107+
error
108+
));
109+
None
110+
});
111+
let should_repair_visibility =
112+
match (provider_before.as_deref(), provider_after.as_deref()) {
113+
(Some(before), Some(after)) => before != after,
114+
(None, Some(_)) => true,
115+
_ => false,
116+
};
117+
if should_repair_visibility {
118+
match modules::codex_session_visibility::repair_session_visibility_across_instances() {
119+
Ok(summary) => {
120+
modules::logger::log_info(&format!(
121+
"实例切换 API 服务后已自动执行历史会话可见性修复: {}",
122+
summary.message
123+
));
124+
}
125+
Err(error) => {
126+
modules::logger::log_warn(&format!(
127+
"实例 API 服务切换成功,但自动修复历史会话可见性失败,请稍后在会话管理中手动补跑: {}",
128+
error
129+
));
130+
}
131+
}
132+
}
133+
134+
return Ok(());
135+
}
136+
137+
modules::codex_instance::inject_account_to_profile(profile_dir, bind_account_id).await
138+
}
139+
83140
fn default_instance_view(
84141
default_dir: &Path,
85142
default_settings: &DefaultInstanceSettings,
@@ -406,7 +463,7 @@ pub async fn codex_start_instance(instance_id: String) -> Result<CodexInstancePr
406463
let _ = modules::codex_instance::update_default_pid(None)?;
407464
}
408465
if let Some(ref account_id) = default_bind_account_id {
409-
modules::codex_instance::inject_account_to_profile(&default_dir, account_id).await?;
466+
inject_bound_account_to_profile(&default_dir, account_id).await?;
410467
}
411468

412469
if default_settings.launch_mode == InstanceLaunchMode::Cli {
@@ -453,11 +510,7 @@ pub async fn codex_start_instance(instance_id: String) -> Result<CodexInstancePr
453510
}
454511

455512
if let Some(ref account_id) = instance.bind_account_id {
456-
modules::codex_instance::inject_account_to_profile(
457-
Path::new(&instance.user_data_dir),
458-
account_id,
459-
)
460-
.await?;
513+
inject_bound_account_to_profile(Path::new(&instance.user_data_dir), account_id).await?;
461514
}
462515

463516
if instance.launch_mode == InstanceLaunchMode::Cli {

src-tauri/src/lib.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,10 @@ pub fn run() {
130130
modules::web_report::start_server().await;
131131
});
132132

133+
tauri::async_runtime::spawn(async {
134+
modules::codex_local_access::restore_local_access_gateway().await;
135+
});
136+
133137
{
134138
let app_handle = app.handle().clone();
135139
tauri::async_runtime::spawn(async move {
@@ -392,6 +396,15 @@ pub fn run() {
392396
commands::codex::save_codex_account_groups,
393397
commands::codex::load_codex_model_providers,
394398
commands::codex::save_codex_model_providers,
399+
commands::codex::codex_local_access_get_state,
400+
commands::codex::codex_local_access_save_accounts,
401+
commands::codex::codex_local_access_remove_account,
402+
commands::codex::codex_local_access_rotate_api_key,
403+
commands::codex::codex_local_access_clear_stats,
404+
commands::codex::codex_local_access_update_port,
405+
commands::codex::codex_local_access_update_routing_strategy,
406+
commands::codex::codex_local_access_set_enabled,
407+
commands::codex::codex_local_access_activate,
395408
// GitHub Copilot Commands
396409
commands::github_copilot::list_github_copilot_accounts,
397410
commands::github_copilot::delete_github_copilot_account,

0 commit comments

Comments
 (0)