Wave 3 返工:修复总工 4 条必改(cursor/goose 脚本 URL、goose 配置路径按平台选型、yaml+crushrc 编解码)

Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
leefer
2026-08-25 19:34:10 +08:00
co-authored by multica-agent
parent b0d7124170
commit 0cec468ae6
9 changed files with 271 additions and 35 deletions
+63 -6
View File
@@ -476,6 +476,19 @@ fn generic_extract_version(output: &str) -> Option<String> {
// 配置读写
// =====================================================================
/// 判断配置文件是否适用于当前平台(未声明 platforms 视为全平台适用)。
/// 用于同一 CLI 在不同平台路径不同的场景(如 GooseWindows %APPDATA%… / Linux ~/.config/…)。
fn file_applies_to_current_platform(f: &agentdock_adapter::schema::ConfigFile) -> bool {
if f.platforms.is_empty() {
return true;
}
#[cfg(windows)]
let cur = "windows";
#[cfg(not(windows))]
let cur = "linux";
f.platforms.iter().any(|p| p == cur)
}
/// 读取适配器配置,产出表单状态。
pub fn read_config(adapter: &Adapter, secrets: &dyn SecretStore) -> Result<ConfigFormState, EngineError> {
let cli_id = adapter.id.clone();
@@ -486,8 +499,8 @@ pub fn read_config(adapter: &Adapter, secrets: &dyn SecretStore) -> Result<Confi
let mut environment = Vec::new();
if let Some(cfg) = cfg_opt {
// 文件解析状态
for f in &cfg.files {
// 文件解析状态(仅当前平台适用的配置文件)
for f in cfg.files.iter().filter(|f| file_applies_to_current_platform(f)) {
let path = cfg::resolve_path(&f.path);
let exists = path.exists();
let (parse_ok, error) = if exists {
@@ -578,7 +591,7 @@ fn read_file_field(adapter: &Adapter, field_id: &str) -> Result<Option<String>,
let Some(cfg) = adapter.configuration.as_ref() else {
return Ok(None);
};
for f in &cfg.files {
for f in cfg.files.iter().filter(|f| file_applies_to_current_platform(f)) {
let path = cfg::resolve_path(&f.path);
let Ok(text) = std::fs::read_to_string(&path) else {
continue;
@@ -642,7 +655,10 @@ pub fn write_config(
}
}
"file" | "env" => {
if let Some(idx) = cfg.files.iter().position(|f| matches!(f.scope.as_deref(), Some("user") | None)) {
if let Some(idx) = cfg.files.iter().position(|f| {
file_applies_to_current_platform(f)
&& matches!(f.scope.as_deref(), Some("user") | None)
}) {
file_patches.entry(idx).or_default().push((field.id.clone(), value.clone()));
} else {
errors.push(format!("{} 未声明可写的配置文件", field.label_zh));
@@ -694,7 +710,7 @@ pub fn verify_config(adapter: &Adapter) -> ConfigVerifyResult {
// 1. 重读配置文件并解析(有配置文件声明时)
if let Some(cfg) = adapter.configuration.as_ref() {
for f in &cfg.files {
for f in cfg.files.iter().filter(|f| file_applies_to_current_platform(f)) {
let path = cfg::resolve_path(&f.path);
if !path.exists() {
continue;
@@ -980,7 +996,7 @@ pub fn diagnose(
// 4. 配置损坏类
if let Some(cfg) = adapter.configuration.as_ref() {
for f in &cfg.files {
for f in cfg.files.iter().filter(|f| file_applies_to_current_platform(f)) {
let path = cfg::resolve_path(&f.path);
if !path.exists() {
continue;
@@ -1334,6 +1350,47 @@ configuration:
let _ = std::fs::remove_dir_all(&tmp);
}
#[test]
fn write_goose_yaml_config_fields() {
// goose 用 yaml 配置 + files[].platforms 按平台选型;验证 yaml 编解码已接进 write_config
let dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../../adapters");
let adapters = load_adapters(&dir).expect("真实适配器目录应可加载");
let mut adapter = adapters.into_iter().find(|a| a.id == "goose").unwrap();
let tmp = std::env::temp_dir().join(format!("agentdock-core-goose-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&tmp);
std::fs::create_dir_all(&tmp).unwrap();
let file = tmp.join("config.yaml");
if let Some(c) = adapter.configuration.as_mut() {
for f in &mut c.files {
if file_applies_to_current_platform(f) {
f.path = file.to_string_lossy().to_string();
}
}
}
let secrets = MockSecretStore::new();
let mut patch = std::collections::BTreeMap::new();
patch.insert("GOOSE_PROVIDER".to_string(), "anthropic".to_string());
patch.insert("GOOSE_MODEL".to_string(), "claude-sonnet-4".to_string());
patch.insert("api_key".to_string(), "sk-test-xxx".to_string());
let res = write_config(&adapter, &patch, &secrets).unwrap();
assert_eq!(res.written_fields.len(), 3);
assert!(res.written_file.is_some(), "应写入当前平台配置文件");
let text = std::fs::read_to_string(&file).unwrap();
assert!(text.contains("anthropic"), "provider 应写入 yaml: {text}");
assert!(text.contains("claude-sonnet-4"), "model 应写入 yaml: {text}");
assert!(!text.contains("sk-test-xxx"), "API Key 不得写入配置文件");
assert!(secrets.has(&service_name("goose"), "api_key"));
let state = read_config(&adapter, &secrets).unwrap();
let provider = state.fields.iter().find(|f| f.id == "GOOSE_PROVIDER").unwrap();
assert_eq!(provider.value.as_deref(), Some("anthropic"));
let _ = std::fs::remove_dir_all(&tmp);
}
#[test]
fn write_config_creates_backup_on_second_write() {
let adapter = codex_adapter();