修复客户端 Rust 格式检查失败
格式化客户端扩展实现以通过 cargo fmt 检查 同步整理客户端扩展模块在 main.rs 中的声明与导入顺序
This commit is contained in:
@@ -1083,55 +1083,55 @@ pub(crate) fn prepare_enabled_client_skill_root(
|
||||
|
||||
pub(crate) fn prepare_enabled_client_mcp_servers() -> Result<Vec<ClientMcpRuntimeServer>, 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<String, BTreeSet<String>> = BTreeMap::new();
|
||||
for item in &index.items {
|
||||
let mut used_names: BTreeMap<String, BTreeSet<String>> = 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<ClientExtensionItem, String> {
|
||||
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<ClientExtensionItem, 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())
|
||||
.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::<BTreeSet<_>>();
|
||||
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::<BTreeSet<_>>();
|
||||
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::<BTreeSet<_>>();
|
||||
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
|
||||
|
||||
@@ -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::*;
|
||||
|
||||
Reference in New Issue
Block a user