chore(lint): clean up frontend and Rust lint issues

This commit is contained in:
xintaofei
2026-04-23 15:56:41 +08:00
parent 1dd40d0baf
commit 022172a9ea
69 changed files with 1138 additions and 1248 deletions

View File

@@ -8,9 +8,9 @@ use tauri::State;
use crate::acp::binary_cache;
use crate::acp::error::AcpError;
use crate::acp::opencode_plugins::{self, PluginCheckSummary};
#[cfg(feature = "tauri-runtime")]
use crate::acp::manager::ConnectionManager;
use crate::acp::opencode_plugins::{self, PluginCheckSummary};
use crate::acp::preflight::{self, PreflightResult};
use crate::acp::registry;
use crate::acp::types::{
@@ -154,9 +154,7 @@ pub(crate) fn verify_agent_installed(agent_type: AgentType) -> Result<(), AcpErr
}
Ok(())
}
registry::AgentDistribution::Binary {
cmd, platforms, ..
} => {
registry::AgentDistribution::Binary { cmd, platforms, .. } => {
let platform = registry::current_platform();
if !platforms.iter().any(|p| p.platform == platform) {
return Err(AcpError::PlatformNotSupported(format!(
@@ -212,7 +210,11 @@ async fn npm_list_version(
prefix: Option<&std::path::Path>,
) -> Option<String> {
let mut cmd = crate::process::tokio_command(npm_path);
cmd.arg("list").arg("-g").arg(package_name).arg("--json").arg("--depth=0");
cmd.arg("list")
.arg("-g")
.arg(package_name)
.arg("--json")
.arg("--depth=0");
if let Some(p) = prefix {
cmd.arg(format!("--prefix={}", p.display()));
}
@@ -266,9 +268,9 @@ async fn run_npm_streaming(
cmd.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped());
let mut child = cmd.spawn().map_err(|e| {
AcpError::protocol(format!("failed to spawn npm: {e}"))
})?;
let mut child = cmd
.spawn()
.map_err(|e| AcpError::protocol(format!("failed to spawn npm: {e}")))?;
let stdout = child.stdout.take();
let stderr = child.stderr.take();
@@ -283,9 +285,7 @@ async fn run_npm_streaming(
let reader = BufReader::new(out);
let mut lines = reader.lines();
while let Ok(Some(line)) = lines.next_line().await {
emit_agent_install_event(
&emitter, &task_id, AgentInstallEventKind::Log, &line,
);
emit_agent_install_event(&emitter, &task_id, AgentInstallEventKind::Log, &line);
}
}
}
@@ -300,9 +300,7 @@ async fn run_npm_streaming(
let reader = BufReader::new(err);
let mut lines = reader.lines();
while let Ok(Some(line)) = lines.next_line().await {
emit_agent_install_event(
&emitter, &task_id, AgentInstallEventKind::Log, &line,
);
emit_agent_install_event(&emitter, &task_id, AgentInstallEventKind::Log, &line);
if !collected.is_empty() {
collected.push('\n');
}
@@ -316,9 +314,10 @@ async fn run_npm_streaming(
let (_, stderr_result) = tokio::join!(stdout_handle, stderr_handle);
let collected_stderr = stderr_result.unwrap_or_default();
let status = child.wait().await.map_err(|e| {
AcpError::protocol(format!("failed to wait for npm process: {e}"))
})?;
let status = child
.wait()
.await
.map_err(|e| AcpError::protocol(format!("failed to wait for npm process: {e}")))?;
Ok((status.success(), collected_stderr))
}
@@ -331,49 +330,58 @@ async fn install_npm_global_package_streaming(
let registry_arg = format!("--registry={NPM_OFFICIAL_REGISTRY}");
emit_agent_install_event(
emitter, task_id, AgentInstallEventKind::Log,
emitter,
task_id,
AgentInstallEventKind::Log,
format!("$ npm install -g {package}"),
);
let (success, stderr) = run_npm_streaming(
&["install", "-g", &registry_arg, package],
task_id,
emitter,
).await?;
let (success, stderr) =
run_npm_streaming(&["install", "-g", &registry_arg, package], task_id, emitter).await?;
if !success {
// EACCES: permission denied — retry with a user-local --prefix so
// we don't require root/sudo on macOS / Linux.
if stderr.contains("EACCES") {
emit_agent_install_event(
emitter, task_id, AgentInstallEventKind::Log,
emitter,
task_id,
AgentInstallEventKind::Log,
"Permission denied, retrying with user prefix...",
);
return install_npm_to_user_prefix_streaming(
package, &registry_arg, task_id, emitter,
).await;
return install_npm_to_user_prefix_streaming(package, &registry_arg, task_id, emitter)
.await;
}
// EEXIST: file conflict — retry with --force to overwrite
if stderr.contains("EEXIST") {
emit_agent_install_event(
emitter, task_id, AgentInstallEventKind::Log,
emitter,
task_id,
AgentInstallEventKind::Log,
"File conflict, retrying with --force...",
);
let (retry_success, retry_stderr) = run_npm_streaming(
&["install", "-g", "--force", &registry_arg, package],
task_id,
emitter,
).await?;
)
.await?;
if !retry_success {
if retry_stderr.contains("EACCES") {
emit_agent_install_event(
emitter, task_id, AgentInstallEventKind::Log,
emitter,
task_id,
AgentInstallEventKind::Log,
"Permission denied on --force retry, falling back to user prefix...",
);
return install_npm_to_user_prefix_streaming(
package, &registry_arg, task_id, emitter,
).await;
package,
&registry_arg,
task_id,
emitter,
)
.await;
}
let err = retry_stderr.trim().to_string();
let msg = if err.is_empty() {
@@ -424,7 +432,9 @@ async fn install_npm_to_user_prefix_streaming(
let prefix_arg = format!("--prefix={}", prefix.display());
emit_agent_install_event(
emitter, task_id, AgentInstallEventKind::Log,
emitter,
task_id,
AgentInstallEventKind::Log,
format!("$ npm install -g --prefix={} {package}", prefix.display()),
);
@@ -432,21 +442,32 @@ async fn install_npm_to_user_prefix_streaming(
&["install", "-g", &prefix_arg, registry_arg, package],
task_id,
emitter,
).await?;
)
.await?;
if !success {
// EEXIST in the user prefix: retry with --force to overwrite stale files
// from a previous installation.
if stderr.contains("EEXIST") {
emit_agent_install_event(
emitter, task_id, AgentInstallEventKind::Log,
emitter,
task_id,
AgentInstallEventKind::Log,
"File conflict in user prefix, retrying with --force...",
);
let (force_success, force_stderr) = run_npm_streaming(
&["install", "-g", "--force", &prefix_arg, registry_arg, package],
&[
"install",
"-g",
"--force",
&prefix_arg,
registry_arg,
package,
],
task_id,
emitter,
).await?;
)
.await?;
if !force_success {
let err = force_stderr.trim().to_string();
let msg = if err.is_empty() {
@@ -854,10 +875,7 @@ fn persist_cline_local_config(config_patch_json: Option<&str>) -> Result<(), Acp
act_model_key.to_string(),
serde_json::Value::String(model.clone()),
);
gs_obj.insert(
plan_model_key.to_string(),
serde_json::Value::String(model),
);
gs_obj.insert(plan_model_key.to_string(), serde_json::Value::String(model));
}
None => {
gs_obj.remove(act_model_key);
@@ -888,9 +906,8 @@ fn persist_cline_local_config(config_patch_json: Option<&str>) -> Result<(), Acp
}
if let Some(parent) = gs_path.parent() {
fs::create_dir_all(parent).map_err(|e| {
AcpError::protocol(format!("create cline data directory failed: {e}"))
})?;
fs::create_dir_all(parent)
.map_err(|e| AcpError::protocol(format!("create cline data directory failed: {e}")))?;
}
let serialized_gs = serde_json::to_string_pretty(&gs)
.map_err(|e| AcpError::protocol(format!("serialize cline globalState failed: {e}")))?;
@@ -917,10 +934,7 @@ fn persist_cline_local_config(config_patch_json: Option<&str>) -> Result<(), Acp
let key_field = cline_api_key_field_for_provider(&provider);
match trim_non_empty(runtime.api_key) {
Some(api_key) => {
secrets_obj.insert(
key_field.to_string(),
serde_json::Value::String(api_key),
);
secrets_obj.insert(key_field.to_string(), serde_json::Value::String(api_key));
}
None => {
secrets_obj.remove(key_field);
@@ -928,9 +942,8 @@ fn persist_cline_local_config(config_patch_json: Option<&str>) -> Result<(), Acp
}
if let Some(parent) = secrets_path.parent() {
fs::create_dir_all(parent).map_err(|e| {
AcpError::protocol(format!("create cline data directory failed: {e}"))
})?;
fs::create_dir_all(parent)
.map_err(|e| AcpError::protocol(format!("create cline data directory failed: {e}")))?;
}
let serialized_secrets = serde_json::to_string_pretty(&secrets)
.map_err(|e| AcpError::protocol(format!("serialize cline secrets failed: {e}")))?;
@@ -1719,7 +1732,11 @@ fn trim_non_empty(value: Option<String>) -> Option<String> {
/// Shared by runtime env resolution, model-provider cascade, and config patching.
fn agent_env_keys(agent_type: AgentType) -> (&'static str, &'static str, &'static str) {
match agent_type {
AgentType::ClaudeCode => ("ANTHROPIC_BASE_URL", "ANTHROPIC_AUTH_TOKEN", "ANTHROPIC_MODEL"),
AgentType::ClaudeCode => (
"ANTHROPIC_BASE_URL",
"ANTHROPIC_AUTH_TOKEN",
"ANTHROPIC_MODEL",
),
AgentType::Gemini => ("GOOGLE_GEMINI_BASE_URL", "GEMINI_API_KEY", "GEMINI_MODEL"),
_ => ("OPENAI_BASE_URL", "OPENAI_API_KEY", "OPENAI_MODEL"),
}
@@ -1814,11 +1831,17 @@ fn cascade_update_agent_config(
AgentType::ClaudeCode | AgentType::Gemini => {
// Write into config.env (not root-level)
let mut env = serde_json::Map::new();
env.insert(url_key.to_string(), serde_json::Value::String(api_url.to_string()));
env.insert(key_key.to_string(), serde_json::Value::String(api_key.to_string()));
env.insert(
url_key.to_string(),
serde_json::Value::String(api_url.to_string()),
);
env.insert(
key_key.to_string(),
serde_json::Value::String(api_key.to_string()),
);
let patch = serde_json::json!({ "env": env });
let patch_str = serde_json::to_string(&patch)
.map_err(|e| AcpError::protocol(e.to_string()))?;
let patch_str =
serde_json::to_string(&patch).map_err(|e| AcpError::protocol(e.to_string()))?;
persist_agent_local_config_json(agent_type, Some(&patch_str))?;
}
AgentType::OpenClaw => {
@@ -1855,7 +1878,10 @@ fn cascade_update_agent_config(
if api_url.trim().is_empty() {
table.remove("api_base_url");
} else {
table.insert("api_base_url".to_string(), toml::Value::String(api_url.to_string()));
table.insert(
"api_base_url".to_string(),
toml::Value::String(api_url.to_string()),
);
}
}
let toml_str = toml::to_string_pretty(&toml_value)
@@ -1882,8 +1908,8 @@ fn cascade_update_agent_config(
persist_opencode_auth_json(&auth_str)?;
let patch = serde_json::json!({ "apiBaseUrl": api_url });
let patch_str = serde_json::to_string(&patch)
.map_err(|e| AcpError::protocol(e.to_string()))?;
let patch_str =
serde_json::to_string(&patch).map_err(|e| AcpError::protocol(e.to_string()))?;
persist_agent_local_config_json(agent_type, Some(&patch_str))?;
}
AgentType::Cline => {}
@@ -2106,17 +2132,11 @@ pub(crate) async fn acp_get_agent_status_core(
true,
setting.as_ref().and_then(|m| m.installed_version.clone()),
),
registry::AgentDistribution::Binary {
platforms, cmd, ..
} => {
let detected =
binary_cache::detect_installed_version(agent_type, cmd)
.ok()
.flatten();
(
platforms.iter().any(|p| p.platform == platform),
detected,
)
registry::AgentDistribution::Binary { platforms, cmd, .. } => {
let detected = binary_cache::detect_installed_version(agent_type, cmd)
.ok()
.flatten();
(platforms.iter().any(|p| p.platform == platform), detected)
}
};
@@ -2137,9 +2157,7 @@ pub async fn acp_get_agent_status(
acp_get_agent_status_core(agent_type, &db).await
}
pub(crate) async fn acp_list_agents_core(
db: &AppDatabase,
) -> Result<Vec<AcpAgentInfo>, AcpError> {
pub(crate) async fn acp_list_agents_core(db: &AppDatabase) -> Result<Vec<AcpAgentInfo>, AcpError> {
let platform = registry::current_platform();
let agent_types = registry::all_acp_agents();
@@ -2417,9 +2435,17 @@ pub async fn acp_update_agent_preferences(
) -> Result<(), AcpError> {
let emitter = EventEmitter::Tauri(app);
acp_update_agent_preferences_core(
agent_type, enabled, env, config_json, opencode_auth_json,
codex_auth_json, codex_config_toml, &db, &emitter,
).await
agent_type,
enabled,
env,
config_json,
opencode_auth_json,
codex_auth_json,
codex_config_toml,
&db,
&emitter,
)
.await
}
pub(crate) async fn acp_update_agent_env_core(
@@ -2557,7 +2583,12 @@ pub async fn acp_update_agent_config(
) -> Result<(), AcpError> {
let emitter = EventEmitter::Tauri(app);
acp_update_agent_config_core(
agent_type, config_json, opencode_auth_json, codex_auth_json, codex_config_toml, &emitter,
agent_type,
config_json,
opencode_auth_json,
codex_auth_json,
codex_config_toml,
&emitter,
)
.await
}
@@ -2589,17 +2620,25 @@ pub(crate) async fn acp_download_agent_binary_core(
})?;
emit_agent_install_event(
emitter, &task_id, AgentInstallEventKind::Log,
emitter,
&task_id,
AgentInstallEventKind::Log,
format!("Downloading {} v{version} for {platform}", meta.name),
);
let emitter_clone = emitter.clone();
let task_id_clone = task_id.clone();
let _ = binary_cache::ensure_binary_for_agent_with_progress(
agent_type, version, fallback.url, cmd,
agent_type,
version,
fallback.url,
cmd,
move |msg| {
emit_agent_install_event(
&emitter_clone, &task_id_clone, AgentInstallEventKind::Log, msg,
&emitter_clone,
&task_id_clone,
AgentInstallEventKind::Log,
msg,
);
},
)
@@ -2607,21 +2646,26 @@ pub(crate) async fn acp_download_agent_binary_core(
emit_acp_agents_updated(emitter, "binary_downloaded", Some(agent_type));
Ok(())
}
registry::AgentDistribution::Npx { .. } => Err(
AcpError::protocol("download is only supported for binary agents"),
),
registry::AgentDistribution::Npx { .. } => Err(AcpError::protocol(
"download is only supported for binary agents",
)),
};
match &result {
Ok(()) => {
emit_agent_install_event(
emitter, &task_id, AgentInstallEventKind::Completed,
emitter,
&task_id,
AgentInstallEventKind::Completed,
format!("{} installed successfully", meta.name),
);
}
Err(e) => {
emit_agent_install_event(
emitter, &task_id, AgentInstallEventKind::Failed, e.to_string(),
emitter,
&task_id,
AgentInstallEventKind::Failed,
e.to_string(),
);
}
}
@@ -2645,12 +2689,9 @@ pub(crate) async fn acp_detect_agent_local_version_core(
) -> Result<Option<String>, AcpError> {
let detected = detect_local_version(agent_type).await;
if let Some(version) = detected.clone() {
let _ = agent_setting_service::set_installed_version(
conn,
agent_type,
Some(version.clone()),
)
.await;
let _ =
agent_setting_service::set_installed_version(conn, agent_type, Some(version.clone()))
.await;
return Ok(Some(version));
}
@@ -2699,13 +2740,17 @@ pub(crate) async fn acp_prepare_npx_agent_core(
.and_then(|m| m.installed_version);
emit_agent_install_event(
emitter, &task_id, AgentInstallEventKind::Log,
emitter,
&task_id,
AgentInstallEventKind::Log,
format!("Installing {} ({package})", meta.name),
);
install_npm_global_package_streaming(package, &task_id, emitter).await?;
emit_agent_install_event(
emitter, &task_id, AgentInstallEventKind::Log,
emitter,
&task_id,
AgentInstallEventKind::Log,
"Detecting installed version...",
);
let resolved = detect_local_version(agent_type)
@@ -2741,13 +2786,18 @@ pub(crate) async fn acp_prepare_npx_agent_core(
match &result {
Ok(version) => {
emit_agent_install_event(
emitter, &task_id, AgentInstallEventKind::Completed,
emitter,
&task_id,
AgentInstallEventKind::Completed,
format!("{} v{version} installed successfully", meta.name),
);
}
Err(e) => {
emit_agent_install_event(
emitter, &task_id, AgentInstallEventKind::Failed, e.to_string(),
emitter,
&task_id,
AgentInstallEventKind::Failed,
e.to_string(),
);
}
}
@@ -2777,7 +2827,9 @@ pub(crate) async fn acp_uninstall_agent_core(
let meta = registry::get_agent_meta(agent_type);
emit_agent_install_event(
emitter, &task_id, AgentInstallEventKind::Log,
emitter,
&task_id,
AgentInstallEventKind::Log,
format!("Uninstalling {}...", meta.name),
);
@@ -2802,13 +2854,18 @@ pub(crate) async fn acp_uninstall_agent_core(
match &result {
Ok(()) => {
emit_agent_install_event(
emitter, &task_id, AgentInstallEventKind::Completed,
emitter,
&task_id,
AgentInstallEventKind::Completed,
format!("{} uninstalled successfully", meta.name),
);
}
Err(e) => {
emit_agent_install_event(
emitter, &task_id, AgentInstallEventKind::Failed, e.to_string(),
emitter,
&task_id,
AgentInstallEventKind::Failed,
e.to_string(),
);
}
}
@@ -2868,9 +2925,7 @@ pub async fn acp_list_agent_skills(
let Some(spec) = skill_storage_spec(agent_type) else {
return Ok(AgentSkillsListResult {
supported: false,
message: Some(format!(
"{agent_type} 暂不支持在设置页管理 Skills"
)),
message: Some(format!("{agent_type} 暂不支持在设置页管理 Skills")),
locations: Vec::new(),
skills: Vec::new(),
});
@@ -3033,8 +3088,7 @@ pub async fn acp_delete_agent_skill(
}
pub(crate) async fn opencode_list_plugins_core() -> Result<PluginCheckSummary, AcpError> {
opencode_plugins::check_opencode_plugins(None)
.map_err(|e| AcpError::Protocol(e))
opencode_plugins::check_opencode_plugins(None).map_err(AcpError::Protocol)
}
#[cfg_attr(feature = "tauri-runtime", tauri::command)]
@@ -3049,7 +3103,7 @@ pub(crate) async fn opencode_install_plugins_core(
) -> Result<(), AcpError> {
opencode_plugins::install_missing_plugins(names, task_id, emitter)
.await
.map_err(|e| AcpError::Protocol(e))
.map_err(AcpError::Protocol)
}
#[cfg(feature = "tauri-runtime")]
@@ -3068,13 +3122,11 @@ pub(crate) async fn opencode_uninstall_plugin_core(
) -> Result<PluginCheckSummary, AcpError> {
opencode_plugins::uninstall_plugin(name)
.await
.map_err(|e| AcpError::Protocol(e))
.map_err(AcpError::Protocol)
}
#[cfg_attr(feature = "tauri-runtime", tauri::command)]
pub async fn opencode_uninstall_plugin(
name: String,
) -> Result<PluginCheckSummary, AcpError> {
pub async fn opencode_uninstall_plugin(name: String) -> Result<PluginCheckSummary, AcpError> {
opencode_uninstall_plugin_core(name).await
}
@@ -3108,7 +3160,10 @@ struct DeviceCodeUserCodeResp {
device_auth_id: String,
#[serde(alias = "usercode")]
user_code: String,
#[serde(default = "default_interval", deserialize_with = "deserialize_interval")]
#[serde(
default = "default_interval",
deserialize_with = "deserialize_interval"
)]
interval: u64,
}
@@ -3118,11 +3173,8 @@ fn default_interval() -> u64 {
fn extract_jwt_account_id(jwt: &str) -> Option<String> {
let payload = jwt.split('.').nth(1)?;
let decoded = base64::Engine::decode(
&base64::engine::general_purpose::URL_SAFE_NO_PAD,
payload,
)
.ok()?;
let decoded =
base64::Engine::decode(&base64::engine::general_purpose::URL_SAFE_NO_PAD, payload).ok()?;
let value: serde_json::Value = serde_json::from_slice(&decoded).ok()?;
value
.get("https://api.openai.com/auth")
@@ -3138,11 +3190,13 @@ where
use serde::de;
let value = serde_json::Value::deserialize(deserializer)?;
match &value {
serde_json::Value::Number(n) => n.as_u64().ok_or_else(|| {
de::Error::custom(format!("invalid interval number: {n}"))
}),
serde_json::Value::Number(n) => n
.as_u64()
.ok_or_else(|| de::Error::custom(format!("invalid interval number: {n}"))),
serde_json::Value::String(s) => s.trim().parse::<u64>().map_err(de::Error::custom),
_ => Err(de::Error::custom(format!("unexpected interval type: {value}"))),
_ => Err(de::Error::custom(format!(
"unexpected interval type: {value}"
))),
}
}
@@ -3161,9 +3215,7 @@ struct OAuthTokenResp {
refresh_token: String,
}
pub(crate) async fn codex_request_device_code_core()
-> Result<CodexDeviceCodeResponse, AcpError>
{
pub(crate) async fn codex_request_device_code_core() -> Result<CodexDeviceCodeResponse, AcpError> {
let client = reqwest::Client::new();
let url = format!("{CODEX_OAUTH_ISSUER}/api/accounts/deviceauth/usercode");
let body = serde_json::json!({ "client_id": CODEX_OAUTH_CLIENT_ID });
@@ -3187,8 +3239,11 @@ pub(crate) async fn codex_request_device_code_core()
.text()
.await
.map_err(|e| AcpError::protocol(format!("read device code response failed: {e}")))?;
let uc: DeviceCodeUserCodeResp = serde_json::from_str(&raw_body)
.map_err(|e| AcpError::protocol(format!("parse device code response failed: {e} | body: {raw_body}")))?;
let uc: DeviceCodeUserCodeResp = serde_json::from_str(&raw_body).map_err(|e| {
AcpError::protocol(format!(
"parse device code response failed: {e} | body: {raw_body}"
))
})?;
Ok(CodexDeviceCodeResponse {
user_code: uc.user_code,
@@ -3199,9 +3254,7 @@ pub(crate) async fn codex_request_device_code_core()
}
#[cfg_attr(feature = "tauri-runtime", tauri::command)]
pub async fn codex_request_device_code()
-> Result<CodexDeviceCodeResponse, AcpError>
{
pub async fn codex_request_device_code() -> Result<CodexDeviceCodeResponse, AcpError> {
codex_request_device_code_core().await
}

View File

@@ -98,20 +98,19 @@ pub async fn connect_chat_channel_core(
.map_err(AppCommandError::from)?
.ok_or_else(|| AppCommandError::not_found(format!("Chat channel {id} not found")))?;
let channel_type: ChannelType =
serde_json::from_value(serde_json::Value::String(model.channel_type.clone()))
.map_err(|_| {
AppCommandError::configuration_invalid(format!(
"Invalid channel type: {}",
model.channel_type
))
})?;
let channel_type: ChannelType = serde_json::from_value(serde_json::Value::String(
model.channel_type.clone(),
))
.map_err(|_| {
AppCommandError::configuration_invalid(format!(
"Invalid channel type: {}",
model.channel_type
))
})?;
let config: serde_json::Value =
serde_json::from_str(&model.config_json).map_err(|e| {
AppCommandError::configuration_invalid("Invalid config JSON")
.with_detail(e.to_string())
})?;
let config: serde_json::Value = serde_json::from_str(&model.config_json).map_err(|e| {
AppCommandError::configuration_invalid("Invalid config JSON").with_detail(e.to_string())
})?;
let token = crate::keyring_store::get_channel_token(id).ok_or_else(|| {
eprintln!("[connect_chat_channel] channel {id}: Token not set in keyring");
@@ -138,29 +137,25 @@ pub async fn connect_chat_channel_core(
Ok(())
}
pub async fn test_chat_channel_core(
db: &AppDatabase,
id: i32,
) -> Result<(), AppCommandError> {
pub async fn test_chat_channel_core(db: &AppDatabase, id: i32) -> Result<(), AppCommandError> {
let model = chat_channel_service::get_by_id(&db.conn, id)
.await
.map_err(AppCommandError::from)?
.ok_or_else(|| AppCommandError::not_found(format!("Chat channel {id} not found")))?;
let channel_type: ChannelType =
serde_json::from_value(serde_json::Value::String(model.channel_type.clone()))
.map_err(|_| {
AppCommandError::configuration_invalid(format!(
"Invalid channel type: {}",
model.channel_type
))
})?;
let channel_type: ChannelType = serde_json::from_value(serde_json::Value::String(
model.channel_type.clone(),
))
.map_err(|_| {
AppCommandError::configuration_invalid(format!(
"Invalid channel type: {}",
model.channel_type
))
})?;
let config: serde_json::Value =
serde_json::from_str(&model.config_json).map_err(|e| {
AppCommandError::configuration_invalid("Invalid config JSON")
.with_detail(e.to_string())
})?;
let config: serde_json::Value = serde_json::from_str(&model.config_json).map_err(|e| {
AppCommandError::configuration_invalid("Invalid config JSON").with_detail(e.to_string())
})?;
let token = crate::keyring_store::get_channel_token(id)
.ok_or_else(|| AppCommandError::configuration_missing("Token not set"))?;
@@ -215,18 +210,20 @@ pub async fn list_chat_channel_messages_core(
) -> Result<Vec<ChatChannelMessageLogInfo>, AppCommandError> {
let limit = limit.unwrap_or(50);
let offset = offset.unwrap_or(0);
let rows = chat_channel_message_log_service::list_by_channel(&db.conn, channel_id, limit, offset)
.await
.map_err(AppCommandError::from)?;
Ok(rows.into_iter().map(ChatChannelMessageLogInfo::from).collect())
let rows =
chat_channel_message_log_service::list_by_channel(&db.conn, channel_id, limit, offset)
.await
.map_err(AppCommandError::from)?;
Ok(rows
.into_iter()
.map(ChatChannelMessageLogInfo::from)
.collect())
}
const COMMAND_PREFIX_KEY: &str = "chat_command_prefix";
const DEFAULT_COMMAND_PREFIX: &str = "/";
pub async fn get_chat_command_prefix_core(
db: &AppDatabase,
) -> Result<String, AppCommandError> {
pub async fn get_chat_command_prefix_core(db: &AppDatabase) -> Result<String, AppCommandError> {
let val = crate::db::service::app_metadata_service::get_value(&db.conn, COMMAND_PREFIX_KEY)
.await
.map_err(AppCommandError::from)?;
@@ -238,10 +235,7 @@ pub async fn set_chat_command_prefix_core(
prefix: String,
) -> Result<(), AppCommandError> {
let trimmed = prefix.trim();
if trimmed.is_empty()
|| trimmed.len() > 3
|| trimmed.chars().any(|c| c.is_alphanumeric())
{
if trimmed.is_empty() || trimmed.len() > 3 || trimmed.chars().any(|c| c.is_alphanumeric()) {
return Err(AppCommandError::invalid_input(
"Prefix must be 1-3 non-alphanumeric characters",
));
@@ -254,9 +248,7 @@ pub async fn set_chat_command_prefix_core(
const MESSAGE_LANGUAGE_KEY: &str = "chat_message_language";
pub async fn get_chat_message_language_core(
db: &AppDatabase,
) -> Result<String, AppCommandError> {
pub async fn get_chat_message_language_core(db: &AppDatabase) -> Result<String, AppCommandError> {
let val = crate::db::service::app_metadata_service::get_value(&db.conn, MESSAGE_LANGUAGE_KEY)
.await
.map_err(AppCommandError::from)?;
@@ -360,14 +352,20 @@ pub async fn weixin_check_qrcode_core(
if result.status == "confirmed" {
eprintln!(
"[Weixin] QR confirmed for channel {channel_id}, bot_token={}, base_url={}",
result.bot_token.as_deref().map(|t| if t.len() > 8 { &t[..8] } else { t }).unwrap_or("None"),
result
.bot_token
.as_deref()
.map(|t| if t.len() > 8 { &t[..8] } else { t })
.unwrap_or("None"),
result.base_url.as_deref().unwrap_or("None"),
);
if let Some(ref token) = result.bot_token {
save_chat_channel_token_core(channel_id, token)?;
eprintln!("[Weixin] Token saved for channel {channel_id}");
} else {
eprintln!("[Weixin] WARNING: No bot_token in confirmed response for channel {channel_id}");
eprintln!(
"[Weixin] WARNING: No bot_token in confirmed response for channel {channel_id}"
);
}
if let Some(ref base_url) = result.base_url {
let config_json = serde_json::json!({ "base_url": base_url }).to_string();
@@ -415,7 +413,16 @@ pub async fn create_chat_channel(
daily_report_enabled: bool,
daily_report_time: Option<String>,
) -> Result<ChatChannelInfo, AppCommandError> {
create_chat_channel_core(&db, name, channel_type, config_json, enabled, daily_report_enabled, daily_report_time).await
create_chat_channel_core(
&db,
name,
channel_type,
config_json,
enabled,
daily_report_enabled,
daily_report_time,
)
.await
}
#[allow(clippy::too_many_arguments)]
@@ -431,7 +438,17 @@ pub async fn update_chat_channel(
daily_report_enabled: Option<bool>,
daily_report_time: Option<Option<String>>,
) -> Result<ChatChannelInfo, AppCommandError> {
update_chat_channel_core(&db, id, name, enabled, config_json, event_filter_json, daily_report_enabled, daily_report_time).await
update_chat_channel_core(
&db,
id,
name,
enabled,
config_json,
event_filter_json,
daily_report_enabled,
daily_report_time,
)
.await
}
#[cfg(feature = "tauri-runtime")]
@@ -446,7 +463,10 @@ pub async fn delete_chat_channel(
#[cfg(feature = "tauri-runtime")]
#[tauri::command]
pub async fn save_chat_channel_token(channel_id: i32, token: String) -> Result<(), AppCommandError> {
pub async fn save_chat_channel_token(
channel_id: i32,
token: String,
) -> Result<(), AppCommandError> {
save_chat_channel_token_core(channel_id, &token)
}

View File

@@ -3,9 +3,9 @@ use std::collections::{HashMap, HashSet};
use crate::app_error::AppCommandError;
#[cfg(feature = "tauri-runtime")]
use crate::db::entities::conversation;
use crate::db::service::{conversation_service, folder_service};
#[cfg(feature = "tauri-runtime")]
use crate::db::service::import_service;
use crate::db::service::{conversation_service, folder_service};
#[cfg(feature = "tauri-runtime")]
use crate::db::AppDatabase;
use crate::models::*;
@@ -288,8 +288,7 @@ pub async fn get_folder_conversation_core(
.await
.map_err(AppCommandError::from)?;
let (turns, session_stats, resolved_ext_id) = if let Some(ref ext_id) = summary.external_id
{
let (turns, session_stats, resolved_ext_id) = if let Some(ref ext_id) = summary.external_id {
let at = summary.agent_type;
let eid = ext_id.clone();
let db_created_at = summary.created_at;
@@ -317,7 +316,10 @@ pub async fn get_folder_conversation_core(
// ID after session/new fallback overwrote the original
// (Gemini CLI). Fall back to matching by folder_path
// and started_at from the parsed conversation list.
if matches!(at, AgentType::OpenClaw | AgentType::Cline | AgentType::Gemini) {
if matches!(
at,
AgentType::OpenClaw | AgentType::Cline | AgentType::Gemini
) {
if let Ok(all) = parser.list_conversations() {
// Filter by folder_path first, then find the closest
// started_at match within 300 seconds of db_created_at.
@@ -333,17 +335,14 @@ pub async fn get_folder_conversation_core(
(c.started_at - db_created_at).num_seconds().unsigned_abs()
})
.filter(|c| {
let diff = (c.started_at - db_created_at).num_seconds().unsigned_abs();
let diff =
(c.started_at - db_created_at).num_seconds().unsigned_abs();
diff < 300
});
if let Some(conv) = matched {
let new_ext_id = conv.id.clone();
if let Ok(d) = parser.get_conversation(&new_ext_id) {
return Ok((
d.turns,
d.session_stats,
Some(new_ext_id),
));
return Ok((d.turns, d.session_stats, Some(new_ext_id)));
}
}
}
@@ -367,8 +366,7 @@ pub async fn get_folder_conversation_core(
// If we resolved a different external_id (e.g. ACP UUID → parser branch ID),
// update the database so future lookups are direct.
if let Some(new_ext_id) = resolved_ext_id {
let _ =
conversation_service::update_external_id(conn, conversation_id, new_ext_id).await;
let _ = conversation_service::update_external_id(conn, conversation_id, new_ext_id).await;
}
let mut summary = summary;

View File

@@ -23,11 +23,11 @@ use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use tokio::sync::Mutex;
use crate::acp::types::AgentSkillScope;
use crate::commands::acp::{
preferred_scope_skill_dir, remove_skill_entry, scoped_skill_dirs, skill_storage_spec,
validate_skill_id,
};
use crate::acp::types::AgentSkillScope;
use crate::models::agent::AgentType;
// ─── Embedded bundle ────────────────────────────────────────────────────
@@ -373,9 +373,7 @@ fn create_link_raw(src: &Path, dst: &Path) -> io::Result<bool> {
copy_dir_recursive(src, dst).map_err(|copy_err| {
io::Error::new(
io::ErrorKind::Other,
format!(
"junction failed ({junction_err}); copy fallback failed ({copy_err})"
),
format!("junction failed ({junction_err}); copy fallback failed ({copy_err})"),
)
})?;
Ok(true)
@@ -518,9 +516,7 @@ fn ensure_central_experts_installed_blocking() -> InstallReport {
report.pending_user_review.push(meta.id.clone());
}
Err(e) => {
report
.errors
.push(format!("{}: {}", meta.id, e));
report.errors.push(format!("{}: {}", meta.id, e));
}
}
}
@@ -639,10 +635,10 @@ fn extract_bundle_dir(
.path()
.to_str()
.ok_or_else(|| ExpertsError::Io("non-utf8 path in bundle".into()))?;
let rel_within =
rel.strip_prefix(bundle_prefix)
.and_then(|s| s.strip_prefix('/'))
.unwrap_or(rel);
let rel_within = rel
.strip_prefix(bundle_prefix)
.and_then(|s| s.strip_prefix('/'))
.unwrap_or(rel);
let out_path = target.join(rel_within);
if let Some(parent) = out_path.parent() {
fs::create_dir_all(parent)?;
@@ -686,8 +682,7 @@ pub async fn experts_list() -> Result<Vec<ExpertListItem>, ExpertsError> {
pub async fn experts_list_for_agent(
agent_type: AgentType,
) -> Result<Vec<ExpertListItem>, ExpertsError> {
let _ = skill_storage_spec(agent_type)
.ok_or(ExpertsError::UnsupportedAgent(agent_type))?;
let _ = skill_storage_spec(agent_type).ok_or(ExpertsError::UnsupportedAgent(agent_type))?;
let dirs = scoped_skill_dirs(agent_type, AgentSkillScope::Global, None)
.map_err(|_| ExpertsError::UnsupportedAgent(agent_type))?;
@@ -727,8 +722,8 @@ pub async fn experts_list_for_agent(
pub async fn experts_get_install_status(
expert_id: String,
) -> Result<Vec<ExpertInstallStatus>, ExpertsError> {
let expert_id = validate_skill_id(&expert_id)
.map_err(|e| ExpertsError::Metadata(e.to_string()))?;
let expert_id =
validate_skill_id(&expert_id).map_err(|e| ExpertsError::Metadata(e.to_string()))?;
let _ = find_metadata(&expert_id)?; // ensure it exists in the bundle
let expected = expert_central_path(&expert_id);
let agents = supported_agents();
@@ -776,8 +771,8 @@ pub async fn experts_link_to_agent(
expert_id: String,
agent_type: AgentType,
) -> Result<ExpertInstallStatus, ExpertsError> {
let expert_id = validate_skill_id(&expert_id)
.map_err(|e| ExpertsError::Metadata(e.to_string()))?;
let expert_id =
validate_skill_id(&expert_id).map_err(|e| ExpertsError::Metadata(e.to_string()))?;
let _ = find_metadata(&expert_id)?;
let central = expert_central_path(&expert_id);
if !central.exists() {
@@ -819,9 +814,8 @@ pub async fn experts_link_to_agent(
}
ExpertLinkState::NotLinked => {
// Shouldn't happen after AlreadyExists, but retry once.
create_link_raw(&central, &link_path).map_err(|e| ExpertsError::Io(format!(
"retry link failed: {e}"
)))?;
create_link_raw(&central, &link_path)
.map_err(|e| ExpertsError::Io(format!("retry link failed: {e}")))?;
}
}
}
@@ -846,8 +840,8 @@ pub async fn experts_unlink_from_agent(
expert_id: String,
agent_type: AgentType,
) -> Result<(), ExpertsError> {
let expert_id = validate_skill_id(&expert_id)
.map_err(|e| ExpertsError::Metadata(e.to_string()))?;
let expert_id =
validate_skill_id(&expert_id).map_err(|e| ExpertsError::Metadata(e.to_string()))?;
let _guard = mutation_lock().lock().await;
@@ -865,10 +859,14 @@ pub async fn experts_unlink_from_agent(
continue;
}
let state = classify_link(&candidate, &central);
if matches!(state, ExpertLinkState::LinkedToCodeg | ExpertLinkState::Broken) {
if matches!(
state,
ExpertLinkState::LinkedToCodeg | ExpertLinkState::Broken
) {
// Safe to remove a link to our central store or a broken link.
remove_skill_entry(&candidate)
.map_err(|e| ExpertsError::Io(format!("remove link {}: {e}", candidate.display())))?;
remove_skill_entry(&candidate).map_err(|e| {
ExpertsError::Io(format!("remove link {}: {e}", candidate.display()))
})?;
removed = true;
} else if state == ExpertLinkState::LinkedElsewhere {
return Err(ExpertsError::ForeignLink {
@@ -893,8 +891,8 @@ pub async fn experts_unlink_from_agent(
#[cfg_attr(feature = "tauri-runtime", tauri::command)]
pub async fn experts_read_content(expert_id: String) -> Result<String, ExpertsError> {
let expert_id = validate_skill_id(&expert_id)
.map_err(|e| ExpertsError::Metadata(e.to_string()))?;
let expert_id =
validate_skill_id(&expert_id).map_err(|e| ExpertsError::Metadata(e.to_string()))?;
let _ = find_metadata(&expert_id)?;
let path = expert_central_path(&expert_id).join("SKILL.md");
if !path.exists() {

View File

@@ -1192,11 +1192,7 @@ pub async fn git_checkout(path: String, branch_name: String) -> Result<(), AppCo
}
#[cfg_attr(feature = "tauri-runtime", tauri::command)]
pub async fn git_reset(
path: String,
commit: String,
mode: String,
) -> Result<(), AppCommandError> {
pub async fn git_reset(path: String, commit: String, mode: String) -> Result<(), AppCommandError> {
let mode = mode.trim().to_lowercase();
let mode_flag = match mode.as_str() {
"soft" | "mixed" | "hard" | "keep" => format!("--{mode}"),
@@ -2128,10 +2124,7 @@ pub async fn git_delete_branch(
.map_err(AppCommandError::io)?;
if !output.status.success() {
return Err(git_command_error(
&format!("branch {flag}"),
&output.stderr,
));
return Err(git_command_error(&format!("branch {flag}"), &output.stderr));
}
Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
}

View File

@@ -1735,9 +1735,7 @@ fn upsert_openclaw_server(id: &str, spec: &Value) -> Result<(), AppCommandError>
let mcp = obj
.get_mut("mcp")
.and_then(Value::as_object_mut)
.ok_or_else(|| {
mcp_configuration_invalid(format!("invalid mcp in {}", path.display()))
})?;
.ok_or_else(|| mcp_configuration_invalid(format!("invalid mcp in {}", path.display())))?;
if !mcp.get("servers").map(Value::is_object).unwrap_or(false) {
mcp.insert("servers".to_string(), Value::Object(Map::new()));

View File

@@ -18,9 +18,7 @@ fn validate_agent_types(agent_types: &[String]) -> Result<(), AppCommandError> {
}
for at in agent_types {
let _: AgentType = serde_json::from_value(serde_json::Value::String(at.clone()))
.map_err(|_| {
AppCommandError::invalid_input(format!("Invalid agent type: {at}"))
})?;
.map_err(|_| AppCommandError::invalid_input(format!("Invalid agent type: {at}")))?;
}
Ok(())
}
@@ -32,20 +30,28 @@ fn validate_fields(
) -> Result<(), AppCommandError> {
if let Some(n) = name {
if n.len() > 256 {
return Err(AppCommandError::invalid_input("Name must be 256 characters or less"));
return Err(AppCommandError::invalid_input(
"Name must be 256 characters or less",
));
}
}
if let Some(u) = api_url {
if u.len() > 2048 {
return Err(AppCommandError::invalid_input("API URL must be 2048 characters or less"));
return Err(AppCommandError::invalid_input(
"API URL must be 2048 characters or less",
));
}
if !u.starts_with("http://") && !u.starts_with("https://") {
return Err(AppCommandError::invalid_input("API URL must start with http:// or https://"));
return Err(AppCommandError::invalid_input(
"API URL must start with http:// or https://",
));
}
}
if let Some(k) = api_key {
if k.len() > 4096 {
return Err(AppCommandError::invalid_input("API Key must be 4096 characters or less"));
return Err(AppCommandError::invalid_input(
"API Key must be 4096 characters or less",
));
}
}
Ok(())
@@ -72,15 +78,9 @@ pub async fn create_model_provider_core(
let agent_types_json = serde_json::to_string(&agent_types)
.map_err(|e| AppCommandError::invalid_input(e.to_string()))?;
let model = model_provider_service::create(
&db.conn,
name,
api_url,
api_key,
agent_types_json,
)
.await
.map_err(AppCommandError::from)?;
let model = model_provider_service::create(&db.conn, name, api_url, api_key, agent_types_json)
.await
.map_err(AppCommandError::from)?;
Ok(ModelProviderInfo::from(model))
}
@@ -122,8 +122,12 @@ pub async fn update_model_provider_core(
.map_err(AppCommandError::from)?;
// Cascade credential changes to all dependent agent settings and config files.
let url_changed = api_url.as_deref().is_some_and(|u| u != old_provider.api_url);
let key_changed = api_key.as_deref().is_some_and(|k| k != old_provider.api_key);
let url_changed = api_url
.as_deref()
.is_some_and(|u| u != old_provider.api_url);
let key_changed = api_key
.as_deref()
.is_some_and(|k| k != old_provider.api_key);
if url_changed || key_changed {
let final_url = api_url.as_deref().unwrap_or(&old_provider.api_url);
let final_key = api_key.as_deref().unwrap_or(&old_provider.api_key);
@@ -135,10 +139,7 @@ pub async fn update_model_provider_core(
Ok(ModelProviderInfo::from(model))
}
pub async fn delete_model_provider_core(
db: &AppDatabase,
id: i32,
) -> Result<(), AppCommandError> {
pub async fn delete_model_provider_core(db: &AppDatabase, id: i32) -> Result<(), AppCommandError> {
// Check if any agent settings reference this provider.
let dependents = agent_setting_service::find_by_model_provider_id(&db.conn, id)
.await

View File

@@ -28,12 +28,7 @@ pub async fn send_notification(
#[cfg(not(target_os = "macos"))]
{
use tauri_plugin_notification::NotificationExt;
let _ = app
.notification()
.builder()
.title(title)
.body(body)
.show();
let _ = app.notification().builder().title(title).body(body).show();
}
Ok(())

View File

@@ -29,9 +29,7 @@ async fn detect_one(name: &str) -> PackageManagerInfo {
match result {
Ok(output) if output.status.success() => {
let version = String::from_utf8_lossy(&output.stdout)
.trim()
.to_string();
let version = String::from_utf8_lossy(&output.stdout).trim().to_string();
PackageManagerInfo {
name: name.to_string(),
installed: true,
@@ -76,7 +74,9 @@ pub async fn create_shadcn_project(
return Err(AppCommandError::invalid_input("Template is required"));
}
if target_dir.is_empty() {
return Err(AppCommandError::invalid_input("Target directory is required"));
return Err(AppCommandError::invalid_input(
"Target directory is required",
));
}
let full_path = PathBuf::from(&target_dir).join(&project_name);

View File

@@ -139,7 +139,11 @@ pub async fn update_system_language_settings(
.map_err(AppCommandError::from)?;
let emitter = crate::web::event_bridge::EventEmitter::Tauri(app);
crate::web::event_bridge::emit_event(&emitter, LANGUAGE_SETTINGS_UPDATED_EVENT, settings.clone());
crate::web::event_bridge::emit_event(
&emitter,
LANGUAGE_SETTINGS_UPDATED_EVENT,
settings.clone(),
);
Ok(settings)
}

View File

@@ -32,16 +32,14 @@ pub(crate) fn prepare_credential_env(
}
};
let helper_script = match git_credential::create_credential_helper_script(
app_data_dir,
&app_binary,
) {
Ok(p) => p,
Err(e) => {
eprintln!("[TERM] failed to create credential helper script: {}", e);
return None;
}
};
let helper_script =
match git_credential::create_credential_helper_script(app_data_dir, &app_binary) {
Ok(p) => p,
Err(e) => {
eprintln!("[TERM] failed to create credential helper script: {}", e);
return None;
}
};
let helper_path_str = helper_script.to_string_lossy().to_string();

View File

@@ -24,9 +24,7 @@ async fn run_git_version(git_path: &str) -> Result<GitDetectResult, AppCommandEr
.arg("--version")
.output()
.await
.map_err(|_| {
AppCommandError::not_found(format!("Cannot execute git at: {git_path}"))
})?;
.map_err(|_| AppCommandError::not_found(format!("Cannot execute git at: {git_path}")))?;
if !output.status.success() {
return Ok(GitDetectResult {
@@ -108,9 +106,7 @@ pub(crate) async fn detect_git_core(
#[cfg(feature = "tauri-runtime")]
#[cfg_attr(feature = "tauri-runtime", tauri::command)]
pub async fn detect_git(
db: State<'_, AppDatabase>,
) -> Result<GitDetectResult, AppCommandError> {
pub async fn detect_git(db: State<'_, AppDatabase>) -> Result<GitDetectResult, AppCommandError> {
detect_git_core(&db.conn).await
}
@@ -145,9 +141,7 @@ async fn load_git_settings(
#[cfg(feature = "tauri-runtime")]
#[cfg_attr(feature = "tauri-runtime", tauri::command)]
pub async fn get_git_settings(
db: State<'_, AppDatabase>,
) -> Result<GitSettings, AppCommandError> {
pub async fn get_git_settings(db: State<'_, AppDatabase>) -> Result<GitSettings, AppCommandError> {
load_git_settings(&db.conn).await
}
@@ -221,27 +215,21 @@ pub async fn update_github_accounts(
// ---------------------------------------------------------------------------
#[cfg_attr(feature = "tauri-runtime", tauri::command)]
pub async fn save_account_token(
account_id: String,
token: String,
) -> Result<(), AppCommandError> {
pub async fn save_account_token(account_id: String, token: String) -> Result<(), AppCommandError> {
crate::keyring_store::set_token(&account_id, &token)
.map_err(|e| AppCommandError::io_error("Failed to save token to keyring").with_detail(e))
}
#[cfg_attr(feature = "tauri-runtime", tauri::command)]
pub async fn get_account_token(
account_id: String,
) -> Result<Option<String>, AppCommandError> {
pub async fn get_account_token(account_id: String) -> Result<Option<String>, AppCommandError> {
Ok(crate::keyring_store::get_token(&account_id))
}
#[cfg_attr(feature = "tauri-runtime", tauri::command)]
pub async fn delete_account_token(
account_id: String,
) -> Result<(), AppCommandError> {
crate::keyring_store::delete_token(&account_id)
.map_err(|e| AppCommandError::io_error("Failed to delete token from keyring").with_detail(e))
pub async fn delete_account_token(account_id: String) -> Result<(), AppCommandError> {
crate::keyring_store::delete_token(&account_id).map_err(|e| {
AppCommandError::io_error("Failed to delete token from keyring").with_detail(e)
})
}
// ---------------------------------------------------------------------------
@@ -283,7 +271,9 @@ pub async fn validate_github_token(
.header("Accept", "application/vnd.github+json")
.send()
.await
.map_err(|e| AppCommandError::network("Failed to connect to GitHub API").with_detail(e.to_string()))?;
.map_err(|e| {
AppCommandError::network("Failed to connect to GitHub API").with_detail(e.to_string())
})?;
if !response.status().is_success() {
let status = response.status().as_u16();
@@ -315,13 +305,9 @@ pub async fn validate_github_token(
})
.unwrap_or_default();
let user = response
.json::<GitHubUserResponse>()
.await
.map_err(|e| {
AppCommandError::network("Failed to parse GitHub API response")
.with_detail(e.to_string())
})?;
let user = response.json::<GitHubUserResponse>().await.map_err(|e| {
AppCommandError::network("Failed to parse GitHub API response").with_detail(e.to_string())
})?;
Ok(GitHubTokenValidation {
success: true,

View File

@@ -1,15 +1,15 @@
use std::collections::HashMap;
use std::sync::Mutex;
use std::sync::atomic::{AtomicU8, Ordering as AtomicOrdering};
#[cfg(target_os = "macos")]
use std::sync::atomic::AtomicU32;
use std::sync::atomic::{AtomicU8, Ordering as AtomicOrdering};
use std::sync::Mutex;
use sea_orm::DatabaseConnection;
use tauri::{AppHandle, Manager, WebviewUrl, WebviewWindowBuilder};
use crate::app_error::AppCommandError;
use crate::db::AppDatabase;
use crate::db::service::app_metadata_service;
use crate::db::AppDatabase;
use crate::models::FolderDetail;
/// Base traffic-light position (logical px) at 100 % zoom.
@@ -123,12 +123,15 @@ fn is_system_dark_mode() -> bool {
// Output: " AppsUseLightTheme REG_DWORD 0x0"
// Extract the last token on the matching line to avoid
// substring false-positives (e.g. "0x00000001" contains "0x0").
stdout.lines().find(|l| l.contains("AppsUseLightTheme")).map(|line| {
line.split_whitespace()
.last()
.map(|val| val == "0x0" || val == "0x00000000")
.unwrap_or(false)
})
stdout
.lines()
.find(|l| l.contains("AppsUseLightTheme"))
.map(|line| {
line.split_whitespace()
.last()
.map(|val| val == "0x0" || val == "0x00000000")
.unwrap_or(false)
})
})
.unwrap_or(false)
})
@@ -263,6 +266,12 @@ impl SettingsWindowState {
}
}
impl Default for SettingsWindowState {
fn default() -> Self {
Self::new()
}
}
impl CommitWindowState {
pub fn new() -> Self {
Self {
@@ -284,6 +293,12 @@ impl CommitWindowState {
}
}
impl Default for CommitWindowState {
fn default() -> Self {
Self::new()
}
}
fn resolve_settings_route(section: Option<&str>) -> &'static str {
match section {
Some("appearance") => "settings/appearance",
@@ -508,6 +523,12 @@ impl MergeWindowState {
}
}
impl Default for MergeWindowState {
fn default() -> Self {
Self::new()
}
}
#[cfg(feature = "tauri-runtime")]
#[cfg_attr(feature = "tauri-runtime", tauri::command)]
pub async fn open_merge_window(
@@ -738,11 +759,9 @@ pub async fn open_project_boot_window(
.inner_size(1400.0, 900.0)
.min_inner_size(1100.0, 700.0)
.center();
let window = apply_platform_window_style(builder)
.build()
.map_err(|e| {
AppCommandError::window("Failed to open project boot window", e.to_string())
})?;
let window = apply_platform_window_style(builder).build().map_err(|e| {
AppCommandError::window("Failed to open project boot window", e.to_string())
})?;
post_window_setup(&window);
Ok(())
@@ -764,12 +783,8 @@ pub async fn update_traffic_light_position(
CURRENT_ZOOM.store(clamped, AtomicOrdering::Relaxed);
// Persist to DB so the next launch reads the correct value.
let _ = app_metadata_service::upsert_value(
&db.conn,
ZOOM_LEVEL_DB_KEY,
&clamped.to_string(),
)
.await;
let _ =
app_metadata_service::upsert_value(&db.conn, ZOOM_LEVEL_DB_KEY, &clamped.to_string()).await;
let _ = app;
Ok(())
@@ -786,13 +801,7 @@ pub async fn update_appearance_mode(
) -> Result<(), AppCommandError> {
CACHED_APPEARANCE_MODE.store(mode_from_str(&mode), AtomicOrdering::Relaxed);
let _ = app_metadata_service::upsert_value(
&db.conn,
APPEARANCE_MODE_DB_KEY,
&mode,
)
.await;
let _ = app_metadata_service::upsert_value(&db.conn, APPEARANCE_MODE_DB_KEY, &mode).await;
Ok(())
}