refactor(agents): remove direct client launch functionality
This commit is contained in:
1 parent
7ee28e10b0
commit
d2f238f829
17 files changed
+62
-2299
No files matched your search
@@ -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
|
||||
|
||||
|
||||
+2
-2
@@ -94,8 +94,8 @@ API 接入页面按照协议或 Provider 管理上游 API 凭证和服务地址
|
||||
- Hermes Agent
|
||||
- Pi(通过 CLIProxyAPI provider 插件)
|
||||
|
||||
对于受支持的客户端,软件可以同步可用模型目录、选择默认模型、在应用托管配置前备份原始配置、
|
||||
恢复之前的配置,以及启动可用的桌面端或命令行入口。
|
||||
对于受支持的客户端,软件可以同步可用模型目录、选择默认模型、在应用托管配置前备份原始配置,
|
||||
以及恢复之前的配置。
|
||||
|
||||
## 其他功能
|
||||
|
||||
|
||||
@@ -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=="],
|
||||
|
||||
@@ -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"
|
||||
|
||||
Generated
+1
-143
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
@@ -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<String>,
|
||||
}
|
||||
|
||||
@@ -153,25 +150,6 @@ pub(crate) struct SessionIndexCleanupResult {
|
||||
backup_path: Option<String>,
|
||||
}
|
||||
|
||||
#[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<ListCodexSessionsRequest>,
|
||||
) -> Result<CodexSessionPage, String> {
|
||||
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<RepairOnLaunchSetting, String> {
|
||||
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<RepairOnLaunchSetting, String> {
|
||||
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<ChatGptCloseResult, String> {
|
||||
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<CodexSessionRepairResult, String> {
|
||||
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<CodexSessionRepairResult, String> {
|
||||
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<CodexSessionPage, String> {
|
||||
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<Vec<u32>, String> {
|
||||
windows_app_process_ids(&["ChatGPT.exe", "Codex.exe"])
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn chatgpt_app_process_ids() -> Result<Vec<u32>, String> {
|
||||
windows_app_process_ids(&["ChatGPT.exe"])
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn windows_app_process_ids(names: &[&str]) -> Result<Vec<u32>, String> {
|
||||
use std::os::windows::process::CommandExt;
|
||||
@@ -2412,11 +2325,6 @@ fn codex_app_process_ids() -> Result<Vec<u32>, String> {
|
||||
unix_app_process_ids(&["ChatGPT", "Codex"])
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
fn chatgpt_app_process_ids() -> Result<Vec<u32>, String> {
|
||||
unix_app_process_ids(&["ChatGPT"])
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
fn unix_app_process_ids(names: &[&str]) -> Result<Vec<u32>, String> {
|
||||
let mut ids = Vec::new();
|
||||
@@ -2445,108 +2353,6 @@ fn unix_app_process_ids(names: &[&str]) -> Result<Vec<u32>, String> {
|
||||
Ok(ids)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn close_chatgpt_app_processes() -> Result<ChatGptCloseResult, String> {
|
||||
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::<Vec<_>>()
|
||||
.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<ChatGptCloseResult, String> {
|
||||
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::<Vec<_>>()
|
||||
.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");
|
||||
|
||||
+43
-1092
File diff suppressed because it is too large.
Load diff
@@ -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!({
|
||||
|
||||
+1
-33
@@ -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} 行目の形式が無効です',
|
||||
|
||||
+2
-66
@@ -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<MessageKey, string> = {
|
||||
'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<MessageKey, string> = {
|
||||
'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<MessageKey, string> = {
|
||||
'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<MessageKey, string> = {
|
||||
'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<MessageKey, string> = {
|
||||
'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<MessageKey, string> = {
|
||||
'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',
|
||||
|
||||
+2
-371
@@ -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<AgentLaunchTarget | null>(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<OAuthLoginRequiredAction | null>(null);
|
||||
const [oauthConfigurationDraft, setOauthConfigurationDraft] = useState<boolean | null>(null);
|
||||
const [piProviderUpdateStatus, setPiProviderUpdateStatus] = useState<PiProviderUpdateStatus | null>(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<ChatGptCloseResult>('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() {
|
||||
<div className="agent-modification-control">
|
||||
{selected === 'codex' ? (
|
||||
<div className="agent-codex-options">
|
||||
<CodexSessionAutoRestoreCard />
|
||||
<label
|
||||
className="agent-oauth-configuration"
|
||||
title={t('agents.modify.oauthConfiguration')}
|
||||
@@ -1612,78 +1423,6 @@ export function AgentsPage() {
|
||||
</section>
|
||||
)}
|
||||
|
||||
<div className="agent-config-footer">
|
||||
<div className="agent-launch-control">
|
||||
<div className="agent-launch-actions">
|
||||
{selected === 'codex' ? (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
className="secondary-button agent-launch-button"
|
||||
onClick={() => void launchAgent(cliLaunchTarget)}
|
||||
disabled={busy || !canLaunchTarget(cliLaunchTarget) || draftChanged}
|
||||
title={draftChanged
|
||||
? t('agents.launch.applyFirst')
|
||||
: cliLaunchTarget?.detail ?? t('agents.launch.unavailable')}
|
||||
>
|
||||
{busyAction === 'launch-cli' ? <LoaderCircle size={16} className="spin" /> : <Terminal size={16} />}
|
||||
{busyAction === 'launch-cli' ? t('agents.launch.starting') : t('agents.launch.startCli')}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="primary-button agent-launch-button"
|
||||
onClick={() => void launchAgent(appLaunchTarget)}
|
||||
disabled={busy || !canLaunchTarget(appLaunchTarget) || draftChanged}
|
||||
title={draftChanged
|
||||
? t('agents.launch.applyFirst')
|
||||
: appLaunchTarget?.detail ?? t('agents.launch.unavailable')}
|
||||
>
|
||||
{busyAction === 'launch-app' ? <LoaderCircle size={16} className="spin" /> : <AppWindow size={16} />}
|
||||
{busyAction === 'launch-app' ? t('agents.launch.starting') : t('agents.launch.startApp')}
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
className="primary-button agent-launch-button"
|
||||
onClick={() => void launchAgent(defaultLaunchTarget)}
|
||||
disabled={busy || !canLaunchTarget(defaultLaunchTarget) || draftChanged}
|
||||
title={draftChanged
|
||||
? t('agents.launch.applyFirst')
|
||||
: activeStatus?.modificationState === 'applied'
|
||||
? defaultLaunchTarget?.detail
|
||||
: t('agents.launch.enableFirst')}
|
||||
>
|
||||
{busyAction === 'launch'
|
||||
? <LoaderCircle size={16} className="spin" />
|
||||
: <Play size={16} />}
|
||||
{busyAction === 'launch' ? t('agents.launch.starting') : defaultLaunchTarget ? t('agents.launch.start', { target: defaultLaunchTarget.label }) : t('agents.launch.unavailable')}
|
||||
</button>
|
||||
)}
|
||||
{selected === 'codex' ? (
|
||||
<button
|
||||
type="button"
|
||||
className="danger-button agent-close-app-button"
|
||||
onClick={openCloseAppConfirmation}
|
||||
disabled={busy}
|
||||
>
|
||||
{busyAction === 'close-app' ? <LoaderCircle size={16} className="spin" /> : <Power size={16} />}
|
||||
{busyAction === 'close-app' ? t('agents.launch.closingChatgpt') : t('agents.launch.closeChatgpt')}
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
{launchError ? (
|
||||
<span className="agent-inline-message error" role="alert" aria-live="polite">
|
||||
{launchError}
|
||||
</span>
|
||||
) : null}
|
||||
{closeAppNotice ? (
|
||||
<span className="agent-inline-message" role="status" aria-live="polite">
|
||||
{closeAppNotice}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
@@ -1700,91 +1439,6 @@ export function AgentsPage() {
|
||||
</section>
|
||||
</div>
|
||||
|
||||
{launchDirectoryDialogOpen ? (
|
||||
<div className="config-dialog-backdrop" onMouseDown={(event) => {
|
||||
if (event.currentTarget === event.target && !busy) {
|
||||
setLaunchDirectoryDialogOpen(false);
|
||||
setLaunchDirectoryTarget(null);
|
||||
}
|
||||
}}>
|
||||
<section
|
||||
className="config-dialog agent-claude-code-launch-dialog"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="agent-launch-directory-title"
|
||||
>
|
||||
<div className="config-dialog-heading">
|
||||
<div>
|
||||
<h2 id="agent-launch-directory-title">
|
||||
{t('agents.claudeCodeLaunch.dialogTitle', { client: activeDefinition.name })}
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
<p>{t('agents.claudeCodeLaunch.dialogDescription', { client: activeDefinition.name })}</p>
|
||||
<div className="config-dialog-field">
|
||||
<span>{t('agents.claudeCodeLaunch.workingDirectory')}</span>
|
||||
<button
|
||||
type="button"
|
||||
className="agent-claude-code-directory-picker"
|
||||
onClick={() => void chooseLaunchDirectory()}
|
||||
disabled={busy}
|
||||
autoFocus
|
||||
>
|
||||
<span>
|
||||
<small>{launchDirectory
|
||||
? t('agents.claudeCodeLaunch.selectedDirectory')
|
||||
: t('agents.claudeCodeLaunch.noDirectory')}</small>
|
||||
<strong title={launchDirectory || undefined}>
|
||||
{launchDirectory || t('agents.claudeCodeLaunch.chooseDirectory')}
|
||||
</strong>
|
||||
</span>
|
||||
<b>{busyAction === 'directory'
|
||||
? t('agents.claudeCodeLaunch.choosing')
|
||||
: t('agents.claudeCodeLaunch.browse')}</b>
|
||||
</button>
|
||||
</div>
|
||||
{selected === 'claude-code' ? (
|
||||
<label className="agent-claude-code-suppress-prompt">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={suppressLaunchDirectoryPrompt}
|
||||
onChange={(event) => setSuppressLaunchDirectoryPrompt(event.currentTarget.checked)}
|
||||
disabled={busy}
|
||||
/>
|
||||
<span>{t('agents.claudeCodeLaunch.neverAskAgain')}</span>
|
||||
</label>
|
||||
) : null}
|
||||
{launchDirectoryError ? (
|
||||
<span className="agent-inline-message error" role="alert" aria-live="polite">
|
||||
{launchDirectoryError}
|
||||
</span>
|
||||
) : null}
|
||||
<div className="config-dialog-actions two-actions">
|
||||
<button
|
||||
type="button"
|
||||
className="secondary-button"
|
||||
onClick={() => {
|
||||
setLaunchDirectoryDialogOpen(false);
|
||||
setLaunchDirectoryTarget(null);
|
||||
}}
|
||||
disabled={busy}
|
||||
>
|
||||
{t('common.cancel')}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="primary-button"
|
||||
onClick={() => void launchFromDirectoryDialog()}
|
||||
disabled={busy || !launchDirectory.trim() || !launchDirectoryTarget}
|
||||
>
|
||||
{busyAction === 'launch' ? <LoaderCircle size={16} className="spin" /> : null}
|
||||
{t('agents.claudeCodeLaunch.launch', { client: activeDefinition.name })}
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{defaultConfirmOpen ? (
|
||||
<div className="config-dialog-backdrop">
|
||||
<section className="config-dialog agent-restore-dialog" role="alertdialog" aria-modal="true" aria-labelledby="agent-default-title">
|
||||
@@ -1833,29 +1487,6 @@ export function AgentsPage() {
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{closeAppConfirmOpen ? (
|
||||
<div className="config-dialog-backdrop">
|
||||
<section className="config-dialog agent-restore-dialog" role="alertdialog" aria-modal="true" aria-labelledby="agent-close-app-title">
|
||||
<div className="config-dialog-heading">
|
||||
<div><AlertTriangle size={19} /><h2 id="agent-close-app-title">{t('agents.closeApp.title')}</h2></div>
|
||||
</div>
|
||||
<p>{t('agents.closeApp.description')}</p>
|
||||
{closeAppError ? (
|
||||
<span className="agent-inline-message error" role="alert" aria-live="polite">
|
||||
{closeAppError}
|
||||
</span>
|
||||
) : null}
|
||||
<div className="config-dialog-actions two-actions">
|
||||
<button type="button" className="secondary-button" onClick={closeCloseAppConfirmation} disabled={busy}>{t('common.cancel')}</button>
|
||||
<button type="button" className="danger-button" onClick={() => void closeChatGptApp()} disabled={busy}>
|
||||
{busyAction === 'close-app' ? <LoaderCircle size={16} className="spin" /> : <Power size={16} />}
|
||||
{t('agents.closeApp.confirm')}
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{oauthLoginRequiredAction ? (
|
||||
<div className="config-dialog-backdrop">
|
||||
<section className="config-dialog agent-restore-dialog" role="alertdialog" aria-modal="true" aria-labelledby="agent-oauth-login-required-title">
|
||||
|
||||
@@ -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<StatusMessage | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
let disposed = false;
|
||||
void invoke<RepairOnLaunchSetting>('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<RepairOnLaunchSetting>('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 (
|
||||
<section
|
||||
className={`agent-core-setting-section codex-auto-restore-card ${enabled ? 'enabled' : ''}`}
|
||||
aria-busy={loading || saving}
|
||||
>
|
||||
<div className="agent-section-heading">
|
||||
<div>
|
||||
<strong>{t('agents.sessions.autoRepair')}</strong>
|
||||
</div>
|
||||
<label className="codex-auto-restore-toggle">
|
||||
{loading || saving ? <LoaderCircle size={14} className="spin" aria-hidden="true" /> : null}
|
||||
<strong>
|
||||
{loading
|
||||
? t('agents.sessions.settingLoading')
|
||||
: enabled
|
||||
? t('agents.sessions.settingOn')
|
||||
: t('agents.sessions.settingOff')}
|
||||
</strong>
|
||||
<span className="switch-control codex-auto-restore-switch">
|
||||
<input
|
||||
type="checkbox"
|
||||
role="switch"
|
||||
aria-label={t('agents.sessions.autoRepair')}
|
||||
checked={enabled}
|
||||
disabled={loading || saving}
|
||||
onChange={(event) => void updateSetting(event.currentTarget.checked)}
|
||||
/>
|
||||
<span className="switch-track" />
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
{status ? (
|
||||
<small
|
||||
className="codex-auto-restore-message error"
|
||||
role="alert"
|
||||
>
|
||||
{status.text}
|
||||
</small>
|
||||
) : null}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -15,7 +15,6 @@ export type CodexSessionPage = {
|
||||
offset: number;
|
||||
limit: number;
|
||||
hasMore: boolean;
|
||||
repairOnLaunch: boolean;
|
||||
warnings: string[];
|
||||
};
|
||||
|
||||
|
||||
+2
-237
@@ -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;
|
||||
|
||||
@@ -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: '简体中文' },
|
||||
|
||||
Reference in new issue
Block a user