From d2f238f829e5b1138ef1de4ca224a76986db117a Mon Sep 17 00:00:00 2001 From: lzt404 <2596933790@qq.com> Date: Fri, 7 Aug 2026 23:04:18 +0800 Subject: [PATCH] refactor(agents): remove direct client launch functionality --- README.md | 4 +- README.zh-CN.md | 4 +- bun.lock | 3 - package.json | 1 - src-tauri/Cargo.lock | 144 +-- src-tauri/Cargo.toml | 1 - src-tauri/capabilities/default.json | 3 +- src-tauri/src/codex_sessions.rs | 230 +---- src-tauri/src/main.rs | 1135 +-------------------- src-tauri/src/usage.rs | 3 - src/i18n/ja.ts | 34 +- src/i18n/resources.ts | 68 +- src/pages/AgentsPage.tsx | 373 +------ src/pages/CodexSessionAutoRestoreCard.tsx | 104 -- src/services/codexSessionState.ts | 1 - src/styles.css | 239 +---- tests/i18n.test.ts | 14 - 17 files changed, 62 insertions(+), 2299 deletions(-) delete mode 100644 src/pages/CodexSessionAutoRestoreCard.tsx diff --git a/README.md b/README.md index 3a629fa..8a13148 100644 --- a/README.md +++ b/README.md @@ -99,8 +99,8 @@ proxy. Supported clients include: - Pi (with the CLIProxyAPI provider extension) For supported clients, the application can synchronize the available model catalog, select a -default model, back up the original configuration before applying managed settings, restore the -previous configuration, and launch an available desktop or CLI entry point. +default model, back up the original configuration before applying managed settings, and restore the +previous configuration. ## Additional Capabilities diff --git a/README.zh-CN.md b/README.zh-CN.md index 2551e4d..ff6c5cc 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -94,8 +94,8 @@ API 接入页面按照协议或 Provider 管理上游 API 凭证和服务地址 - Hermes Agent - Pi(通过 CLIProxyAPI provider 插件) -对于受支持的客户端,软件可以同步可用模型目录、选择默认模型、在应用托管配置前备份原始配置、 -恢复之前的配置,以及启动可用的桌面端或命令行入口。 +对于受支持的客户端,软件可以同步可用模型目录、选择默认模型、在应用托管配置前备份原始配置, +以及恢复之前的配置。 ## 其他功能 diff --git a/bun.lock b/bun.lock index 086e187..abf4563 100644 --- a/bun.lock +++ b/bun.lock @@ -6,7 +6,6 @@ "name": "cpa-gui", "dependencies": { "@tauri-apps/api": "^2.0.0", - "@tauri-apps/plugin-dialog": "^2.7.1", "lucide-react": "^1.24.0", "react": "^19.0.0", "react-dom": "^19.0.0", @@ -200,8 +199,6 @@ "@tauri-apps/cli-win32-x64-msvc": ["@tauri-apps/cli-win32-x64-msvc@2.11.4", "", { "os": "win32", "cpu": "x64" }, "sha512-+vDiqBIU5dMISg/wNvX3sF+ZHfgJGJ5T0AcO+EHNXV9GGAG+P5fzodlDXD3QdKCRgZxMoCm5PPvj3BqLNjBthw=="], - "@tauri-apps/plugin-dialog": ["@tauri-apps/plugin-dialog@2.7.2", "", { "dependencies": { "@tauri-apps/api": "^2.11.0" } }, "sha512-pX0IGm1I3I6wc+zeKYcq1GSqogK6okCNX5fOdaNU5ab1AjGS6l1E5wFNjEb7meg7ZFSp0JUs+0jQGQNyOvLrsg=="], - "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="], "@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="], diff --git a/package.json b/package.json index 6ca7758..9a905ed 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,6 @@ }, "dependencies": { "@tauri-apps/api": "^2.0.0", - "@tauri-apps/plugin-dialog": "^2.7.1", "lucide-react": "^1.24.0", "react": "^19.0.0", "react-dom": "^19.0.0" diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index b490e1e..8864a43 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -605,7 +605,6 @@ dependencies = [ "tar", "tauri", "tauri-build", - "tauri-plugin-dialog", "tauri-plugin-opener", "tokio", "tokio-util", @@ -2488,7 +2487,6 @@ checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ "bitflags 2.13.0", "block2", - "libc", "objc2", "objc2-core-foundation", ] @@ -3170,30 +3168,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "rfd" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a15ad77d9e70a92437d8f74c35d99b4e4691128df018833e99f90bcd36152672" -dependencies = [ - "block2", - "dispatch2", - "glib-sys", - "gobject-sys", - "gtk-sys", - "js-sys", - "log", - "objc2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation", - "raw-window-handle", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "windows-sys 0.60.2", -] - [[package]] name = "ring" version = "0.17.14" @@ -4030,48 +4004,6 @@ dependencies = [ "walkdir", ] -[[package]] -name = "tauri-plugin-dialog" -version = "2.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2d3c1dbe38037e7f590cdf2492594d5ceebe031e7bc7e827509b22a999d2940" -dependencies = [ - "log", - "raw-window-handle", - "rfd", - "serde", - "serde_json", - "tauri", - "tauri-plugin", - "tauri-plugin-fs", - "thiserror 2.0.18", - "url", -] - -[[package]] -name = "tauri-plugin-fs" -version = "2.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7ecc274121aca0c036a2b42d1cbe83d368d348f54e0bb8a735c2b1548e8f371" -dependencies = [ - "anyhow", - "dunce", - "glob", - "log", - "objc2-foundation", - "percent-encoding", - "schemars 0.8.22", - "serde", - "serde_json", - "serde_repr", - "tauri", - "tauri-plugin", - "tauri-utils", - "thiserror 2.0.18", - "toml 1.1.3+spec-1.1.0", - "url", -] - [[package]] name = "tauri-plugin-opener" version = "2.5.4" @@ -5219,15 +5151,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.5", -] - [[package]] name = "windows-sys" version = "0.61.2" @@ -5261,30 +5184,13 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", + "windows_i686_gnullvm", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link 0.2.1", - "windows_aarch64_gnullvm 0.53.1", - "windows_aarch64_msvc 0.53.1", - "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm 0.53.1", - "windows_i686_msvc 0.53.1", - "windows_x86_64_gnu 0.53.1", - "windows_x86_64_gnullvm 0.53.1", - "windows_x86_64_msvc 0.53.1", -] - [[package]] name = "windows-threading" version = "0.1.0" @@ -5315,12 +5221,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -5333,12 +5233,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -5351,24 +5245,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" -[[package]] -name = "windows_i686_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -5381,12 +5263,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -5399,12 +5275,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" @@ -5417,12 +5287,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -5435,12 +5299,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" - [[package]] name = "winnow" version = "0.5.40" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index f363e4f..7dfdb95 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -27,7 +27,6 @@ serde_yaml = "0.9" sha2 = "0.10" tar = "0.4" tauri = { version = "~2.11", features = ["tray-icon"] } -tauri-plugin-dialog = "2.7.1" tauri-plugin-opener = "2" tokio = { version = "1", features = ["macros", "sync", "time"] } tokio-util = "0.7" diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index e06f87d..0a29d28 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -8,7 +8,6 @@ "core:event:allow-unlisten", "core:window:allow-set-theme", "core:window:allow-set-background-color", - "core:webview:allow-set-webview-background-color", - "dialog:allow-open" + "core:webview:allow-set-webview-background-color" ] } diff --git a/src-tauri/src/codex_sessions.rs b/src-tauri/src/codex_sessions.rs index faf7e88..f7bfcb3 100644 --- a/src-tauri/src/codex_sessions.rs +++ b/src-tauri/src/codex_sessions.rs @@ -16,8 +16,6 @@ use std::sync::Mutex; use std::time::{Duration, SystemTime, UNIX_EPOCH}; use tauri::{Emitter, Manager}; -use crate::GuiConfigState; - const DEFAULT_PAGE_SIZE: usize = 50; const MAX_PAGE_SIZE: usize = 100; const SQLITE_BUSY_TIMEOUT: Duration = Duration::from_secs(5); @@ -76,7 +74,6 @@ pub(crate) struct CodexSessionPage { offset: usize, limit: usize, has_more: bool, - repair_on_launch: bool, warnings: Vec, } @@ -153,25 +150,6 @@ pub(crate) struct SessionIndexCleanupResult { backup_path: Option, } -#[derive(Debug, Clone, Serialize)] -#[serde(rename_all = "camelCase")] -pub(crate) struct ChatGptCloseResult { - was_running: bool, - closed_processes: usize, -} - -#[derive(Debug, Clone, Deserialize)] -#[serde(rename_all = "camelCase")] -pub(crate) struct SetRepairOnLaunchRequest { - enabled: bool, -} - -#[derive(Debug, Clone, Serialize)] -#[serde(rename_all = "camelCase")] -pub(crate) struct RepairOnLaunchSetting { - enabled: bool, -} - #[derive(Debug)] struct DatabaseDeletePlan { path: PathBuf, @@ -213,7 +191,6 @@ struct SessionIndexMetadata { #[tauri::command] pub(crate) async fn list_codex_sessions( app: tauri::AppHandle, - gui_config_state: tauri::State<'_, GuiConfigState>, request: Option, ) -> Result { let user_home = app @@ -221,13 +198,12 @@ pub(crate) async fn list_codex_sessions( .home_dir() .map_err(|error| format!("无法获取用户目录: {error}"))?; let codex_home = resolve_codex_home(&user_home); - let repair_on_launch = gui_config_state.snapshot()?.codex_session_repair_on_launch; let request = request.unwrap_or(ListCodexSessionsRequest { offset: 0, limit: DEFAULT_PAGE_SIZE, }); tauri::async_runtime::spawn_blocking(move || { - list_codex_sessions_from_home(&codex_home, request.offset, request.limit, repair_on_launch) + list_codex_sessions_from_home(&codex_home, request.offset, request.limit) }) .await .map_err(|error| format!("读取 Codex 会话任务失败: {error}"))? @@ -308,62 +284,6 @@ pub(crate) async fn apply_codex_session_index_cleanup( .map_err(|error| format!("清理 Codex 会话索引任务失败: {error}"))? } -#[tauri::command] -pub(crate) fn get_codex_session_repair_on_launch( - gui_config_state: tauri::State<'_, GuiConfigState>, -) -> Result { - let config = gui_config_state.snapshot()?; - Ok(RepairOnLaunchSetting { - enabled: config.codex_session_repair_on_launch, - }) -} - -#[tauri::command] -pub(crate) fn set_codex_session_repair_on_launch( - gui_config_state: tauri::State<'_, GuiConfigState>, - request: SetRepairOnLaunchRequest, -) -> Result { - let config = gui_config_state.set_codex_session_repair_on_launch(request.enabled)?; - Ok(RepairOnLaunchSetting { - enabled: config.codex_session_repair_on_launch, - }) -} - -#[tauri::command] -pub(crate) async fn close_chatgpt_app() -> Result { - tauri::async_runtime::spawn_blocking(close_chatgpt_app_processes) - .await - .map_err(|error| format!("关闭 ChatGPT App 任务失败: {error}"))? -} - -pub(crate) fn repair_before_codex_launch( - user_home: &Path, -) -> Result { - let codex_home = resolve_codex_home(user_home); - repair_before_codex_launch_from_home(&codex_home) -} - -fn repair_before_codex_launch_from_home( - codex_home: &Path, -) -> Result { - let target_provider = resolve_current_codex_provider(codex_home)?; - match repair_codex_session_metadata_from_home(codex_home, &target_provider, |_| {}) { - Ok(result) => Ok(result), - Err(error) if is_locked_error_message(&error) => Ok(CodexSessionRepairResult { - target_provider, - changed_rollout_files: 0, - sqlite_rows_updated: 0, - skipped_locked_files: Vec::new(), - backup_path: None, - encrypted_content_warning: None, - warnings: vec![format!( - "启动前恢复历史会话时遇到占用文件,已跳过且继续启动:{error}" - )], - }), - Err(error) => Err(error), - } -} - fn resolve_codex_home(user_home: &Path) -> PathBuf { existing_env_directory("CODEX_HOME").unwrap_or_else(|| user_home.join(".codex")) } @@ -521,7 +441,6 @@ fn list_codex_sessions_from_home( codex_home: &Path, offset: usize, requested_limit: usize, - repair_on_launch: bool, ) -> Result { let limit = requested_limit.clamp(1, MAX_PAGE_SIZE); let fetch_limit = offset.saturating_add(limit).saturating_add(1); @@ -556,7 +475,6 @@ fn list_codex_sessions_from_home( offset, limit, has_more, - repair_on_launch, warnings, }) } @@ -2362,11 +2280,6 @@ fn codex_app_process_ids() -> Result, String> { windows_app_process_ids(&["ChatGPT.exe", "Codex.exe"]) } -#[cfg(target_os = "windows")] -fn chatgpt_app_process_ids() -> Result, String> { - windows_app_process_ids(&["ChatGPT.exe"]) -} - #[cfg(target_os = "windows")] fn windows_app_process_ids(names: &[&str]) -> Result, String> { use std::os::windows::process::CommandExt; @@ -2412,11 +2325,6 @@ fn codex_app_process_ids() -> Result, String> { unix_app_process_ids(&["ChatGPT", "Codex"]) } -#[cfg(not(target_os = "windows"))] -fn chatgpt_app_process_ids() -> Result, String> { - unix_app_process_ids(&["ChatGPT"]) -} - #[cfg(not(target_os = "windows"))] fn unix_app_process_ids(names: &[&str]) -> Result, String> { let mut ids = Vec::new(); @@ -2445,108 +2353,6 @@ fn unix_app_process_ids(names: &[&str]) -> Result, String> { Ok(ids) } -#[cfg(target_os = "windows")] -fn close_chatgpt_app_processes() -> Result { - use std::os::windows::process::CommandExt; - const CREATE_NO_WINDOW: u32 = 0x08000000; - let process_ids = chatgpt_app_process_ids()?; - if process_ids.is_empty() { - return Ok(ChatGptCloseResult { - was_running: false, - closed_processes: 0, - }); - } - - let output = Command::new("taskkill") - .args(["/IM", "ChatGPT.exe", "/T", "/F"]) - .creation_flags(CREATE_NO_WINDOW) - .output() - .map_err(|error| format!("无法关闭 ChatGPT.exe: {error}"))?; - let mut remaining = chatgpt_app_process_ids()?; - for _ in 0..20 { - if remaining.is_empty() { - break; - } - std::thread::sleep(Duration::from_millis(100)); - remaining = chatgpt_app_process_ids()?; - } - if !remaining.is_empty() { - let detail = String::from_utf8_lossy(if output.stderr.is_empty() { - &output.stdout - } else { - &output.stderr - }); - return Err(format!( - "ChatGPT App 未能完全关闭(剩余进程:{}){}{}", - remaining - .iter() - .map(u32::to_string) - .collect::>() - .join("、"), - if detail.trim().is_empty() { "" } else { ":" }, - detail.trim() - )); - } - - Ok(ChatGptCloseResult { - was_running: true, - closed_processes: process_ids.len(), - }) -} - -#[cfg(not(target_os = "windows"))] -fn close_chatgpt_app_processes() -> Result { - let process_ids = chatgpt_app_process_ids()?; - if process_ids.is_empty() { - return Ok(ChatGptCloseResult { - was_running: false, - closed_processes: 0, - }); - } - - let mut terminate = Command::new("kill"); - terminate.arg("-TERM"); - for process_id in &process_ids { - terminate.arg(process_id.to_string()); - } - let _ = terminate.status(); - - for _ in 0..20 { - if chatgpt_app_process_ids()?.is_empty() { - return Ok(ChatGptCloseResult { - was_running: true, - closed_processes: process_ids.len(), - }); - } - std::thread::sleep(Duration::from_millis(100)); - } - - let remaining = chatgpt_app_process_ids()?; - let mut force = Command::new("kill"); - force.arg("-KILL"); - for process_id in &remaining { - force.arg(process_id.to_string()); - } - let _ = force.status(); - std::thread::sleep(Duration::from_millis(100)); - let remaining = chatgpt_app_process_ids()?; - if !remaining.is_empty() { - return Err(format!( - "ChatGPT App 未能完全关闭(剩余进程:{})", - remaining - .iter() - .map(u32::to_string) - .collect::>() - .join("、") - )); - } - - Ok(ChatGptCloseResult { - was_running: true, - closed_processes: process_ids.len(), - }) -} - #[cfg(test)] mod tests { use super::*; @@ -2613,14 +2419,14 @@ mod tests { .unwrap(); drop(automation); - let page = list_codex_sessions_from_home(&root, 0, 50, false).unwrap(); + let page = list_codex_sessions_from_home(&root, 0, 50).unwrap(); assert_eq!(page.sessions.len(), 2); assert_eq!(page.sessions[0].id, "thread-1"); assert_eq!(page.sessions[0].title, "First new"); assert_eq!(page.sessions[0].updated_at_ms, Some(4000)); assert_eq!(page.sessions[1].id, "thread-2"); assert!(page.sessions[1].archived); - let second_page = list_codex_sessions_from_home(&root, 1, 1, false).unwrap(); + let second_page = list_codex_sessions_from_home(&root, 1, 1).unwrap(); assert_eq!(second_page.sessions.len(), 1); assert_eq!(second_page.sessions[0].id, "thread-2"); assert!(!second_page.has_more); @@ -2653,7 +2459,7 @@ mod tests { ) .unwrap(); - let page = list_codex_sessions_from_home(&root, 0, 50, false).unwrap(); + let page = list_codex_sessions_from_home(&root, 0, 50).unwrap(); assert_eq!(page.sessions.len(), 2); assert_eq!(page.sessions[0].id, "api-thread"); assert_eq!(page.sessions[0].title, "API session"); @@ -2682,7 +2488,7 @@ mod tests { drop(connection); fs::write(sqlite.join("broken.db"), b"not a sqlite database").unwrap(); - let page = list_codex_sessions_from_home(&root, 0, 0, true).unwrap(); + let page = list_codex_sessions_from_home(&root, 0, 0).unwrap(); assert_eq!(page.limit, 1); assert_eq!(page.sessions.len(), 1); assert_eq!(page.sessions[0].id, "optional-thread"); @@ -2692,9 +2498,8 @@ mod tests { .warnings .iter() .any(|warning| warning.contains("broken.db"))); - assert!(page.repair_on_launch); assert_eq!( - list_codex_sessions_from_home(&root, 0, usize::MAX, false) + list_codex_sessions_from_home(&root, 0, usize::MAX) .unwrap() .limit, MAX_PAGE_SIZE @@ -3092,29 +2897,6 @@ mod tests { assert!(!is_locked_error_message("database disk image is malformed")); } - #[test] - fn launch_hook_repairs_sessions_for_the_current_provider() { - let root = test_root("repair-launch-hook"); - fs::create_dir_all(root.join("sessions")).unwrap(); - let rollout = root.join("sessions/rollout.jsonl"); - fs::write( - &rollout, - "{\"type\":\"session_meta\",\"payload\":{\"id\":\"thread-1\",\"model_provider\":\"legacy\"}}\n", - ) - .unwrap(); - fs::write(root.join("config.toml"), "model_provider = \"openai\"\n").unwrap(); - let result = repair_before_codex_launch_from_home(&root).unwrap(); - assert_eq!(result.target_provider, "openai"); - assert!(fs::read_to_string(&rollout).unwrap().contains("openai")); - - fs::write(root.join("config.toml"), "model_provider = \"cpa-gui\"\n").unwrap(); - let result = repair_before_codex_launch_from_home(&root).unwrap(); - assert_eq!(result.target_provider, "cpa-gui"); - assert_eq!(result.changed_rollout_files, 1); - assert!(fs::read_to_string(&rollout).unwrap().contains("cpa-gui")); - fs::remove_dir_all(root).unwrap(); - } - #[test] fn session_index_cleanup_preserves_live_and_unknown_records_and_checks_snapshot() { let root = test_root("index"); diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 828e1f2..4496cdc 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -505,9 +505,6 @@ struct GuiConfigFile { proxy_url: String, routing_session_affinity: bool, routing_session_affinity_ttl: String, - codex_session_repair_on_launch: bool, - claude_code_working_directory: String, - claude_code_working_directory_prompt_disabled: bool, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize)] @@ -614,9 +611,6 @@ impl Default for GuiConfigFile { proxy_url: String::new(), routing_session_affinity: false, routing_session_affinity_ttl: String::new(), - codex_session_repair_on_launch: false, - claude_code_working_directory: String::new(), - claude_code_working_directory_prompt_disabled: false, } } } @@ -633,15 +627,12 @@ struct GuiConfigPresence { api_access_remarks: Option>, management_secret_key: Option, close_behavior: Option, - codex_session_repair_on_launch: Option, usage_statistics_enabled: Option, plugins_enabled: Option, routing_strategy: Option, proxy_url: Option, routing_session_affinity: Option, routing_session_affinity_ttl: Option, - claude_code_working_directory: Option, - claude_code_working_directory_prompt_disabled: Option, } #[derive(Clone, Serialize)] @@ -661,8 +652,6 @@ struct AgentConfigStatus { supported_platform: bool, installed: bool, plugin_installed: bool, - executable_path: Option, - launch_targets: Vec, version: Option, cli_version: Option, app_version: Option, @@ -679,20 +668,10 @@ struct AgentConfigStatus { applied_model: Option, claude_code_model_mappings: Option, claude_desktop_model_mappings: Option, - claude_code_working_directory: Option, - claude_code_working_directory_prompt_disabled: bool, warnings: Vec, error: Option, } -#[derive(Clone, Debug, Serialize)] -#[serde(rename_all = "camelCase")] -struct AgentLaunchTarget { - id: String, - label: String, - detail: String, -} - #[derive(Clone, Debug, Serialize)] #[serde(rename_all = "camelCase")] struct AgentConfigActionResult { @@ -871,14 +850,6 @@ enum CodexAppTarget { WindowsAppId(String), } -#[derive(Clone, Debug)] -#[cfg_attr(not(any(target_os = "macos", target_os = "windows")), allow(dead_code))] -enum ClaudeDesktopTarget { - Application(PathBuf), - #[cfg(target_os = "windows")] - WindowsAppId(String), -} - impl AgentClient { fn parse(value: &str) -> Result { match value.trim().to_ascii_lowercase().as_str() { @@ -1404,27 +1375,6 @@ impl GuiConfigState { }) } - fn set_codex_session_repair_on_launch(&self, enabled: bool) -> Result { - self.update(|config| { - config.codex_session_repair_on_launch = enabled; - Ok(()) - }) - } - - fn set_claude_code_launch_preferences( - &self, - working_directory: Option<&Path>, - prompt_disabled: bool, - ) -> Result { - self.update(|config| { - if let Some(working_directory) = working_directory { - config.claude_code_working_directory = path_to_string(working_directory); - } - config.claude_code_working_directory_prompt_disabled = prompt_disabled; - Ok(()) - }) - } - fn set_locale(&self, locale: String) -> Result { self.update(|config| { config.locale = normalize_app_locale(&locale).to_string(); @@ -1748,16 +1698,6 @@ fn inspect_agent_config_statuses( .map(|client| inspect_agent_config(client, &home, config.port, api_key)) .collect::>(); statuses.push(inspect_pi_provider_status(&home, config.port, api_key)); - if let Some(status) = statuses - .iter_mut() - .find(|status| status.id == AgentClient::ClaudeCode.id()) - { - status.claude_code_working_directory = - (!config.claude_code_working_directory.trim().is_empty()) - .then(|| config.claude_code_working_directory.clone()); - status.claude_code_working_directory_prompt_disabled = - config.claude_code_working_directory_prompt_disabled; - } Ok(statuses) } @@ -1956,52 +1896,6 @@ async fn uninstall_pi_provider( Ok(result) } -fn validate_agent_working_directory(value: &str) -> Result { - let value = value.trim(); - if value.is_empty() { - return Err( - "AGENT_WORKING_DIRECTORY_REQUIRED: Please select a working directory".to_string(), - ); - } - if value.chars().any(char::is_control) { - return Err("The working directory contains invalid characters".to_string()); - } - let path = PathBuf::from(value); - if !path.is_absolute() { - return Err("The working directory must be an absolute path".to_string()); - } - if !path.is_dir() { - return Err(format!( - "AGENT_WORKING_DIRECTORY_INVALID: The working directory does not exist: {}", - path_to_string(&path) - )); - } - Ok(path) -} - -fn validate_claude_code_working_directory(value: &str) -> Result { - let value = value.trim(); - if value.is_empty() { - return Err( - "CLAUDE_CODE_WORKING_DIRECTORY_REQUIRED: Please select a working directory".to_string(), - ); - } - if value.chars().any(char::is_control) { - return Err("The Claude Code working directory contains invalid characters".to_string()); - } - let path = PathBuf::from(value); - if !path.is_absolute() { - return Err("The Claude Code working directory must be an absolute path".to_string()); - } - if !path.is_dir() { - return Err(format!( - "CLAUDE_CODE_WORKING_DIRECTORY_INVALID: The working directory does not exist: {}", - path_to_string(&path) - )); - } - Ok(path) -} - #[tauri::command] fn check_codex_oauth_login(app: tauri::AppHandle) -> Result<(), String> { let home = app @@ -2742,237 +2636,6 @@ async fn update_agent_config( ) } -#[tauri::command] -async fn launch_agent( - app: tauri::AppHandle, - gui_config_state: tauri::State<'_, GuiConfigState>, - cache: tauri::State<'_, AgentConfigStatusCache>, - client: String, - target: Option, - working_directory: Option, - suppress_working_directory_prompt: Option, -) -> Result<(), String> { - if client.trim().eq_ignore_ascii_case(PI_AGENT_ID) { - let home = app - .path() - .home_dir() - .map_err(|error| format!("无法获取用户目录: {error}"))?; - let config = gui_config_state.snapshot()?; - let status = - inspect_pi_provider_status(&home, config.port, effective_agent_api_key(&config)); - if !status.installed { - return Err("未检测到 Pi CLI,请先安装 Pi 并确保 pi 命令在 PATH 中".to_string()); - } - if !status.configured { - return Err("请先安装并配置 Pi CLIProxyAPI provider 插件".to_string()); - } - let requested_target = target - .as_deref() - .map(str::trim) - .filter(|value| !value.is_empty()); - if requested_target.is_some_and(|value| value != "cli") { - return Err("Pi 只支持 CLI 启动方式".to_string()); - } - let executable = - find_pi_executable(&home).ok_or_else(|| "未找到 Pi CLI 可执行文件".to_string())?; - let launch_directory = working_directory - .as_deref() - .map(validate_agent_working_directory) - .transpose()? - .unwrap_or_else(|| home.clone()); - return launch_cli_agent(&executable, PI_AGENT_NAME, &launch_directory, &[], &[]); - } - let client = AgentClient::parse(&client)?; - if !client.supported_platform() { - return Err(format!("当前平台不支持启动 {}", client.name())); - } - let home = app - .path() - .home_dir() - .map_err(|error| format!("无法获取用户目录: {error}"))?; - let config = gui_config_state.snapshot()?; - let port = config.port; - let status = inspect_agent_config(client, &home, port, effective_agent_api_key(&config)); - validate_agent_launch_modification( - client, - status.modification_enabled, - &status.modification_state, - )?; - if client == AgentClient::Codex && status.oauth_configuration { - validate_codex_oauth_login(&home)?; - } - if client == AgentClient::Codex && config.codex_session_repair_on_launch { - let repair_home = home.clone(); - tauri::async_runtime::spawn_blocking(move || { - codex_sessions::repair_before_codex_launch(&repair_home) - }) - .await - .map_err(|error| format!("启动前会话修复任务失败: {error}"))??; - } - let requested_target = target - .as_deref() - .map(str::trim) - .filter(|value| !value.is_empty()); - - if client == AgentClient::Codex && requested_target != Some("cli") { - if let Some(app_target) = find_codex_app_target(&home) { - return launch_codex_app(&app_target); - } - #[cfg(any(target_os = "macos", target_os = "windows"))] - if let Some(codex_cli) = find_agent_executable(AgentClient::Codex, &home) { - if launch_codex_app_via_cli(&codex_cli, &home).is_ok() { - return Ok(()); - } - } - if requested_target == Some("app") { - return Err("未检测到 ChatGPT App,请重新检测或改用 Codex CLI".to_string()); - } - } - if requested_target.is_some_and(|value| value != "cli" && value != "app") { - return Err("不支持的智能体启动方式".to_string()); - } - if client == AgentClient::ClaudeDesktop && requested_target == Some("cli") { - return Err("Claude Desktop 不支持 CLI 启动方式".to_string()); - } - if client != AgentClient::Codex - && client != AgentClient::ClaudeDesktop - && requested_target == Some("app") - { - return Err(format!("{} 不支持桌面 App 启动方式", client.name())); - } - - if client == AgentClient::ClaudeDesktop { - let target = find_claude_desktop_target(&home) - .ok_or_else(|| "未检测到 Claude Desktop 应用,请先安装或重新检测。".to_string())?; - return launch_claude_desktop(&target); - } - let executable = find_agent_executable(client, &home) - .ok_or_else(|| format!("未找到 {} 的可执行文件", client.name()))?; - if client == AgentClient::ClaudeCode { - let explicit_working_directory = working_directory - .as_deref() - .map(validate_claude_code_working_directory) - .transpose()?; - let selected_working_directory = match explicit_working_directory { - Some(path) => path, - None if config.claude_code_working_directory_prompt_disabled => { - validate_claude_code_working_directory(&config.claude_code_working_directory)? - } - None => { - return Err( - "CLAUDE_CODE_WORKING_DIRECTORY_REQUIRED: Please select a working directory" - .to_string(), - ) - } - }; - if working_directory.is_some() { - gui_config_state.set_claude_code_launch_preferences( - Some(&selected_working_directory), - suppress_working_directory_prompt.unwrap_or(false), - )?; - cache.clear()?; - } - let claude_config_path = agent_config_paths(client, &home)[0].clone(); - { - let _guard = AGENT_CONFIG_FILE_LOCK - .lock() - .map_err(|_| "Claude Code configuration lock is poisoned".to_string())?; - remove_claude_code_conflicting_api_key(&claude_config_path)?; - } - let mappings = inspect_claude_code_model_mappings(&claude_config_path)? - .or_else(|| { - status - .current_model - .as_deref() - .map(ClaudeDesktopModelMappings::all) - }) - .ok_or_else(|| "Claude Code model environment is not configured".to_string())?; - let prepared = fetch_prepared_agent_models(client, &config).await?; - let max_context_tokens = claude_code_max_context_tokens(&mappings, &prepared.models)?; - let max_context_override = - (!claude_code_model_supports_1m(&prepared.models, &mappings.sonnet)?) - .then_some(max_context_tokens); - { - let _guard = AGENT_CONFIG_FILE_LOCK - .lock() - .map_err(|_| "Claude Code configuration lock is poisoned".to_string())?; - update_claude_code_context_window(&claude_config_path, max_context_override)?; - } - let environment = claude_code_launch_environment( - &format!("http://127.0.0.1:{port}"), - effective_agent_api_key(&config), - &mappings, - &prepared.models, - max_context_tokens, - )?; - let mut environment_to_remove = vec!["ANTHROPIC_API_KEY"]; - if !environment - .iter() - .any(|(key, _)| key == "CLAUDE_CODE_EFFORT_LEVEL") - { - environment_to_remove.push("CLAUDE_CODE_EFFORT_LEVEL"); - } - if max_context_override.is_none() { - environment_to_remove.push(CLAUDE_CODE_MAX_CONTEXT_TOKENS_ENV); - } - return launch_cli_agent( - &executable, - client.name(), - &selected_working_directory, - &environment, - &environment_to_remove, - ); - } - let launch_directory = if client == AgentClient::Codex && requested_target == Some("cli") { - working_directory - .as_deref() - .map(validate_agent_working_directory) - .transpose()? - .unwrap_or_else(|| home.clone()) - } else { - home.clone() - }; - launch_cli_agent(&executable, client.name(), &launch_directory, &[], &[]) -} - -fn validate_agent_launch_modification( - client: AgentClient, - enabled: bool, - state: &str, -) -> Result<(), String> { - if client == AgentClient::Codex || (enabled && state == "applied") { - return Ok(()); - } - Err(format!( - "请先为 {} 应用配置修改,确保 CPA 配置生效后再启动", - client.name() - )) -} - -#[allow(dead_code)] -fn validate_agent_can_enable_legacy( - client: AgentClient, - home: &Path, - port: u16, -) -> Result<(), String> { - if !client.supported_platform() { - return Err(format!( - "{} 当前仅支持在 Windows、macOS 和 Linux 上配置", - client.name() - )); - } - let detection = inspect_agent_config(client, home, port, DEFAULT_API_KEY); - if !detection.installed { - return Err(format!("未检测到 {},请先安装后再配置", client.name())); - } - if !detection.config_valid { - return Err(detection - .error - .unwrap_or_else(|| "原配置格式异常,请先修复后再应用配置修改".to_string())); - } - Ok(()) -} - fn validate_agent_can_enable( client: AgentClient, home: &Path, @@ -3510,16 +3173,6 @@ fn inspect_pi_provider_status(home: &Path, port: u16, api_key: &str) -> AgentCon let config_path = pi_provider_config_path(home); let settings_path = pi_provider_settings_path(home); let executable = find_pi_executable(home); - let launch_targets = executable - .as_ref() - .map(|path| { - vec![AgentLaunchTarget { - id: "cli".to_string(), - label: PI_AGENT_NAME.to_string(), - detail: path_to_string(path), - }] - }) - .unwrap_or_default(); let mut errors = Vec::new(); let (plugin_installed, default_provider_matches, current_model) = match read_pi_settings(home) { Ok(Some(settings)) => { @@ -3600,8 +3253,6 @@ fn inspect_pi_provider_status(home: &Path, port: u16, api_key: &str) -> AgentCon supported_platform: true, installed: executable.is_some(), plugin_installed, - executable_path: launch_targets.first().map(|target| target.detail.clone()), - launch_targets, version: cli_version.clone(), cli_version, app_version: None, @@ -3618,8 +3269,6 @@ fn inspect_pi_provider_status(home: &Path, port: u16, api_key: &str) -> AgentCon applied_model: current_model, claude_code_model_mappings: None, claude_desktop_model_mappings: None, - claude_code_working_directory: None, - claude_code_working_directory_prompt_disabled: false, warnings, error, } @@ -4046,7 +3695,6 @@ fn inspect_agent_config( } Err(error) => (false, None, false, false, Some(error)), }; - let launch_targets = agent_launch_targets(client, home); let executable = find_agent_executable(client, home); let cli_version = executable.as_deref().and_then(read_agent_version); let app_version = match client { @@ -4055,14 +3703,12 @@ fn inspect_agent_config( _ => None, }; let version = cli_version.clone().or_else(|| app_version.clone()); - let installed = agent_client_is_installed(client, &launch_targets, version.as_deref()); + let installed = version.is_some(); let mut warnings = Vec::new(); if !client.supported_platform() { warnings.push("当前平台不支持 Claude Desktop 3P 配置".to_string()); - } else if launch_targets.is_empty() && config_exists { - warnings.push("只检测到配置文件,未在 PATH 中找到客户端命令".to_string()); - } else if !installed && !launch_targets.is_empty() && version.is_none() { - warnings.push("检测到客户端入口,但无法读取有效版本,未标记为已安装".to_string()); + } else if !installed && config_exists { + warnings.push("只检测到配置文件,未检测到客户端".to_string()); } if let Some(message) = error.as_ref() { warnings.push(message.clone()); @@ -4076,8 +3722,6 @@ fn inspect_agent_config( supported_platform: client.supported_platform(), installed, plugin_installed: true, - executable_path: launch_targets.first().map(|target| target.detail.clone()), - launch_targets, version, cli_version, app_version, @@ -4096,8 +3740,6 @@ fn inspect_agent_config( .then(|| inspect_claude_code_model_mappings(&paths[0]).ok().flatten()) .flatten(), claude_desktop_model_mappings: modification.claude_desktop_model_mappings, - claude_code_working_directory: None, - claude_code_working_directory_prompt_disabled: false, warnings, error, } @@ -4315,103 +3957,7 @@ fn is_managed_agent_base_url(value: &str) -> bool { value.starts_with("http://127.0.0.1:") || value.starts_with("http://localhost:") } -fn agent_client_is_installed( - client: AgentClient, - launch_targets: &[AgentLaunchTarget], - version: Option<&str>, -) -> bool { - if client == AgentClient::Codex { - // Codex can be installed as either a CLI or a desktop app. Discovery - // of either real entry is enough; version lookup is independent. - launch_targets - .iter() - .any(|target| target.id == "cli" || target.id == "app") - } else { - version.is_some() - } -} - -fn agent_launch_targets(client: AgentClient, home: &Path) -> Vec { - let mut targets = Vec::new(); - if client == AgentClient::ClaudeDesktop { - if let Some(target) = find_claude_desktop_target(home) { - targets.push(AgentLaunchTarget { - id: "app".to_string(), - label: "Claude Desktop".to_string(), - detail: claude_desktop_target_detail(&target), - }); - } - return targets; - } - if client == AgentClient::Codex { - #[cfg(any(target_os = "macos", target_os = "windows"))] - if let Some(target) = find_codex_app_target(home) { - targets.push(AgentLaunchTarget { - id: "app".to_string(), - label: "ChatGPT App".to_string(), - detail: codex_app_target_detail(&target), - }); - } - } - - if let Some(executable) = find_agent_executable(client, home) { - targets.push(AgentLaunchTarget { - id: if client == AgentClient::ClaudeDesktop { - "app".to_string() - } else { - "cli".to_string() - }, - label: match client { - AgentClient::ClaudeDesktop => "Claude Desktop".to_string(), - AgentClient::Codex => "Codex CLI".to_string(), - _ => client.name().to_string(), - }, - detail: path_to_string(&executable), - }); - } - targets -} - -#[cfg(any(target_os = "macos", target_os = "windows"))] -fn codex_app_target_detail(target: &CodexAppTarget) -> String { - match target { - CodexAppTarget::Application(path) => path_to_string(path), - #[cfg(target_os = "windows")] - CodexAppTarget::WindowsAppId(app_id) => format!("Microsoft Store · {app_id}"), - } -} - -fn claude_desktop_target_detail(target: &ClaudeDesktopTarget) -> String { - match target { - ClaudeDesktopTarget::Application(path) => path_to_string(path), - #[cfg(target_os = "windows")] - ClaudeDesktopTarget::WindowsAppId(app_id) => format!("Microsoft Store · {app_id}"), - } -} - -#[cfg(any(target_os = "macos", target_os = "windows"))] -fn launch_codex_app_via_cli(executable: &Path, home: &Path) -> Result<(), String> { - #[cfg(target_os = "windows")] - let mut command = windows_command_for_executable(executable, false); - - #[cfg(not(target_os = "windows"))] - let mut command = Command::new(executable); - - command - .arg("app") - .arg(home) - .current_dir(home) - .stdin(Stdio::null()) - .stdout(Stdio::null()) - .stderr(Stdio::null()); - configure_background_command(&mut command); - command - .spawn() - .map(|_| ()) - .map_err(|error| format!("通过 Codex CLI 启动 ChatGPT App 失败: {error}")) -} - -fn find_codex_app_target(home: &Path) -> Option { +fn find_codex_app_installation(home: &Path) -> Option { #[cfg(target_os = "macos")] { [PathBuf::from("/Applications"), home.join("Applications")] @@ -4432,7 +3978,7 @@ fn find_codex_app_target(home: &Path) -> Option { #[cfg(target_os = "windows")] { - find_windows_codex_app_target(home) + find_windows_codex_app_installation(home) } #[cfg(not(any(target_os = "macos", target_os = "windows")))] @@ -4442,15 +3988,6 @@ fn find_codex_app_target(home: &Path) -> Option { } } -fn find_claude_desktop_target(home: &Path) -> Option { - #[cfg(target_os = "windows")] - { - find_windows_claude_desktop_target(home) - } - #[cfg(not(target_os = "windows"))] - find_claude_desktop_executable(home).map(ClaudeDesktopTarget::Application) -} - fn read_claude_desktop_version(home: &Path) -> Option { #[cfg(target_os = "windows")] { @@ -4465,8 +4002,11 @@ fn read_claude_desktop_version(home: &Path) -> Option { fn read_codex_app_version(home: &Path) -> Option { #[cfg(target_os = "windows")] { - match find_codex_app_target(home)? { - CodexAppTarget::WindowsAppId(_) => read_windows_codex_store_version(), + match find_codex_app_installation(home)? { + CodexAppTarget::WindowsAppId(app_id) => { + let _ = app_id; + read_windows_codex_store_version() + } CodexAppTarget::Application(path) => { read_windows_executable_version(&path).or_else(|| read_agent_version(&path)) } @@ -4474,7 +4014,7 @@ fn read_codex_app_version(home: &Path) -> Option { } #[cfg(target_os = "macos")] { - let CodexAppTarget::Application(path) = find_codex_app_target(home)?; + let CodexAppTarget::Application(path) = find_codex_app_installation(home)?; read_macos_app_version(&path) } #[cfg(not(any(target_os = "windows", target_os = "macos")))] @@ -4552,72 +4092,12 @@ fn windows_command_processor() -> PathBuf { } #[cfg(target_os = "windows")] -fn find_windows_codex_app_target(home: &Path) -> Option { - find_windows_registered_codex_app_target() +fn find_windows_codex_app_installation(home: &Path) -> Option { + find_windows_registered_codex_app_installation() .or_else(|| find_windows_codex_app_id_via_registry().map(CodexAppTarget::WindowsAppId)) .or_else(|| find_windows_codex_app_executable(home).map(CodexAppTarget::Application)) } -#[cfg(target_os = "windows")] -fn find_windows_claude_desktop_target(home: &Path) -> Option { - find_windows_registered_claude_desktop_target() - .or_else(|| find_claude_desktop_executable(home).map(ClaudeDesktopTarget::Application)) -} - -#[cfg(target_os = "windows")] -fn find_windows_registered_claude_desktop_target() -> Option { - const DISCOVERY_SCRIPT: &str = r#" -$ErrorActionPreference = 'SilentlyContinue' -$ProgressPreference = 'SilentlyContinue' -$startApps = @(Get-StartApps) -$appId = $startApps | - Where-Object { - $_.AppID -like 'Claude_*!*' -or - $_.AppID -like 'Anthropic.Claude_*!*' - } | - Select-Object -First 1 -ExpandProperty AppID -if ($appId) { - Write-Output "APPID:$appId" - exit 0 -} - -$packages = @(Get-AppxPackage) | - Where-Object { - $_.Name -eq 'Claude' -or - $_.Name -like 'Anthropic.Claude*' -or - $_.PackageFamilyName -match '^(Claude|Anthropic\.Claude)_' - } -foreach ($package in $packages) { - $manifest = Get-AppxPackageManifest -Package $package.PackageFullName - $application = @($manifest.Package.Applications.Application) | - Where-Object { $_.Id } | - Select-Object -First 1 - if ($application -and $package.PackageFamilyName) { - Write-Output "APPID:$($package.PackageFamilyName)!$($application.Id)" - exit 0 - } -} -"#; - - let encoded_command = windows_powershell_encoded_command(DISCOVERY_SCRIPT); - let mut command = Command::new(windows_powershell_executable()); - command.args([ - "-NoLogo", - "-NoProfile", - "-NonInteractive", - "-ExecutionPolicy", - "Bypass", - "-EncodedCommand", - &encoded_command, - ]); - configure_background_command(&mut command); - let output = command.output().ok()?; - if !output.status.success() { - return None; - } - parse_windows_claude_desktop_discovery_output(&String::from_utf8_lossy(&output.stdout)) -} - #[cfg(target_os = "windows")] fn read_windows_claude_desktop_store_version() -> Option { const VERSION_SCRIPT: &str = r#" @@ -4690,7 +4170,7 @@ if ($package -and $package.Version) { } #[cfg(target_os = "windows")] -fn find_windows_registered_codex_app_target() -> Option { +fn find_windows_registered_codex_app_installation() -> Option { const DISCOVERY_SCRIPT: &str = r#" $ErrorActionPreference = 'SilentlyContinue' $ProgressPreference = 'SilentlyContinue' @@ -4843,14 +4323,6 @@ fn parse_windows_codex_app_discovery_output(output: &str) -> Option Option { - output.lines().find_map(|line| { - let app_id = line.trim().strip_prefix("APPID:")?.trim(); - (!app_id.is_empty()).then(|| ClaudeDesktopTarget::WindowsAppId(app_id.to_string())) - }) -} - #[cfg(target_os = "windows")] fn parse_windows_claude_desktop_version_output(output: &str) -> Option { output.lines().find_map(|line| { @@ -4951,45 +4423,6 @@ fn find_windows_codex_app_executable(home: &Path) -> Option { candidates.into_iter().find(|path| path.is_file()) } -#[cfg(target_os = "macos")] -fn launch_codex_app(target: &CodexAppTarget) -> Result<(), String> { - let CodexAppTarget::Application(application) = target; - Command::new("open") - .arg(application) - .stdin(Stdio::null()) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .spawn() - .map(|_| ()) - .map_err(|error| format!("启动 ChatGPT App 失败: {error}")) -} - -#[cfg(target_os = "windows")] -fn launch_codex_app(target: &CodexAppTarget) -> Result<(), String> { - let mut command = match target { - CodexAppTarget::Application(executable) => Command::new(executable), - CodexAppTarget::WindowsAppId(app_id) => { - let mut command = Command::new(windows_explorer_executable()); - command.arg(format!("shell:AppsFolder\\{app_id}")); - command - } - }; - command - .stdin(Stdio::null()) - .stdout(Stdio::null()) - .stderr(Stdio::null()); - configure_background_command(&mut command); - command - .spawn() - .map(|_| ()) - .map_err(|error| format!("启动 ChatGPT App 失败: {error}")) -} - -#[cfg(not(any(target_os = "macos", target_os = "windows")))] -fn launch_codex_app(_target: &CodexAppTarget) -> Result<(), String> { - Err("当前平台不支持 ChatGPT App".to_string()) -} - fn find_agent_executable(client: AgentClient, home: &Path) -> Option { if client == AgentClient::ClaudeDesktop { return find_claude_desktop_executable(home); @@ -5182,203 +4615,6 @@ fn normalize_detected_agent_version(value: &str) -> Option { .then(|| value.to_string()) } -fn launch_desktop_agent(executable: &Path, label: &str) -> Result<(), String> { - #[cfg(target_os = "macos")] - let mut command = { - let application = executable - .ancestors() - .find(|path| path.extension().and_then(|value| value.to_str()) == Some("app")) - .unwrap_or(executable); - let mut command = Command::new("open"); - command.arg(application); - command - }; - - #[cfg(target_os = "windows")] - let mut command = Command::new(executable); - - #[cfg(target_os = "linux")] - let mut command = Command::new(executable); - - #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))] - { - let _ = (executable, label); - return Err("当前平台不支持桌面智能体".to_string()); - } - - #[cfg(any(target_os = "macos", target_os = "windows", target_os = "linux"))] - configure_background_command(&mut command); - - #[cfg(any(target_os = "macos", target_os = "windows", target_os = "linux"))] - command - .stdin(Stdio::null()) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .spawn() - .map(|_| ()) - .map_err(|error| format!("启动 {label} 失败: {error}")) -} - -fn launch_claude_desktop(target: &ClaudeDesktopTarget) -> Result<(), String> { - match target { - ClaudeDesktopTarget::Application(executable) => { - launch_desktop_agent(executable, "Claude Desktop") - } - #[cfg(target_os = "windows")] - ClaudeDesktopTarget::WindowsAppId(app_id) => { - let mut command = Command::new(windows_explorer_executable()); - command.arg(format!("shell:AppsFolder\\{app_id}")); - command - .stdin(Stdio::null()) - .stdout(Stdio::null()) - .stderr(Stdio::null()); - configure_background_command(&mut command); - command - .spawn() - .map(|_| ()) - .map_err(|error| format!("启动 Claude Desktop 失败: {error}")) - } - } -} - -#[cfg(target_os = "macos")] -fn shell_single_quote(value: &str) -> String { - format!("'{}'", value.replace('\'', "'\"'\"'")) -} - -#[cfg(target_os = "macos")] -fn launch_cli_agent( - executable: &Path, - label: &str, - working_directory: &Path, - environment: &[(String, String)], - environment_to_remove: &[&str], -) -> Result<(), String> { - let environment_removals = environment_to_remove - .iter() - .map(|key| format!("-u {}", shell_single_quote(key))) - .collect::>() - .join(" "); - let environment = environment - .iter() - .map(|(key, value)| format!("{key}={}", shell_single_quote(value))) - .collect::>() - .join(" "); - let command_line = format!( - "cd {} && exec env {} {} {}", - shell_single_quote(&path_to_string(working_directory)), - environment_removals, - environment, - shell_single_quote(&path_to_string(executable)) - ); - let apple_script_command = command_line.replace('\\', "\\\\").replace('"', "\\\""); - let script = format!( - "tell application \"Terminal\"\nactivate\ndo script \"{apple_script_command}\"\nend tell" - ); - let output = Command::new("osascript") - .arg("-e") - .arg(script) - .output() - .map_err(|error| format!("启动 {label} 终端失败: {error}"))?; - if output.status.success() { - return Ok(()); - } - let message = String::from_utf8_lossy(&output.stderr).trim().to_string(); - Err(format!( - "启动 {label} 终端失败{}", - if message.is_empty() { - String::new() - } else { - format!(": {message}") - } - )) -} - -#[cfg(target_os = "linux")] -fn launch_cli_agent( - executable: &Path, - label: &str, - working_directory: &Path, - environment: &[(String, String)], - environment_to_remove: &[&str], -) -> Result<(), String> { - let terminals: &[(&str, &[&str])] = &[ - ("x-terminal-emulator", &["-e"]), - ("gnome-terminal", &["--"]), - ("konsole", &["-e"]), - ("xfce4-terminal", &["-e"]), - ("mate-terminal", &["--"]), - ("kitty", &["-e"]), - ("alacritty", &["-e"]), - ("ghostty", &["-e"]), - ("xterm", &["-e"]), - ]; - let path = env::var_os("PATH").unwrap_or_default(); - let mut last_error = None; - for (terminal, arguments) in terminals { - let available = env::split_paths(&path).any(|directory| directory.join(terminal).is_file()); - if !available { - continue; - } - let mut command = Command::new(terminal); - command - .args(*arguments) - .arg(executable) - .envs(environment.iter().map(|(key, value)| (key, value))) - .current_dir(working_directory) - .stdin(Stdio::null()) - .stdout(Stdio::null()) - .stderr(Stdio::null()); - for key in environment_to_remove { - command.env_remove(key); - } - match command.spawn() { - Ok(_) => return Ok(()), - Err(error) => last_error = Some(error.to_string()), - } - } - Err(match last_error { - Some(error) => format!("启动 {label} 失败: {error}"), - None => format!("启动 {label} 失败:未找到可用的终端程序"), - }) -} - -#[cfg(target_os = "windows")] -fn launch_cli_agent( - executable: &Path, - label: &str, - working_directory: &Path, - environment: &[(String, String)], - environment_to_remove: &[&str], -) -> Result<(), String> { - use std::os::windows::process::CommandExt; - - const CREATE_NEW_CONSOLE: u32 = 0x0000_0010; - let mut command = windows_command_for_executable(executable, true); - command - .envs(environment.iter().map(|(key, value)| (key, value))) - .current_dir(working_directory) - .creation_flags(CREATE_NEW_CONSOLE); - for key in environment_to_remove { - command.env_remove(key); - } - command - .spawn() - .map(|_| ()) - .map_err(|error| format!("启动 {label} 失败: {error}")) -} - -#[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))] -fn launch_cli_agent( - _executable: &Path, - label: &str, - _working_directory: &Path, - _environment: &[(String, String)], - _environment_to_remove: &[&str], -) -> Result<(), String> { - Err(format!("当前平台不支持启动 {label}")) -} - fn inspect_claude_agent_config( path: &Path, port: u16, @@ -5633,52 +4869,6 @@ fn claude_code_model_presentation_environment( .collect()) } -fn claude_code_launch_environment( - base_url: &str, - api_key: &str, - mappings: &ClaudeDesktopModelMappings, - models: &[AgentModelOption], - max_context_tokens: u64, -) -> Result, String> { - let model_settings = claude_code_model_settings(mappings, models)?; - let subagent_model = strip_claude_code_context_suffix(&mappings.haiku).to_string(); - let mut environment = [ - ("ANTHROPIC_BASE_URL", base_url), - ("ANTHROPIC_AUTH_TOKEN", api_key), - ("ANTHROPIC_MODEL", model_settings.sonnet.as_str()), - ( - "ANTHROPIC_DEFAULT_HAIKU_MODEL", - model_settings.haiku.as_str(), - ), - ( - "ANTHROPIC_DEFAULT_SONNET_MODEL", - model_settings.sonnet.as_str(), - ), - ("ANTHROPIC_DEFAULT_OPUS_MODEL", model_settings.opus.as_str()), - ( - "ANTHROPIC_DEFAULT_FABLE_MODEL", - model_settings.sonnet.as_str(), - ), - ("CLAUDE_CODE_SUBAGENT_MODEL", subagent_model.as_str()), - ] - .into_iter() - .map(|(key, value)| (key.to_string(), value.to_string())) - .collect::>(); - environment.extend(claude_code_model_presentation_environment( - mappings, models, - )?); - if let Some(effort_level) = claude_code_model_effort_level(models, &mappings.sonnet)? { - environment.push(("CLAUDE_CODE_EFFORT_LEVEL".to_string(), effort_level)); - } - if !claude_code_model_supports_1m(models, &mappings.sonnet)? { - environment.push(( - CLAUDE_CODE_MAX_CONTEXT_TOKENS_ENV.to_string(), - max_context_tokens.to_string(), - )); - } - Ok(environment) -} - fn inspect_claude_desktop_agent_config( paths: &[PathBuf], port: u16, @@ -6213,57 +5403,6 @@ fn build_claude_agent_config( Ok(rendered) } -fn remove_claude_code_conflicting_api_key(path: &Path) -> Result { - update_agent_json_file(path, "Claude Code configuration", |root| { - let managed = root - .get("env") - .and_then(serde_json::Value::as_object) - .and_then(|env| env.get("ANTHROPIC_BASE_URL")) - .and_then(serde_json::Value::as_str) - .is_some_and(is_managed_agent_base_url); - if !managed { - return false; - } - root.get_mut("env") - .and_then(serde_json::Value::as_object_mut) - .is_some_and(|env| env.remove("ANTHROPIC_API_KEY").is_some()) - }) -} - -fn update_claude_code_context_window( - path: &Path, - max_context_tokens: Option, -) -> Result { - update_agent_json_file(path, "Claude Code configuration", |root| { - let managed = root - .get("env") - .and_then(serde_json::Value::as_object) - .and_then(|env| env.get("ANTHROPIC_BASE_URL")) - .and_then(serde_json::Value::as_str) - .is_some_and(is_managed_agent_base_url); - if !managed { - return false; - } - let env = ensure_json_object_entry(root, "env"); - let Some(max_context_tokens) = max_context_tokens else { - return env.remove(CLAUDE_CODE_MAX_CONTEXT_TOKENS_ENV).is_some(); - }; - let expected = max_context_tokens.to_string(); - if env - .get(CLAUDE_CODE_MAX_CONTEXT_TOKENS_ENV) - .and_then(serde_json::Value::as_str) - == Some(expected.as_str()) - { - return false; - } - env.insert( - CLAUDE_CODE_MAX_CONTEXT_TOKENS_ENV.to_string(), - serde_json::Value::String(expected), - ); - true - }) -} - fn parse_agent_json_object( existing: Option<&str>, label: &str, @@ -16237,6 +15376,22 @@ fn load_or_create_gui_config() -> Result { ) }; + if gui_config_exists { + if let Ok(content) = fs::read_to_string(&config_path) { + changed |= [ + "codex-session-repair-on-launch", + "claude-code-working-directory", + "claude-code-working-directory-prompt-disabled", + ] + .iter() + .any(|key| { + content + .lines() + .any(|line| line.trim_start().starts_with(key)) + }); + } + } + let core_config_path = core_install_dir()?.join(CORE_CONFIG_FILE); let core_is_newer = file_modified_time(&core_config_path) .zip(file_modified_time(&config_path)) @@ -16328,16 +15483,6 @@ fn load_or_create_gui_config() -> Result { if presence.close_behavior.is_none() { changed = true; } - if presence.codex_session_repair_on_launch.is_none() { - changed = true; - } - if presence.claude_code_working_directory.is_none() - || presence - .claude_code_working_directory_prompt_disabled - .is_none() - { - changed = true; - } let management_secret_rotated = ensure_strong_management_secret(&mut config)?; changed |= management_secret_rotated; changed |= sanitize_gui_config(&mut config)?; @@ -16618,11 +15763,6 @@ fn sanitize_gui_config(config: &mut GuiConfigFile) -> Result { config.routing_session_affinity_ttl = routing_session_affinity_ttl; changed = true; } - let claude_code_working_directory = config.claude_code_working_directory.trim().to_string(); - if config.claude_code_working_directory != claude_code_working_directory { - config.claude_code_working_directory = claude_code_working_directory; - changed = true; - } let window_size = configured_window_size(config); let normalized_width = window_size.map(|size| size.width); let normalized_height = window_size.map(|size| size.height); @@ -16685,21 +15825,16 @@ fn write_gui_config_to_path(config: &GuiConfigFile, config_path: &Path) -> Resul "routing-session-affinity-ttl", value(config.routing_session_affinity_ttl.as_str()), ), - ( - "codex-session-repair-on-launch", - value(config.codex_session_repair_on_launch), - ), - ( - "claude-code-working-directory", - value(config.claude_code_working_directory.as_str()), - ), - ( - "claude-code-working-directory-prompt-disabled", - value(config.claude_code_working_directory_prompt_disabled), - ), ] { set_codex_table_item(root, key, item); } + for key in [ + "codex-session-repair-on-launch", + "claude-code-working-directory", + "claude-code-working-directory-prompt-disabled", + ] { + root.remove(key); + } for (key, dimension) in [ ("window-width", config.window_width), ("window-height", config.window_height), @@ -16782,13 +15917,6 @@ fn validate_gui_config(config: &GuiConfigFile) -> Result<(), String> { { return Err("会话粘性 TTL 不能包含控制字符".to_string()); } - if config - .claude_code_working_directory - .chars() - .any(char::is_control) - { - return Err("Claude Code working directory cannot contain control characters".to_string()); - } Ok(()) } @@ -18994,7 +18122,6 @@ fn main() { let initial_window_size = configured_window_size(&gui_config); let app = tauri::Builder::default() - .plugin(tauri_plugin_dialog::init()) .plugin( tauri_plugin_opener::Builder::new() .open_js_links_on_click(false) @@ -19179,7 +18306,6 @@ fn main() { clear_codex_config, set_agent_config_enabled, update_agent_config, - launch_agent, get_lan_ipv4, save_gui_settings, save_network_routing_settings, @@ -19227,10 +18353,7 @@ fn main() { codex_sessions::delete_codex_sessions, codex_sessions::repair_codex_session_metadata, codex_sessions::preview_codex_session_index_cleanup, - codex_sessions::apply_codex_session_index_cleanup, - codex_sessions::get_codex_session_repair_on_launch, - codex_sessions::set_codex_session_repair_on_launch, - codex_sessions::close_chatgpt_app + codex_sessions::apply_codex_session_index_cleanup ]) .build(tauri::generate_context!()) .expect("failed to build app"); @@ -19472,7 +18595,7 @@ data: {"delta":{"type":"text_delta","text":"H"}} } #[test] - fn codex_oauth_launch_requires_auth_json_with_tokens() { + fn codex_oauth_requires_auth_json_with_tokens() { let home = agent_test_home("codex-oauth-login"); let auth_path = home.join("auth.json"); @@ -20659,7 +19782,7 @@ model: let path = home.join("config.toml"); fs::write( &path, - "# keep this comment\ncustom-option = \"keep\"\nport = 7000\n\n[third-party]\nenabled = true\n", + "# keep this comment\ncustom-option = \"keep\"\ncodex-session-repair-on-launch = true\nclaude-code-working-directory = \"legacy\"\nclaude-code-working-directory-prompt-disabled = true\nport = 7000\n\n[third-party]\nenabled = true\n", ) .unwrap(); let config = GuiConfigFile { @@ -20669,9 +19792,6 @@ model: auth_dir: path_to_string(&home.join("custom-auth")), management_secret_key: "custom-secret".to_string(), usage_statistics_enabled: false, - codex_session_repair_on_launch: true, - claude_code_working_directory: path_to_string(&home), - claude_code_working_directory_prompt_disabled: true, ..GuiConfigFile::default() }; @@ -20684,9 +19804,8 @@ model: assert!(content.contains("host = \"0.0.0.0\"")); assert!(content.contains("management-secret-key = \"custom-secret\"")); assert!(content.contains("usage-statistics-enabled = false")); - assert!(content.contains("codex-session-repair-on-launch = true")); - assert!(content.contains("claude-code-working-directory = ")); - assert!(content.contains("claude-code-working-directory-prompt-disabled = true")); + assert!(!content.contains("codex-session-repair-on-launch")); + assert!(!content.contains("claude-code-working-directory")); fs::remove_dir_all(home).unwrap(); } @@ -20755,7 +19874,6 @@ model: assert!(content.contains("management-secret-key = \"\"")); assert!(content.contains("plugins-enabled = false")); assert!(content.contains("routing-strategy = \"round-robin\"")); - assert!(content.contains("codex-session-repair-on-launch = false")); } #[test] @@ -20874,7 +19992,7 @@ model: } #[test] - fn claude_code_role_mappings_drive_settings_and_launch_environment() { + fn claude_code_role_mappings_drive_settings() { let mappings = ClaudeDesktopModelMappings { opus: "gpt-opus".to_string(), sonnet: "gpt-sonnet".to_string(), @@ -20925,34 +20043,6 @@ model: value["env"]["ANTHROPIC_CUSTOM_MODEL_OPTION_NAME"], "gpt-sonnet (272K context)" ); - - let environment = claude_code_launch_environment( - "http://127.0.0.1:8317", - "test-key", - &mappings, - &models, - claude_code_max_context_tokens(&mappings, &models).unwrap(), - ) - .unwrap(); - let read = |key: &str| { - environment - .iter() - .find(|(candidate, _)| candidate == key) - .map(|(_, value)| value.as_str()) - }; - assert_eq!(read("ANTHROPIC_BASE_URL"), Some("http://127.0.0.1:8317")); - assert_eq!(read("ANTHROPIC_AUTH_TOKEN"), Some("test-key")); - assert_eq!(read("ANTHROPIC_API_KEY"), None); - assert_eq!(read("ANTHROPIC_DEFAULT_OPUS_MODEL"), Some("gpt-opus")); - assert_eq!(read("ANTHROPIC_DEFAULT_SONNET_MODEL"), Some("gpt-sonnet")); - assert_eq!(read("ANTHROPIC_DEFAULT_HAIKU_MODEL"), Some("gpt-haiku")); - assert_eq!(read("CLAUDE_CODE_SUBAGENT_MODEL"), Some("gpt-haiku")); - assert_eq!(read("CLAUDE_CODE_EFFORT_LEVEL"), None); - assert_eq!( - read("ANTHROPIC_CUSTOM_MODEL_OPTION_NAME"), - Some("gpt-sonnet (272K context)") - ); - assert_eq!(read(CLAUDE_CODE_MAX_CONTEXT_TOKENS_ENV), Some("272000")); } #[test] @@ -21025,18 +20115,6 @@ model: "DeepSeek V4 Pro (Fable mapping, 1M context)" ); assert_eq!(value["model"], "deepseek-v4-pro[1m]"); - - let environment = claude_code_launch_environment( - "http://127.0.0.1:8317", - "test-key", - &mappings, - &models, - claude_code_max_context_tokens(&mappings, &models).unwrap(), - ) - .unwrap(); - assert!(!environment - .iter() - .any(|(key, _)| key == CLAUDE_CODE_MAX_CONTEXT_TOKENS_ENV)); } #[test] @@ -21134,62 +20212,6 @@ model: ); } - #[test] - fn claude_code_launch_migrates_legacy_dual_auth_configuration() { - let directory = agent_test_home("claude-code-dual-auth-migration"); - let path = directory.join("settings.json"); - fs::write( - &path, - r#"{"env":{"ANTHROPIC_BASE_URL":"http://127.0.0.1:8317","ANTHROPIC_API_KEY":"legacy","ANTHROPIC_AUTH_TOKEN":"token"}}"#, - ) - .unwrap(); - - assert!(remove_claude_code_conflicting_api_key(&path).unwrap()); - assert!(update_claude_code_context_window(&path, Some(272_000)).unwrap()); - assert!(!update_claude_code_context_window(&path, Some(272_000)).unwrap()); - let value: serde_json::Value = - serde_json::from_str(&fs::read_to_string(&path).unwrap()).unwrap(); - assert!(value["env"].get("ANTHROPIC_API_KEY").is_none()); - assert_eq!(value["env"]["ANTHROPIC_AUTH_TOKEN"], "token"); - assert_eq!(value["env"][CLAUDE_CODE_MAX_CONTEXT_TOKENS_ENV], "272000"); - fs::remove_dir_all(directory).unwrap(); - } - - #[test] - fn claude_code_1m_variant_removes_global_context_override() { - let directory = agent_test_home("claude-code-remove-context-override"); - let path = directory.join("settings.json"); - fs::write( - &path, - r#"{"env":{"ANTHROPIC_BASE_URL":"http://127.0.0.1:8317","CLAUDE_CODE_MAX_CONTEXT_TOKENS":"1000000"}}"#, - ) - .unwrap(); - - assert!(update_claude_code_context_window(&path, None).unwrap()); - assert!(!update_claude_code_context_window(&path, None).unwrap()); - let value: serde_json::Value = - serde_json::from_str(&fs::read_to_string(&path).unwrap()).unwrap(); - assert!(value["env"] - .get(CLAUDE_CODE_MAX_CONTEXT_TOKENS_ENV) - .is_none()); - fs::remove_dir_all(directory).unwrap(); - } - - #[test] - fn claude_code_working_directory_must_be_absolute_and_exist() { - let directory = agent_test_home("claude-code-working-directory"); - assert_eq!( - validate_claude_code_working_directory(&path_to_string(&directory)).unwrap(), - directory - ); - assert!(validate_claude_code_working_directory("relative/path").is_err()); - assert!(validate_claude_code_working_directory(&path_to_string( - &directory.join("missing") - )) - .is_err()); - fs::remove_dir_all(directory).unwrap(); - } - #[test] fn codex_agent_config_uses_managed_provider_without_losing_comments() { let rendered = build_codex_agent_config( @@ -22163,32 +21185,6 @@ oauth-model-alias: assert_eq!(GuiConfigFile::default().locale, "zh-CN"); } - #[test] - fn codex_installation_is_detected_from_either_cli_or_app_entry() { - let cli_target = vec![AgentLaunchTarget { - id: "cli".to_string(), - label: "Codex CLI".to_string(), - detail: "codex".to_string(), - }]; - let app_target = vec![AgentLaunchTarget { - id: "app".to_string(), - label: "ChatGPT App".to_string(), - detail: "Microsoft Store · OpenAI.Codex_2p2nqsd0c76g0!App".to_string(), - }]; - - assert!(agent_client_is_installed( - AgentClient::Codex, - &cli_target, - None - )); - assert!(agent_client_is_installed( - AgentClient::Codex, - &app_target, - None - )); - assert!(!agent_client_is_installed(AgentClient::Codex, &[], None)); - } - #[cfg(target_os = "windows")] #[test] fn windows_chatgpt_discovery_parser_accepts_registered_app_and_executable() { @@ -22236,34 +21232,6 @@ oauth-model-alias: .is_none()); } - #[cfg(target_os = "windows")] - #[test] - fn windows_claude_desktop_discovery_parser_accepts_store_app() { - let target = parse_windows_claude_desktop_discovery_output( - "notice\r\nAPPID:Claude_pzs8sxrjxfjjc!Claude\r\n", - ) - .unwrap(); - match target { - ClaudeDesktopTarget::WindowsAppId(app_id) => { - assert_eq!(app_id, "Claude_pzs8sxrjxfjjc!Claude"); - } - ClaudeDesktopTarget::Application(_) => panic!("expected Store application ID"), - } - assert!(parse_windows_claude_desktop_discovery_output("no app found\r\n").is_none()); - assert_eq!( - parse_windows_claude_desktop_version_output("VERSION:1.24012.9.0\r\n").as_deref(), - Some("1.24012.9.0") - ); - assert!(parse_windows_claude_desktop_version_output("VERSION:\r\n").is_none()); - assert!(parse_windows_claude_desktop_version_output("VERSION:unknown\r\n").is_none()); - assert_eq!( - parse_windows_codex_version_output("VERSION:26.727.6591.0\r\n").as_deref(), - Some("26.727.6591.0") - ); - assert!(parse_windows_codex_version_output("VERSION:\r\n").is_none()); - assert!(parse_windows_codex_version_output("VERSION:unknown\r\n").is_none()); - } - #[test] fn thinking_alias_adds_fork_and_matching_payload_rule() { let input = "# Keep this comment\ndebug: true\npayload:\n override:\n - models:\n - name: existing-fast\n protocol: codex\n params:\n service_tier: priority\n"; @@ -23722,9 +22690,6 @@ custom_option = "keep-original" proxy_url: "socks5://127.0.0.1:7890".to_string(), routing_session_affinity: true, routing_session_affinity_ttl: "1h".to_string(), - codex_session_repair_on_launch: false, - claude_code_working_directory: String::new(), - claude_code_working_directory_prompt_disabled: false, }; let merged = merge_core_config_yaml(template, Some(current), &config).unwrap(); @@ -24309,20 +23274,6 @@ custom_option = "keep-original" } } - #[test] - fn codex_launch_is_independent_from_managed_configuration_state() { - assert!(validate_agent_launch_modification(AgentClient::Codex, true, "applied").is_ok()); - assert!( - validate_agent_launch_modification(AgentClient::Codex, false, "unconfigured").is_ok() - ); - assert!(validate_agent_launch_modification(AgentClient::Codex, true, "invalid",).is_ok()); - assert!( - validate_agent_launch_modification(AgentClient::OpenCode, false, "unconfigured") - .is_err() - ); - assert!(validate_agent_launch_modification(AgentClient::OpenCode, true, "applied").is_ok()); - } - #[test] fn replacing_a_core_preserves_only_regular_bundled_assets() { let root = agent_test_home("bundled-assets"); diff --git a/src-tauri/src/usage.rs b/src-tauri/src/usage.rs index de6a86a..6da5bff 100644 --- a/src-tauri/src/usage.rs +++ b/src-tauri/src/usage.rs @@ -2201,9 +2201,6 @@ mod tests { proxy_url: String::new(), routing_session_affinity: false, routing_session_affinity_ttl: String::new(), - codex_session_repair_on_launch: false, - claude_code_working_directory: String::new(), - claude_code_working_directory_prompt_disabled: false, }; let record = normalize_usage_record( serde_json::json!({ diff --git a/src/i18n/ja.ts b/src/i18n/ja.ts index cca3a25..b3c9915 100644 --- a/src/i18n/ja.ts +++ b/src/i18n/ja.ts @@ -499,11 +499,6 @@ export const jaOverrides = { 'agents.sessions.databaseMissing': 'セッションデータベースが見つかりません', 'agents.sessions.repairTitle': '過去のセッションを復元', 'agents.sessions.repairDescription': '他の Provider のセッションを現在のセッション Provider に同期します。', - 'agents.sessions.autoRepair': '過去のセッションを自動復元', - 'agents.sessions.settingLoading': '読み込み中', - 'agents.sessions.settingOn': 'オン', - 'agents.sessions.settingOff': 'オフ', - 'agents.sessions.settingLoadFailed': '自動復元設定の読み込みに失敗しました:{error}', 'agents.sessions.repairNow': '今すぐ復元', 'agents.sessions.repairing': '復元中', 'agents.sessions.loadFailed': 'Codex セッションの読み込みに失敗しました:{error}', @@ -535,7 +530,6 @@ export const jaOverrides = { 'agents.sessions.deleteResult': '{deleted} 件を削除、{failed} 件失敗。{details}{backup}', 'agents.sessions.deleteFailed': 'セッションの削除に失敗しました:{error}', 'agents.sessions.backupAt': 'バックアップ先:{path}', - 'agents.sessions.settingFailed': '自動復元設定の保存に失敗しました:{error}', 'agents.sessions.repairResult': '過去のセッションを {provider} に復元しました:rollout {files} ファイルを移行、データベース {rows} 行を更新、使用中の {skipped} ファイルをスキップしました。', 'agents.sessions.repairFailed': '過去のセッションの復元に失敗しました:{error}', 'agents.sessions.progressCount': '{processed}/{total}', @@ -574,29 +568,17 @@ export const jaOverrides = { 'agents.claudeDesktopMapping.title': 'Claude Desktop モデル → 上流モデル', 'agents.claudeDesktopMapping.description': '各 Claude Desktop モデルが実際に呼び出す上流モデルを選択します。', 'agents.claudeCodeMapping.title': 'Claude Code モデル → 上流モデル', - 'agents.claudeCodeMapping.description': '各タスク役割に対して、起動時に設定する上流モデルを選択します。', + 'agents.claudeCodeMapping.description': '各タスク役割に対して使用する上流モデルを選択します。', 'agents.claudeDesktopMapping.customMapping': 'カスタムマッピング', 'agents.claudeDesktopMapping.customMappingHint': '有効にするとエイリアスモデルのみ、無効にするとエイリアス未設定の元モデルのみ表示します。', 'agents.claudeDesktopMapping.opus': 'Claude Opus 5 のマッピング先', 'agents.claudeDesktopMapping.sonnet': 'Claude Sonnet 4.6 のマッピング先', 'agents.claudeDesktopMapping.haiku': 'Claude Haiku 4.5 のマッピング先', - 'agents.claudeCodeLaunch.noDirectory': '未選択', - 'agents.claudeCodeLaunch.chooseDirectory': 'ディレクトリを選択', - 'agents.claudeCodeLaunch.selectedDirectory': '選択した作業ディレクトリ', - 'agents.claudeCodeLaunch.choosing': '選択中', - 'agents.claudeCodeLaunch.dialogTitle': '{client} の起動ディレクトリを選択', - 'agents.claudeCodeLaunch.dialogDescription': '{client} を起動する前に、作業ディレクトリを選択してください。', - 'agents.claudeCodeLaunch.workingDirectory': '作業ディレクトリ', - 'agents.claudeCodeLaunch.browse': '参照', - 'agents.claudeCodeLaunch.neverAskAgain': '今後この確認を表示しない', - 'agents.claudeCodeLaunch.directoryRequired': '先に作業ディレクトリを選択してください', - 'agents.claudeCodeLaunch.launch': '{client} を起動', 'agents.oauthLoginRequired.title': 'Codex へのログインが必要です', 'agents.oauthLoginRequired.enableDescription': '有効な Codex のログイン認証情報が見つからないため、OAuth 設定方式を有効にできません。Codex アプリで ChatGPT アカウントにログインしてください。Codex アプリでログインを開始できない場合は、先に', 'agents.oauthLoginRequired.enableClearConfiguration': '設定を消去', 'agents.oauthLoginRequired.enableDescriptionSuffix': 'を使用してください。', 'agents.oauthLoginRequired.applyDescription': '有効な Codex のログイン認証情報が見つからないため、OAuth 設定を適用できません。Codex で ChatGPT アカウントにログインしてから、設定を適用してください。', - 'agents.oauthLoginRequired.launchDescription': 'OAuth 設定方式が有効ですが、有効な Codex のログイン認証情報が見つかりません。起動する前に Codex で ChatGPT アカウントにログインしてください。', 'agents.oauthLoginRequired.confirm': '了解しました', 'agents.modify.apply': '設定変更を適用', 'agents.modify.update': '設定を更新', @@ -604,15 +586,6 @@ export const jaOverrides = { 'agents.modify.default': 'デフォルト設定', 'agents.modify.clear': '設定を消去', 'agents.modify.invalid': '現在の設定を解析できません。「設定変更を適用」は最小の有効な設定を再構築し、「デフォルト設定」はファイル全体をリセットします。', - 'agents.launch.enableFirst': '先に設定変更を適用してください', - 'agents.launch.applyFirst': '先に選択中のモデルを適用してください', - 'agents.launch.starting': '起動中', - 'agents.launch.startCli': 'CLI を起動', - 'agents.launch.startApp': 'App を起動', - 'agents.launch.start': '{target} を起動', - 'agents.launch.unavailable': '起動できません', - 'agents.launch.closeChatgpt': 'ChatGPT App を終了', - 'agents.launch.closingChatgpt': '終了中', 'agents.pi.installTitle': 'Pi CLIProxyAPI プロバイダー', 'agents.pi.installDescription': 'プラグインをインストールまたは更新し、CPA 認証情報、既定のプロバイダー、既定のモデルを修復します', 'agents.pi.install': 'Pi プラグインをインストール', @@ -631,11 +604,6 @@ export const jaOverrides = { 'agents.clear.description': 'Codex 設定ディレクトリの auth.json と config.toml を完全に削除し、このアプリが記録した適用状態をリセットします。バックアップは作成されず、元に戻せません。', 'agents.clear.confirm': '設定を消去', 'agents.clear.success': 'Codex 設定を消去しました。', - 'agents.closeApp.title': 'ChatGPT App を終了', - 'agents.closeApp.description': 'このコンピューターで実行中の ChatGPT App プロセスとその子プロセスをすべて終了します。生成中の応答や未保存の状態が失われる可能性があります。', - 'agents.closeApp.confirm': 'App を終了', - 'agents.closeApp.success': 'ChatGPT App を終了しました({count} プロセス)。', - 'agents.closeApp.notRunning': 'ChatGPT App は実行されていません。', 'apiAccess.compatibleName': 'OpenAI 互換 {number}', 'apiAccess.error.headerMissingColon': 'カスタムヘッダーの {number} 行目にコロンがありません', 'apiAccess.error.headerInvalid': 'カスタムヘッダーの {number} 行目の形式が無効です', diff --git a/src/i18n/resources.ts b/src/i18n/resources.ts index f6a8f09..f6fcdb4 100644 --- a/src/i18n/resources.ts +++ b/src/i18n/resources.ts @@ -502,11 +502,6 @@ export const zhCN = { 'agents.sessions.databaseMissing': '未发现会话数据库', 'agents.sessions.repairTitle': '恢复历史会话', 'agents.sessions.repairDescription': '将其他渠道的会话同步到当前会话', - 'agents.sessions.autoRepair': '自动恢复历史会话', - 'agents.sessions.settingLoading': '读取中', - 'agents.sessions.settingOn': '已开启', - 'agents.sessions.settingOff': '已关闭', - 'agents.sessions.settingLoadFailed': '读取自动恢复设置失败:{error}', 'agents.sessions.repairNow': '立即恢复', 'agents.sessions.repairing': '正在恢复', 'agents.sessions.loadFailed': '读取 Codex 会话失败:{error}', @@ -538,7 +533,6 @@ export const zhCN = { 'agents.sessions.deleteResult': '已删除 {deleted} 个,失败 {failed} 个。{details}{backup}', 'agents.sessions.deleteFailed': '删除会话失败:{error}', 'agents.sessions.backupAt': '备份位置:{path}', - 'agents.sessions.settingFailed': '保存自动恢复设置失败:{error}', 'agents.sessions.repairResult': '历史会话已恢复到 {provider}:迁移 {files} 个 rollout 文件、更新 {rows} 条数据库记录、跳过 {skipped} 个占用文件。', 'agents.sessions.repairFailed': '恢复历史会话失败:{error}', 'agents.sessions.progressCount': '{processed}/{total}', @@ -572,23 +566,12 @@ export const zhCN = { 'agents.claudeDesktopMapping.title': 'Claude Desktop 模型 → 上游模型', 'agents.claudeDesktopMapping.description': '选择每个 Claude Desktop 模型实际调用的上游模型。', 'agents.claudeCodeMapping.title': 'Claude Code 模型 → 上游模型', - 'agents.claudeCodeMapping.description': '为不同任务角色选择启动时注入的上游模型。', + 'agents.claudeCodeMapping.description': '为不同任务角色选择使用的上游模型。', 'agents.claudeDesktopMapping.customMapping': '自定义映射', 'agents.claudeDesktopMapping.customMappingHint': '开启后只显示别名模型;关闭后只显示未设置别名的原始模型。', 'agents.claudeDesktopMapping.opus': 'Claude Opus 5 映射到', 'agents.claudeDesktopMapping.sonnet': 'Claude Sonnet 4.6 映射到', 'agents.claudeDesktopMapping.haiku': 'Claude Haiku 4.5 映射到', - 'agents.claudeCodeLaunch.noDirectory': '尚未选择', - 'agents.claudeCodeLaunch.chooseDirectory': '选择目录', - 'agents.claudeCodeLaunch.selectedDirectory': '已选择的工作目录', - 'agents.claudeCodeLaunch.choosing': '选择中', - 'agents.claudeCodeLaunch.dialogTitle': '选择 {client} 启动目录', - 'agents.claudeCodeLaunch.dialogDescription': '需要先选择工作目录,然后才能启动 {client}。', - 'agents.claudeCodeLaunch.workingDirectory': '工作目录', - 'agents.claudeCodeLaunch.browse': '浏览', - 'agents.claudeCodeLaunch.neverAskAgain': '永久关闭此提示', - 'agents.claudeCodeLaunch.directoryRequired': '请先选择工作目录', - 'agents.claudeCodeLaunch.launch': '启动 {client}', 'agents.model.readingAvailable': '正在读取可用模型', 'agents.model.current': '当前已应用:{model}', 'agents.model.firstSelection': '{count} 个可用模型,首次默认选择第一项', @@ -599,7 +582,6 @@ export const zhCN = { 'agents.oauthLoginRequired.enableClearConfiguration': '清空配置', 'agents.oauthLoginRequired.enableDescriptionSuffix': '功能。', 'agents.oauthLoginRequired.applyDescription': '无法应用 OAuth 配置:未检测到有效的 Codex 登录凭据。请先在 Codex 中登录 ChatGPT 账号,然后再应用配置。', - 'agents.oauthLoginRequired.launchDescription': '已开启 OAuth 配置模式,但未检测到有效的 Codex 登录凭据。请先在 Codex 中登录 ChatGPT 账号,然后再启动。', 'agents.oauthLoginRequired.confirm': '我知道了', 'agents.modify.apply': '应用配置修改', 'agents.modify.update': '更新配置', @@ -607,15 +589,6 @@ export const zhCN = { 'agents.modify.default': '默认配置', 'agents.modify.clear': '清空配置', 'agents.modify.invalid': '当前配置无法解析。点击“应用配置修改”会重建最小合法配置;“默认配置”会重置整个文件。', - 'agents.launch.enableFirst': '请先应用配置修改', - 'agents.launch.applyFirst': '请先应用当前模型选择', - 'agents.launch.starting': '启动中', - 'agents.launch.startCli': '启动 CLI', - 'agents.launch.startApp': '启动 App', - 'agents.launch.start': '启动 {target}', - 'agents.launch.unavailable': '无法启动', - 'agents.launch.closeChatgpt': '关闭 ChatGPT App', - 'agents.launch.closingChatgpt': '关闭中', 'agents.pi.installTitle': 'Pi CLIProxyAPI provider', 'agents.pi.installDescription': '安装或更新插件,并修复 CPA 凭据、默认 provider 与默认模型', 'agents.pi.install': '安装 Pi 插件', @@ -634,11 +607,6 @@ export const zhCN = { 'agents.clear.description': '这会永久删除 Codex 配置目录中的 auth.json 和 config.toml,并重置软件记录的应用状态。该操作没有备份且无法恢复。', 'agents.clear.confirm': '确认清空配置', 'agents.clear.success': 'Codex 配置已清空。', - 'agents.closeApp.title': '关闭 ChatGPT App', - 'agents.closeApp.description': '这会关闭本机所有正在运行的 ChatGPT App 进程及其子进程。正在生成的回复或尚未保存的状态可能丢失。', - 'agents.closeApp.confirm': '确认关闭 App', - 'agents.closeApp.success': 'ChatGPT App 已关闭({count} 个进程)。', - 'agents.closeApp.notRunning': 'ChatGPT App 当前没有运行。', 'apiAccess.compatibleName': 'OpenAI 兼容 {number}', 'apiAccess.error.headerMissingColon': '自定义请求头第 {number} 行缺少冒号', 'apiAccess.error.headerInvalid': '自定义请求头第 {number} 行格式无效', @@ -1463,11 +1431,6 @@ export const en: Record = { 'agents.sessions.databaseMissing': 'No session database found', 'agents.sessions.repairTitle': 'Restore Historical Sessions', 'agents.sessions.repairDescription': 'Sync sessions from other providers to the current session provider.', - 'agents.sessions.autoRepair': 'Automatically restore historical sessions', - 'agents.sessions.settingLoading': 'Loading', - 'agents.sessions.settingOn': 'On', - 'agents.sessions.settingOff': 'Off', - 'agents.sessions.settingLoadFailed': 'Failed to load the automatic-restoration setting: {error}', 'agents.sessions.repairNow': 'Restore now', 'agents.sessions.repairing': 'Restoring', 'agents.sessions.loadFailed': 'Failed to load Codex sessions: {error}', @@ -1499,7 +1462,6 @@ export const en: Record = { 'agents.sessions.deleteResult': 'Deleted {deleted}; failed {failed}. {details}{backup}', 'agents.sessions.deleteFailed': 'Failed to delete sessions: {error}', 'agents.sessions.backupAt': 'Backup: {path}', - 'agents.sessions.settingFailed': 'Failed to save the automatic-restoration setting: {error}', 'agents.sessions.repairResult': 'Historical sessions restored to {provider}: migrated {files} rollout files, updated {rows} database rows, and skipped {skipped} locked files.', 'agents.sessions.repairFailed': 'Failed to restore historical sessions: {error}', 'agents.sessions.progressCount': '{processed}/{total}', @@ -1533,23 +1495,12 @@ export const en: Record = { 'agents.claudeDesktopMapping.title': 'Claude Desktop Model → Upstream Model', 'agents.claudeDesktopMapping.description': 'Choose the upstream model each Claude Desktop model actually calls.', 'agents.claudeCodeMapping.title': 'Claude Code Model → Upstream Model', - 'agents.claudeCodeMapping.description': 'Choose the upstream model injected at launch for each task role.', + 'agents.claudeCodeMapping.description': 'Choose the upstream model used for each task role.', 'agents.claudeDesktopMapping.customMapping': 'Custom Mapping', 'agents.claudeDesktopMapping.customMappingHint': 'When enabled, only aliased models are shown; when disabled, only original models without aliases are shown.', 'agents.claudeDesktopMapping.opus': 'Map Claude Opus 5 to', 'agents.claudeDesktopMapping.sonnet': 'Map Claude Sonnet 4.6 to', 'agents.claudeDesktopMapping.haiku': 'Map Claude Haiku 4.5 to', - 'agents.claudeCodeLaunch.noDirectory': 'Not selected', - 'agents.claudeCodeLaunch.chooseDirectory': 'Choose Directory', - 'agents.claudeCodeLaunch.selectedDirectory': 'Selected working directory', - 'agents.claudeCodeLaunch.choosing': 'Choosing', - 'agents.claudeCodeLaunch.dialogTitle': 'Choose a Launch Directory for {client}', - 'agents.claudeCodeLaunch.dialogDescription': 'Select a working directory before starting {client}.', - 'agents.claudeCodeLaunch.workingDirectory': 'Working directory', - 'agents.claudeCodeLaunch.browse': 'Browse', - 'agents.claudeCodeLaunch.neverAskAgain': 'Never show this prompt again', - 'agents.claudeCodeLaunch.directoryRequired': 'Select a working directory first', - 'agents.claudeCodeLaunch.launch': 'Start {client}', 'agents.model.readingAvailable': 'Loading available models', 'agents.model.current': 'Currently applied: {model}', 'agents.model.firstSelection': '{count} models available; the first is selected by default', @@ -1560,7 +1511,6 @@ export const en: Record = { 'agents.oauthLoginRequired.enableClearConfiguration': 'Clear Configuration', 'agents.oauthLoginRequired.enableDescriptionSuffix': 'first.', 'agents.oauthLoginRequired.applyDescription': 'OAuth configuration cannot be applied because no valid Codex sign-in credentials were found. Sign in to your ChatGPT account in Codex, then apply the configuration.', - 'agents.oauthLoginRequired.launchDescription': 'OAuth configuration mode is enabled, but no valid Codex sign-in credentials were found. Sign in to your ChatGPT account in Codex before launching it.', 'agents.oauthLoginRequired.confirm': 'Got it', 'agents.modify.apply': 'Apply Configuration Changes', 'agents.modify.update': 'Update Configuration', @@ -1568,15 +1518,6 @@ export const en: Record = { 'agents.modify.default': 'Default Configuration', 'agents.modify.clear': 'Clear Configuration', 'agents.modify.invalid': 'The current configuration cannot be parsed. Apply Configuration Changes rebuilds a minimal valid configuration; Default Configuration resets the whole file.', - 'agents.launch.enableFirst': 'Apply the configuration changes first', - 'agents.launch.applyFirst': 'Apply the selected model first', - 'agents.launch.starting': 'Starting', - 'agents.launch.startCli': 'Start CLI', - 'agents.launch.startApp': 'Start App', - 'agents.launch.start': 'Start {target}', - 'agents.launch.unavailable': 'Cannot Start', - 'agents.launch.closeChatgpt': 'Close ChatGPT App', - 'agents.launch.closingChatgpt': 'Closing', 'agents.pi.installTitle': 'Pi CLIProxyAPI provider', 'agents.pi.installDescription': 'Install or update the plugin and repair CPA credentials, the default provider, and the default model.', 'agents.pi.install': 'Install Pi Plugin', @@ -1595,11 +1536,6 @@ export const en: Record = { 'agents.clear.description': 'This permanently deletes auth.json and config.toml from the Codex configuration directory and resets the application state tracked by this app. No backup is created and the operation cannot be undone.', 'agents.clear.confirm': 'Clear Configuration', 'agents.clear.success': 'The Codex configuration has been cleared.', - 'agents.closeApp.title': 'Close ChatGPT App', - 'agents.closeApp.description': 'This closes all running ChatGPT App processes and their child processes on this computer. In-progress responses or unsaved state may be lost.', - 'agents.closeApp.confirm': 'Close App', - 'agents.closeApp.success': 'ChatGPT App was closed ({count} processes).', - 'agents.closeApp.notRunning': 'ChatGPT App is not running.', 'apiAccess.compatibleName': 'OpenAI Compatible {number}', 'apiAccess.error.headerMissingColon': 'Custom header line {number} is missing a colon', 'apiAccess.error.headerInvalid': 'Custom header line {number} is invalid', diff --git a/src/pages/AgentsPage.tsx b/src/pages/AgentsPage.tsx index 5725632..e7ac7f6 100644 --- a/src/pages/AgentsPage.tsx +++ b/src/pages/AgentsPage.tsx @@ -10,20 +10,15 @@ import { } from 'react'; import { invoke } from '@tauri-apps/api/core'; import { listen } from '@tauri-apps/api/event'; -import { open } from '@tauri-apps/plugin-dialog'; import { AlertTriangle, - AppWindow, BadgeCheck, Bot, Check, ChevronDown, LoaderCircle, - Play, - Power, RefreshCw, Search, - Terminal, Trash2, Wrench, X, @@ -49,7 +44,6 @@ import { } from '../services/agentConfigurationDraft'; import type { ModelOption } from '../services/modelService'; import { getCurrentLocale, translate, useI18n } from '../i18n'; -import { CodexSessionAutoRestoreCard } from './CodexSessionAutoRestoreCard'; import { CodexSessionsPanel } from './CodexSessionsPanel'; type AgentClientId = @@ -69,8 +63,6 @@ type AgentConfigStatus = { supportedPlatform: boolean; installed: boolean; pluginInstalled: boolean; - executablePath: string | null; - launchTargets: AgentLaunchTarget[]; version: string | null; cliVersion: string | null; appVersion: string | null; @@ -85,18 +77,10 @@ type AgentConfigStatus = { appliedModel: string | null; claudeCodeModelMappings: ClaudeModelMappings | null; claudeDesktopModelMappings: ClaudeModelMappings | null; - claudeCodeWorkingDirectory: string | null; - claudeCodeWorkingDirectoryPromptDisabled: boolean; warnings: string[]; error: string | null; }; -type AgentLaunchTarget = { - id: string; - label: string; - detail: string; -}; - type AgentConfigActionResult = { outcome: 'applied' | 'default'; enabled: boolean; @@ -111,12 +95,7 @@ type PiProviderUpdateStatus = { updateAvailable: boolean; }; -type ChatGptCloseResult = { - wasRunning: boolean; - closedProcesses: number; -}; - -type OAuthLoginRequiredAction = 'enable' | 'apply' | 'launch'; +type OAuthLoginRequiredAction = 'enable' | 'apply'; type ClaudeModelMappings = { opus: string; @@ -555,30 +534,21 @@ export function AgentsPage() { createClaudeModelMappings(''), ); const [claudeCustomMapping, setClaudeCustomMapping] = useState(false); - const [launchDirectoryDialogOpen, setLaunchDirectoryDialogOpen] = useState(false); - const [launchDirectory, setLaunchDirectory] = useState(''); - const [launchDirectoryTarget, setLaunchDirectoryTarget] = useState(null); - const [suppressLaunchDirectoryPrompt, setSuppressLaunchDirectoryPrompt] = useState(false); - const [launchDirectoryError, setLaunchDirectoryError] = useState(''); const [loading, setLoading] = useState(true); const [modelLoading, setModelLoading] = useState(false); const [busyAction, setBusyAction] = useState< - 'apply' | 'close-config' | 'default' | 'clear' | 'install-pi' | 'update-pi' | 'repair-pi' | 'uninstall-pi' | 'launch' | 'launch-cli' | 'launch-app' | 'close-app' | 'oauth-check' | 'directory' | null + 'apply' | 'close-config' | 'default' | 'clear' | 'install-pi' | 'update-pi' | 'repair-pi' | 'uninstall-pi' | 'oauth-check' | null >(null); const busy = busyAction !== null; const [detectionError, setDetectionError] = useState(''); const [modelError, setModelError] = useState(''); const [modelSelectionError, setModelSelectionError] = useState(''); const [configurationError, setConfigurationError] = useState(''); - const [launchError, setLaunchError] = useState(''); const [defaultError, setDefaultError] = useState(''); const [defaultConfirmOpen, setDefaultConfirmOpen] = useState(false); const [clearError, setClearError] = useState(''); const [clearNotice, setClearNotice] = useState(''); const [clearConfirmOpen, setClearConfirmOpen] = useState(false); - const [closeAppError, setCloseAppError] = useState(''); - const [closeAppNotice, setCloseAppNotice] = useState(''); - const [closeAppConfirmOpen, setCloseAppConfirmOpen] = useState(false); const [oauthLoginRequiredAction, setOauthLoginRequiredAction] = useState(null); const [oauthConfigurationDraft, setOauthConfigurationDraft] = useState(null); const [piProviderUpdateStatus, setPiProviderUpdateStatus] = useState(null); @@ -673,20 +643,13 @@ export function AgentsPage() { setActiveSubpage(DEFAULT_AGENT_SUBPAGE); setModelSelectionError(''); setConfigurationError(''); - setLaunchError(''); setDefaultError(''); setDefaultConfirmOpen(false); setClearError(''); setClearNotice(''); setClearConfirmOpen(false); - setCloseAppError(''); - setCloseAppNotice(''); - setCloseAppConfirmOpen(false); setOauthLoginRequiredAction(null); setOauthConfigurationDraft(null); - setLaunchDirectoryDialogOpen(false); - setLaunchDirectoryTarget(null); - setLaunchDirectoryError(''); claudeModelMappingsDirtyRef.current = false; }, [selected]); @@ -768,10 +731,6 @@ export function AgentsPage() { selectedModel, ]); - const activeLaunchTargets = activeStatus?.launchTargets ?? []; - const defaultLaunchTarget = activeLaunchTargets[0] ?? null; - const cliLaunchTarget = activeLaunchTargets.find((target) => target.id === 'cli') ?? null; - const appLaunchTarget = activeLaunchTargets.find((target) => target.id === 'app') ?? null; const appliedModel = activeStatus?.appliedModel ?? activeStatus?.currentModel ?? ''; const modelDraftChanged = !isClaudeModelMappingClient && Boolean( selectedModel.trim() @@ -813,19 +772,6 @@ export function AgentsPage() { ? claudeMappingsReady : selectedModelOption), ); - const launchEnabled = Boolean( - activeStatus?.supportedPlatform - && activeStatus.installed - && (selected === 'codex' - || (activeStatus.modificationEnabled && activeStatus.modificationState === 'applied')), - ); - const canLaunchTarget = (target: AgentLaunchTarget | null) => launchEnabled && Boolean(target); - const requiresLaunchDirectory = (target: AgentLaunchTarget | null) => Boolean( - target - && (selected === 'claude-code' - || selected === 'pi' - || (selected === 'codex' && target.id === 'cli')), - ); const modelHint = modelSelectionError || modelError || (modelLoading @@ -1100,130 +1046,6 @@ export function AgentsPage() { } }; - const openLaunchDirectoryDialog = (target: AgentLaunchTarget) => { - setLaunchDirectory( - selected === 'claude-code' ? activeStatus?.claudeCodeWorkingDirectory ?? '' : '', - ); - setLaunchDirectoryTarget(target); - setSuppressLaunchDirectoryPrompt(false); - setLaunchDirectoryError(''); - setLaunchDirectoryDialogOpen(true); - }; - - const chooseLaunchDirectory = async () => { - setBusyAction('directory'); - setLaunchDirectoryError(''); - try { - const selectedDirectory = await open({ - directory: true, - multiple: false, - defaultPath: launchDirectory || activeStatus?.claudeCodeWorkingDirectory || undefined, - title: t('agents.claudeCodeLaunch.dialogTitle', { client: activeDefinition.name }), - }); - if (typeof selectedDirectory === 'string') { - setLaunchDirectory(selectedDirectory); - } - } catch (requestError) { - setLaunchDirectoryError(String(requestError)); - } finally { - setBusyAction(null); - } - }; - - const invokeAgentLaunch = async ( - target: AgentLaunchTarget, - workingDirectory: string | null = null, - suppressWorkingDirectoryPrompt: boolean | null = null, - ) => { - if (!target) return; - const launchAction = selected === 'codex' - ? target.id === 'cli' ? 'launch-cli' : 'launch-app' - : 'launch'; - setBusyAction(launchAction); - setLaunchError(''); - try { - if (draftChanged) { - throw new Error(t('agents.error.applyFirst')); - } - await invoke('launch_agent', { - client: selected, - target: target.id, - workingDirectory, - suppressWorkingDirectoryPrompt, - }); - if (requiresLaunchDirectory(target) && workingDirectory) { - setLaunchDirectoryDialogOpen(false); - setLaunchDirectoryTarget(null); - if (selected === 'claude-code') { - await reloadStatusesAfterAction(); - } - } - } catch (requestError) { - const message = String(requestError); - if ( - selected === 'claude-code' - && (message.includes('CLAUDE_CODE_WORKING_DIRECTORY_REQUIRED') - || message.includes('CLAUDE_CODE_WORKING_DIRECTORY_INVALID')) - ) { - openLaunchDirectoryDialog(target); - setLaunchDirectoryError( - message.includes('CLAUDE_CODE_WORKING_DIRECTORY_INVALID') ? message : '', - ); - } else if (requiresLaunchDirectory(target) && workingDirectory) { - setLaunchDirectoryError(message); - } else if (!handleOAuthLoginError(requestError, 'launch')) { - setLaunchError(message); - } - } finally { - setBusyAction(null); - } - }; - - const launchAgent = async (target: AgentLaunchTarget | null) => { - if (!target) return; - if ( - requiresLaunchDirectory(target) - && !(selected === 'claude-code' && activeStatus?.claudeCodeWorkingDirectoryPromptDisabled) - ) { - openLaunchDirectoryDialog(target); - return; - } - await invokeAgentLaunch(target); - }; - - const launchFromDirectoryDialog = async () => { - const target = launchDirectoryTarget; - if (!target) return; - const workingDirectory = launchDirectory.trim(); - if (!workingDirectory) { - setLaunchDirectoryError(t('agents.claudeCodeLaunch.directoryRequired')); - return; - } - setLaunchDirectoryError(''); - await invokeAgentLaunch( - target, - workingDirectory, - selected === 'claude-code' ? suppressLaunchDirectoryPrompt : null, - ); - }; - - const closeChatGptApp = async () => { - setBusyAction('close-app'); - setCloseAppError(''); - setCloseAppNotice(''); - try { - const result = await invoke('close_chatgpt_app'); - setCloseAppConfirmOpen(false); - setCloseAppNotice(result.wasRunning - ? t('agents.closeApp.success', { count: result.closedProcesses }) - : t('agents.closeApp.notRunning')); - } catch (requestError) { - setCloseAppError(String(requestError)); - } finally { - setBusyAction(null); - } - }; - const openDefaultConfirmation = () => { setDefaultError(''); setDefaultConfirmOpen(true); @@ -1244,16 +1066,6 @@ export function AgentsPage() { setClearConfirmOpen(false); }; - const openCloseAppConfirmation = () => { - setCloseAppError(''); - setCloseAppConfirmOpen(true); - }; - - const closeCloseAppConfirmation = () => { - setCloseAppError(''); - setCloseAppConfirmOpen(false); - }; - const availableSubpages = agentSubpages.filter( (subpage) => !subpage.clients || subpage.clients.includes(selected), ); @@ -1534,7 +1346,6 @@ export function AgentsPage() {
{selected === 'codex' ? (
-
) : null} @@ -1700,91 +1439,6 @@ export function AgentsPage() {
- {launchDirectoryDialogOpen ? ( -
{ - if (event.currentTarget === event.target && !busy) { - setLaunchDirectoryDialogOpen(false); - setLaunchDirectoryTarget(null); - } - }}> -
-
-
-

- {t('agents.claudeCodeLaunch.dialogTitle', { client: activeDefinition.name })} -

-
-
-

{t('agents.claudeCodeLaunch.dialogDescription', { client: activeDefinition.name })}

-
- {t('agents.claudeCodeLaunch.workingDirectory')} - -
- {selected === 'claude-code' ? ( - - ) : null} - {launchDirectoryError ? ( - - {launchDirectoryError} - - ) : null} -
- - -
-
-
- ) : null} - {defaultConfirmOpen ? (
@@ -1833,29 +1487,6 @@ export function AgentsPage() {
) : null} - {closeAppConfirmOpen ? ( -
-
-
-

{t('agents.closeApp.title')}

-
-

{t('agents.closeApp.description')}

- {closeAppError ? ( - - {closeAppError} - - ) : null} -
- - -
-
-
- ) : null} - {oauthLoginRequiredAction ? (
diff --git a/src/pages/CodexSessionAutoRestoreCard.tsx b/src/pages/CodexSessionAutoRestoreCard.tsx deleted file mode 100644 index 18600b7..0000000 --- a/src/pages/CodexSessionAutoRestoreCard.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import { useEffect, useState } from 'react'; -import { invoke } from '@tauri-apps/api/core'; -import { LoaderCircle } from 'lucide-react'; -import { useI18n } from '../i18n'; - -type RepairOnLaunchSetting = { - enabled: boolean; -}; - -type StatusMessage = { - text: string; -}; - -function errorMessage(error: unknown): string { - return error instanceof Error ? error.message : String(error); -} - -export function CodexSessionAutoRestoreCard() { - const { t } = useI18n(); - const [enabled, setEnabled] = useState(false); - const [loading, setLoading] = useState(true); - const [saving, setSaving] = useState(false); - const [status, setStatus] = useState(null); - - useEffect(() => { - let disposed = false; - void invoke('get_codex_session_repair_on_launch') - .then((result) => { - if (!disposed) setEnabled(result.enabled); - }) - .catch((error) => { - if (!disposed) { - setStatus({ - text: t('agents.sessions.settingLoadFailed', { error: errorMessage(error) }), - }); - } - }) - .finally(() => { - if (!disposed) setLoading(false); - }); - return () => { - disposed = true; - }; - }, [t]); - - const updateSetting = async (nextEnabled: boolean) => { - setSaving(true); - setStatus(null); - try { - const result = await invoke('set_codex_session_repair_on_launch', { - request: { enabled: nextEnabled }, - }); - setEnabled(result.enabled); - } catch (error) { - setStatus({ - text: t('agents.sessions.settingFailed', { error: errorMessage(error) }), - }); - } finally { - setSaving(false); - } - }; - - return ( -
-
-
- {t('agents.sessions.autoRepair')} -
- -
- {status ? ( - - {status.text} - - ) : null} -
- ); -} diff --git a/src/services/codexSessionState.ts b/src/services/codexSessionState.ts index 20c99ce..dd3b4a7 100644 --- a/src/services/codexSessionState.ts +++ b/src/services/codexSessionState.ts @@ -15,7 +15,6 @@ export type CodexSessionPage = { offset: number; limit: number; hasMore: boolean; - repairOnLaunch: boolean; warnings: string[]; }; diff --git a/src/styles.css b/src/styles.css index a378c6e..95b87b2 100644 --- a/src/styles.css +++ b/src/styles.css @@ -5361,7 +5361,6 @@ textarea:focus-visible { padding: 0 2px; } -.agent-config-footer, .agent-status-grid span { display: flex; align-items: center; @@ -5372,16 +5371,6 @@ textarea:focus-visible { gap: 14px; } -.agent-launch-actions { - display: flex; - min-width: 0; - max-width: 100%; - align-items: center; - justify-content: flex-end; - gap: 8px; - flex-wrap: wrap; -} - .agent-status-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); @@ -5502,81 +5491,6 @@ textarea:focus-visible { opacity: 0.62; } -.agent-claude-code-launch-dialog { - grid-template-rows: auto; - width: min(620px, calc(100vw - 32px)); - height: auto; - min-height: 0; -} - -.agent-claude-code-launch-dialog > p { - margin: 0; - color: var(--theme-5f5a53); - font-size: var(--font-size-label); - line-height: var(--line-height-relaxed); -} - -.agent-claude-code-directory-picker { - display: flex; - width: 100%; - min-width: 0; - min-height: 64px; - align-items: center; - justify-content: space-between; - gap: 16px; - padding: 11px 13px; - border: 1px solid var(--theme-d8d2c8); - border-radius: 9px; - background: var(--theme-fff); - color: var(--theme-2d2a26); - text-align: left; - transition: border-color 160ms ease, background-color 160ms ease, box-shadow 160ms ease; -} - -.agent-claude-code-directory-picker:hover:not(:disabled) { - border-color: var(--theme-bdb5a9); - background: var(--theme-faf8f4); -} - -.agent-claude-code-directory-picker:focus-visible { - border-color: var(--theme-8d8377); - outline: 0; - box-shadow: 0 0 0 3px rgba(45, 42, 38, 0.1); -} - -.agent-claude-code-directory-picker > span { - display: grid; - min-width: 0; - gap: 4px; -} - -.agent-claude-code-directory-picker small { - color: var(--theme-8a847c); - font-size: var(--font-size-meta); -} - -.agent-claude-code-directory-picker strong { - overflow: hidden; - font-size: var(--font-size-label); - text-overflow: ellipsis; - white-space: nowrap; -} - -.agent-claude-code-directory-picker > b { - flex: none; - color: var(--theme-8069ad); - font-size: var(--font-size-label); -} - -.agent-claude-code-suppress-prompt { - display: flex; - align-items: center; - gap: 8px; - color: var(--theme-5f5a53); - font-size: var(--font-size-label); - cursor: pointer; -} - .agent-section-heading { display: flex; align-items: center; @@ -5809,17 +5723,6 @@ textarea:focus-visible { min-height: 0; } -.codex-auto-restore-card { - height: 100%; - min-height: 54px; - gap: 6px; - padding: 10px 12px; - border-color: var(--theme-d8d2c8); - border-radius: 9px; - background: var(--theme-faf8f4); - transition: border-color 160ms ease, background-color 160ms ease, box-shadow 160ms ease; -} - .agent-codex-options { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); @@ -5841,54 +5744,6 @@ textarea:focus-visible { line-height: var(--line-height-tight); } -.codex-auto-restore-toggle { - display: inline-flex; - flex: none; - align-items: center; - gap: 8px; - cursor: pointer; - user-select: none; -} - -.codex-auto-restore-switch input:focus-visible + .switch-track { - outline: 3px solid var(--ui-focus-ring); - outline-offset: 2px; -} - -.codex-auto-restore-toggle:has(input:disabled) { - cursor: not-allowed; - opacity: 0.55; -} - -.codex-auto-restore-toggle > strong { - min-width: 34px; - color: var(--theme-5f5a53); - font-size: var(--font-size-meta); - font-weight: 600; - text-align: right; -} - -.codex-auto-restore-card.enabled { - border-color: var(--theme-9db8a5); - background: var(--theme-f3f8f4); -} - -.codex-auto-restore-card.enabled .codex-auto-restore-toggle > strong { - color: var(--theme-2f6b3f); -} - -.codex-auto-restore-message { - max-width: 100%; - color: var(--theme-2f6b3f); - font-size: var(--font-size-meta); - line-height: var(--line-height-body); - overflow-wrap: anywhere; -} - -.codex-auto-restore-message.error { - color: var(--theme-9f2d20); -} - .agent-modification-control { display: grid; min-width: 0; @@ -5986,44 +5841,6 @@ textarea:focus-visible { margin-top: -6px; } -.agent-config-footer { - min-height: 48px; - padding-top: 14px; - border-top: 1px solid var(--theme-e3e1db); -} - -.agent-config-footer > .agent-launch-control { - display: grid; - min-width: 0; - max-width: 100%; - justify-items: end; - gap: 5px; - margin-left: auto; -} - -.agent-launch-control > .agent-inline-message { - max-width: 360px; - text-align: right; -} - -.agent-config-footer .primary-button { - min-width: 120px; -} - -.agent-config-footer .agent-close-app-button { - min-width: 120px; -} - -.agent-launch-actions > button { - min-width: 0; - max-width: 100%; - height: auto; - padding-block: 8px; - line-height: var(--line-height-tight); - overflow-wrap: anywhere; - white-space: normal; -} - .usage-records-page { display: grid; gap: 14px; @@ -7306,8 +7123,7 @@ textarea:focus-visible { } .agent-status-grid, - .agent-status-messages, - .agent-config-footer { + .agent-status-messages { grid-column: 1 / -1; } @@ -7324,30 +7140,6 @@ textarea:focus-visible { } @container agent-panel (max-width: 760px) { - .agent-config-footer { - align-items: stretch; - flex-direction: column; - } - - .agent-config-footer > .agent-launch-control { - width: 100%; - justify-items: stretch; - margin-left: 0; - } - - .agent-launch-actions { - display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); - width: 100%; - } - - .agent-launch-actions > .primary-button:last-child { - grid-column: 1 / -1; - } - - .agent-launch-actions > .agent-close-app-button { - grid-column: 1 / -1; - } } @container agent-panel (max-width: 640px) { @@ -7930,43 +7722,16 @@ textarea:focus-visible { height: auto; } - .agent-config-footer, .agent-section-heading { align-items: stretch; flex-direction: column; } - .codex-auto-restore-toggle { - width: 100%; - min-width: 0; - justify-content: space-between; - } - - .codex-auto-restore-toggle > strong { - text-align: left; - } - - .agent-config-footer .primary-button { - width: 100%; - } - - .agent-modification-control > .agent-inline-message, - .agent-launch-control > .agent-inline-message { + .agent-modification-control > .agent-inline-message { max-width: none; text-align: left; } - .agent-config-footer > .agent-launch-control { - width: 100%; - justify-items: stretch; - } - - .agent-launch-actions { - width: 100%; - align-items: stretch; - flex-direction: column; - } - .oauth-card { grid-template-rows: auto auto auto; min-height: 0; diff --git a/tests/i18n.test.ts b/tests/i18n.test.ts index f91fd99..0d04f3e 100644 --- a/tests/i18n.test.ts +++ b/tests/i18n.test.ts @@ -32,20 +32,6 @@ describe('i18n', () => { expect(translate('en', 'kernel.access.openaiDescription')).toBe('OpenAI-compatible format'); }); - it('translates direct Codex launch actions', () => { - expect(translate('zh-CN', 'agents.launch.startCli')).toBe('启动 CLI'); - expect(translate('zh-TW', 'agents.launch.startApp')).toBe('啟動 App'); - expect(translate('ja', 'agents.launch.startCli')).toBe('CLI を起動'); - expect(translate('en', 'agents.launch.startApp')).toBe('Start App'); - }); - - it('uses the concise automatic session restoration label', () => { - expect(translate('zh-CN', 'agents.sessions.autoRepair')).toBe('自动恢复历史会话'); - expect(translate('zh-TW', 'agents.sessions.autoRepair')).toBe('自動恢復歷史工作階段'); - expect(translate('ja', 'agents.sessions.autoRepair')).toBe('過去のセッションを自動復元'); - expect(translate('en', 'agents.sessions.autoRepair')).toBe('Automatically restore historical sessions'); - }); - it('uses each language native name independently of the active locale', () => { expect(languageOptions).toEqual([ { value: 'zh-CN', nativeLabel: '简体中文' },