diff --git a/apps/ai-game-creator-shell/src-tauri/src/client_extensions.rs b/apps/ai-game-creator-shell/src-tauri/src/client_extensions.rs index e146f41e7..5cf4c6f12 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/client_extensions.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/client_extensions.rs @@ -1083,55 +1083,55 @@ pub(crate) fn prepare_enabled_client_skill_root( pub(crate) fn prepare_enabled_client_mcp_servers() -> Result, String> { update_client_extension_index(|root, index| { - let mut servers = Vec::new(); - let mut index_changed = false; + let mut servers = Vec::new(); + let mut index_changed = false; - for item_index in 0..index.items.len() { - if index.items[item_index].extension_type != "mcp" || !index.items[item_index].enabled { - continue; - } - let source = index - .sources - .iter() - .find(|source| source.id == index.items[item_index].source_id) - .cloned(); - let result = (|| { - if index.items[item_index].name == RESERVED_MCP_SERVER_NAME { - return Err("MCP Server 名称 agc_tools 为客户端内置保留名称".to_string()); + for item_index in 0..index.items.len() { + if index.items[item_index].extension_type != "mcp" || !index.items[item_index].enabled { + continue; } - let source = source.ok_or_else(|| "MCP Server 缺少导入来源".to_string())?; - let config = index.items[item_index] - .mcp_config - .as_ref() - .ok_or_else(|| "MCP Server 缺少原生配置".to_string())?; - let source_file = root - .join(&source.storage_path) - .join(&index.items[item_index].source_relative_path); - let source_directory = source_file - .parent() - .ok_or_else(|| "MCP Server 来源路径无效".to_string())?; - normalize_mcp_runtime_config(config, source_directory) - })(); - match result { - Ok(config) => { - if index.items[item_index].last_error.take().is_some() { - index_changed = true; + let source = index + .sources + .iter() + .find(|source| source.id == index.items[item_index].source_id) + .cloned(); + let result = (|| { + if index.items[item_index].name == RESERVED_MCP_SERVER_NAME { + return Err("MCP Server 名称 agc_tools 为客户端内置保留名称".to_string()); } - servers.push(ClientMcpRuntimeServer { - extension_id: index.items[item_index].id.clone(), - name: index.items[item_index].name.clone(), - config, - }); - } - Err(error) => { - if index.items[item_index].last_error.as_deref() != Some(error.as_str()) { - index.items[item_index].last_error = Some(error); - index_changed = true; + let source = source.ok_or_else(|| "MCP Server 缺少导入来源".to_string())?; + let config = index.items[item_index] + .mcp_config + .as_ref() + .ok_or_else(|| "MCP Server 缺少原生配置".to_string())?; + let source_file = root + .join(&source.storage_path) + .join(&index.items[item_index].source_relative_path); + let source_directory = source_file + .parent() + .ok_or_else(|| "MCP Server 来源路径无效".to_string())?; + normalize_mcp_runtime_config(config, source_directory) + })(); + match result { + Ok(config) => { + if index.items[item_index].last_error.take().is_some() { + index_changed = true; + } + servers.push(ClientMcpRuntimeServer { + extension_id: index.items[item_index].id.clone(), + name: index.items[item_index].name.clone(), + config, + }); + } + Err(error) => { + if index.items[item_index].last_error.as_deref() != Some(error.as_str()) { + index.items[item_index].last_error = Some(error); + index_changed = true; + } } } } - } - Ok((servers, index_changed)) + Ok((servers, index_changed)) }) } @@ -1341,76 +1341,79 @@ fn import_client_extension_at( fingerprint: source_fingerprint, }; update_client_extension_index_at(&root, |_, index| { - let mut used_names: BTreeMap> = BTreeMap::new(); - for item in &index.items { + let mut used_names: BTreeMap> = BTreeMap::new(); + for item in &index.items { + used_names + .entry(item.extension_type.clone()) + .or_default() + .insert(item.name.clone()); + } used_names - .entry(item.extension_type.clone()) + .entry("mcp".to_string()) .or_default() - .insert(item.name.clone()); - } - used_names - .entry("mcp".to_string()) - .or_default() - .insert(RESERVED_MCP_SERVER_NAME.to_string()); - let mut imported = Vec::new(); - let mut renamed = false; - let mut duplicate = false; - for candidate in candidates { - let extension_type = candidate.extension_type; - let enabled = extension_type != "unknown"; - let duplicate_name = index - .items - .iter() - .find(|item| { - item.extension_type == extension_type - && item.fingerprint == candidate.fingerprint - }) - .map(|item| item.name.clone()); - duplicate |= duplicate_name.is_some(); - let names = used_names.entry(extension_type.clone()).or_default(); - let requested_name = duplicate_name - .as_deref() - .unwrap_or(&candidate.original_name); - let (name, was_renamed) = if extension_type == "skill" { - allocate_skill_name(requested_name, names) - } else { - allocate_name(requested_name, names) - }; - renamed |= was_renamed; - let stored = StoredExtensionItem { - id: new_id("extension"), - source_id: source_id.clone(), - extension_type, - name, - original_name: candidate.original_name, - source_relative_path: candidate.source_relative_path, - enabled, - fingerprint: candidate.fingerprint, - last_error: None, - mcp_config: candidate.mcp_config, - }; - index.items.push(stored); - imported.push(stored_item_view( - &ClientExtensionIndex { - schema_version: index.schema_version.clone(), - sources: index - .sources - .iter() - .cloned() - .chain(std::iter::once(source_record.clone())) - .collect(), - items: index.items.clone(), + .insert(RESERVED_MCP_SERVER_NAME.to_string()); + let mut imported = Vec::new(); + let mut renamed = false; + let mut duplicate = false; + for candidate in candidates { + let extension_type = candidate.extension_type; + let enabled = extension_type != "unknown"; + let duplicate_name = index + .items + .iter() + .find(|item| { + item.extension_type == extension_type + && item.fingerprint == candidate.fingerprint + }) + .map(|item| item.name.clone()); + duplicate |= duplicate_name.is_some(); + let names = used_names.entry(extension_type.clone()).or_default(); + let requested_name = duplicate_name + .as_deref() + .unwrap_or(&candidate.original_name); + let (name, was_renamed) = if extension_type == "skill" { + allocate_skill_name(requested_name, names) + } else { + allocate_name(requested_name, names) + }; + renamed |= was_renamed; + let stored = StoredExtensionItem { + id: new_id("extension"), + source_id: source_id.clone(), + extension_type, + name, + original_name: candidate.original_name, + source_relative_path: candidate.source_relative_path, + enabled, + fingerprint: candidate.fingerprint, + last_error: None, + mcp_config: candidate.mcp_config, + }; + index.items.push(stored); + imported.push(stored_item_view( + &ClientExtensionIndex { + schema_version: index.schema_version.clone(), + sources: index + .sources + .iter() + .cloned() + .chain(std::iter::once(source_record.clone())) + .collect(), + items: index.items.clone(), + }, + index.items.last().expect("stored extension item"), + )); + } + index.sources.push(source_record); + Ok(( + ClientExtensionImportResult { + imported, + source_name: source_display_name, + renamed, + duplicate, }, - index.items.last().expect("stored extension item"), - )); - } - index.sources.push(source_record); - Ok((ClientExtensionImportResult { - imported, - source_name: source_display_name, - renamed, - duplicate, - }, true)) + true, + )) }) })(); let _ = fs::remove_dir_all(&staging); @@ -1433,7 +1436,10 @@ fn set_client_extension_enabled_at( ) -> Result { invalidate_client_mcp_connection(id.trim())?; update_client_extension_index_at(root, |_, index| { - let item = index.items.iter_mut().find(|item| item.id == id.trim()) + let item = index + .items + .iter_mut() + .find(|item| item.id == id.trim()) .ok_or_else(|| "未找到客户端扩展".to_string())?; if item.extension_type == "unknown" && enabled { return Err("未识别扩展不能启用".to_string()); @@ -1466,40 +1472,43 @@ fn rename_client_extension_at( ) -> Result { invalidate_client_mcp_connection(id.trim())?; update_client_extension_index_at(root, |_, index| { - let item_index = index.items.iter().position(|item| item.id == id.trim()) - .ok_or_else(|| "未找到客户端扩展".to_string())?; - let extension_type = index.items[item_index].extension_type.clone(); - let conflict = (extension_type == "mcp" && normalized == RESERVED_MCP_SERVER_NAME) - || index.items.iter().enumerate().any(|(index, item)| { - index != item_index - && item.extension_type == extension_type - && if extension_type == "skill" { - item.name.eq_ignore_ascii_case(&normalized) - } else { - item.name == normalized - } - }); - let final_name = if conflict { - let mut names = index + let item_index = index .items .iter() - .filter(|item| item.extension_type == extension_type) - .map(|item| item.name.clone()) - .collect::>(); - if extension_type == "mcp" { - names.insert(RESERVED_MCP_SERVER_NAME.to_string()); - } - if extension_type == "skill" { - allocate_skill_name(&normalized, &mut names).0 + .position(|item| item.id == id.trim()) + .ok_or_else(|| "未找到客户端扩展".to_string())?; + let extension_type = index.items[item_index].extension_type.clone(); + let conflict = (extension_type == "mcp" && normalized == RESERVED_MCP_SERVER_NAME) + || index.items.iter().enumerate().any(|(index, item)| { + index != item_index + && item.extension_type == extension_type + && if extension_type == "skill" { + item.name.eq_ignore_ascii_case(&normalized) + } else { + item.name == normalized + } + }); + let final_name = if conflict { + let mut names = index + .items + .iter() + .filter(|item| item.extension_type == extension_type) + .map(|item| item.name.clone()) + .collect::>(); + if extension_type == "mcp" { + names.insert(RESERVED_MCP_SERVER_NAME.to_string()); + } + if extension_type == "skill" { + allocate_skill_name(&normalized, &mut names).0 + } else { + allocate_name(&normalized, &mut names).0 + } } else { - allocate_name(&normalized, &mut names).0 - } - } else { - normalized.to_string() - }; - index.items[item_index].name = final_name; - let view_item = index.items[item_index].clone(); - Ok((stored_item_view(index, &view_item), true)) + normalized.to_string() + }; + index.items[item_index].name = final_name; + let view_item = index.items[item_index].clone(); + Ok((stored_item_view(index, &view_item), true)) }) } @@ -1512,7 +1521,10 @@ pub(crate) fn remove_client_extension(id: String) -> Result<(), String> { fn remove_client_extension_at(root: &Path, id: &str) -> Result<(), String> { invalidate_client_mcp_connection(id.trim())?; update_client_extension_index_at(root, |_, index| { - let item_index = index.items.iter().position(|item| item.id == id.trim()) + let item_index = index + .items + .iter() + .position(|item| item.id == id.trim()) .ok_or_else(|| "未找到客户端扩展".to_string())?; index.items.remove(item_index); Ok(((), true)) @@ -1616,7 +1628,10 @@ mod tests { }) .collect::>(); if case.id == "unknown" { - assert!(candidates.is_empty(), "unknown fixture must have no candidates"); + assert!( + candidates.is_empty(), + "unknown fixture must have no candidates" + ); assert_eq!(expected, BTreeSet::from([("unknown", "unknown.bin")])); } else { assert_eq!(actual, expected, "fixture case {}", case.id); @@ -1690,14 +1705,15 @@ mod tests { let disabled = set_client_extension_enabled_at(&root, &first_id, false) .expect("disable imported skill"); assert_eq!(disabled.status, "disabled"); - let enabled = set_client_extension_enabled_at(&root, &first_id, true) - .expect("enable imported skill"); + let enabled = + set_client_extension_enabled_at(&root, &first_id, true).expect("enable imported skill"); assert_eq!(enabled.status, "enabled"); - remove_client_extension_at(&root, &second.imported[0].id) - .expect("remove duplicate skill"); + remove_client_extension_at(&root, &second.imported[0].id).expect("remove duplicate skill"); let remaining = list_client_extensions_at(&root).expect("list remaining extensions"); assert!(remaining.iter().any(|item| item.id == first_id)); - assert!(!remaining.iter().any(|item| item.id == second.imported[0].id)); + assert!(!remaining + .iter() + .any(|item| item.id == second.imported[0].id)); } #[test] @@ -2049,46 +2065,23 @@ mod tests { }) .expect("insert MCP extension"); - claim_client_mcp_connection_at( - &root, - [extension_id.to_string()], - "connection-old", - ) - .expect("claim old MCP connection"); - record_client_mcp_startup_status_at( - &root, - extension_id, - "connection-old", - "failed", - ) - .expect("record old failure"); + claim_client_mcp_connection_at(&root, [extension_id.to_string()], "connection-old") + .expect("claim old MCP connection"); + record_client_mcp_startup_status_at(&root, extension_id, "connection-old", "failed") + .expect("record old failure"); - claim_client_mcp_connection_at( - &root, - [extension_id.to_string()], - "connection-new", - ) - .expect("claim new MCP connection"); - record_client_mcp_startup_status_at( - &root, - extension_id, - "connection-old", - "ready", - ) - .expect("ignore stale ready status"); + claim_client_mcp_connection_at(&root, [extension_id.to_string()], "connection-new") + .expect("claim new MCP connection"); + record_client_mcp_startup_status_at(&root, extension_id, "connection-old", "ready") + .expect("ignore stale ready status"); let index = read_index(&root).expect("read status after stale callback"); assert_eq!( index.items[0].last_error.as_deref(), Some("MCP Server 启动失败") ); - record_client_mcp_startup_status_at( - &root, - extension_id, - "connection-new", - "ready", - ) - .expect("record current ready status"); + record_client_mcp_startup_status_at(&root, extension_id, "connection-new", "ready") + .expect("record current ready status"); let index = read_index(&root).expect("read status after current callback"); assert_eq!(index.items[0].last_error, None); release_client_mcp_connection([extension_id.to_string()], "connection-new") @@ -2140,7 +2133,10 @@ mod tests { }) }); left.join().expect("join left update").expect("left update"); - right.join().expect("join right update").expect("right update"); + right + .join() + .expect("join right update") + .expect("right update"); let index = read_index(&root).expect("read serialized index"); let ids = index diff --git a/apps/ai-game-creator-shell/src-tauri/src/main.rs b/apps/ai-game-creator-shell/src-tauri/src/main.rs index 5e636cd14..73b2d42ec 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/main.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/main.rs @@ -50,12 +50,12 @@ mod agent_native_tools; mod assets; mod browser; mod cli; +mod client_extensions; mod collaboration; mod command_exec; mod command_output; mod command_sandbox; mod command_sandbox_trampoline; -mod client_extensions; mod commands; mod config; mod context_compaction; @@ -89,11 +89,11 @@ use agent_native_tools::*; use assets::*; use browser::*; use cli::*; +use client_extensions::*; use collaboration::*; use command_exec::*; use command_output::*; use command_sandbox::*; -use client_extensions::*; use commands::*; use config::*; use context_compaction::*;