Wave 3 返工:修复总工 4 条必改(cursor/goose 脚本 URL、goose 配置路径按平台选型、yaml+crushrc 编解码)
Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
@@ -476,6 +476,19 @@ fn generic_extract_version(output: &str) -> Option<String> {
|
||||
// 配置读写
|
||||
// =====================================================================
|
||||
|
||||
/// 判断配置文件是否适用于当前平台(未声明 platforms 视为全平台适用)。
|
||||
/// 用于同一 CLI 在不同平台路径不同的场景(如 Goose:Windows %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();
|
||||
|
||||
Reference in New Issue
Block a user