fix(agents): record model mapping changes without file changes

Include Claude Desktop mappings in history change detection and preserve the previous selection. Show mapping changes in history previews and invalidate previews when metadata changes.

Validation: 15 history tests pass, including identical-profile model changes and repeated no-op saves.
This commit is contained in:
lzt404 committed 2026-09-11 16:25:14 +08:00
1 parent 0a019ad1d7
commit 60a3683976
2 files changed
+68 -4

No files matched your search

+27 -4
View File
@@ -610,7 +610,9 @@ fn commit_history_with_writer(
if image_revision(&history_images(paths)?) != image_revision(before) {
return Err("配置已被其他程序修改,请刷新后重试".into());
}
if changed.is_empty() {
let prior_mappings = matching_desktop_mappings(client, paths, before);
let next_mappings = mappings.or_else(|| matching_desktop_mappings(client, paths, after));
if changed.is_empty() && prior_mappings == next_mappings {
return Ok(action_result(
"unchanged",
true,
@@ -620,9 +622,12 @@ fn commit_history_with_writer(
));
}
let mut prior = make_version(client, paths, before, &format!("before-{source}"), None);
prior.mappings = matching_desktop_mappings(client, paths, before);
prior.mappings = prior_mappings;
if let Some(mappings) = prior.mappings.as_ref() {
prior.model = Some(mappings.opus.clone());
}
let mut next = make_version(client, paths, after, source, model.clone());
next.mappings = mappings.or_else(|| matching_desktop_mappings(client, paths, after));
next.mappings = next_mappings;
write_version(paths, &prior)?;
if image_revision(&history_images(paths)?) != image_revision(before) {
return Err("备份期间配置发生变化,请刷新后重试".into());
@@ -823,9 +828,27 @@ fn preview(
});
}
}
let current_mappings = matching_desktop_mappings(client, paths, &current);
if client == "claude-desktop" && current_mappings != version.mappings {
let before = serde_json::to_value(&current_mappings).map_err(|e| e.to_string())?;
let after = serde_json::to_value(&version.mappings).map_err(|e| e.to_string())?;
let mut fields = BTreeSet::new();
collect_changes(Some(&before), Some(&after), Vec::new(), &mut fields);
for field in fields {
differences.push(HistoryDifference {
file: path_to_string(&paths[2]),
field: format!("modelMappings.{}", field.join(".")),
before: hidden(get(&before, &field), &field),
after: hidden(get(&after, &field), &field),
});
}
}
let revision = sha256_bytes(
format!("{}:{}", image_revision(&current), serde_json::to_string(&current_mappings).map_err(|e| e.to_string())?).as_bytes(),
);
Ok((
HistoryPreview {
revision: image_revision(&current),
revision,
differences,
},
current,
+41
View File
@@ -405,3 +405,44 @@ fn directory_at_config_path_is_an_error() {
fs::create_dir_all(&paths[0]).unwrap();
assert!(history_images(&paths).is_err());
}
fn desktop_test_models() -> Vec<AgentModelOption> {
["model-a", "model-b"].into_iter().map(|name| AgentModelOption {
name: name.into(), alias: None, is_alias: false, context_window: Some(200_000),
}).collect()
}
fn apply_desktop_test_model(home: &Path, model: &str) -> AgentConfigActionResult {
let mappings = ClaudeDesktopModelMappings::all(model);
apply_agent_configuration_with_oauth(
AgentClient::ClaudeDesktop, home, 8317, "test-key", model,
AgentConfigurationOptions {
models: &desktop_test_models(), codex_catalog: None, oauth_configuration: false,
claude_code_model_mappings: None, claude_desktop_model_mappings: Some(&mappings),
},
).unwrap()
}
#[test]
fn desktop_mapping_changes_are_saved_when_profile_bytes_are_identical() {
let home = Home::new();
let paths = history_paths("claude-desktop", &home.0).unwrap();
let first = apply_desktop_test_model(&home.0, "model-a");
let images = history_images(&paths).unwrap();
let first_id = first.history_version.unwrap();
let original_preview = preview("claude-desktop", &paths, &first_id).unwrap().0;
let second = apply_desktop_test_model(&home.0, "model-b");
assert_eq!(second.outcome, "updated");
assert!(second.changed_files.is_empty());
assert!(second.history_version.is_some());
assert_eq!(images, history_images(&paths).unwrap());
assert_eq!(versions("claude-desktop", &paths).unwrap().0.len(), 4);
assert_eq!(current_desktop_history_mappings(&home.0).unwrap().opus, "model-b");
let next_preview = preview("claude-desktop", &paths, &first_id).unwrap().0;
assert_ne!(original_preview.revision, next_preview.revision);
assert!(next_preview.differences.iter().any(|diff| diff.field == "modelMappings.opus"));
let unchanged = apply_desktop_test_model(&home.0, "model-b");
assert_eq!(unchanged.outcome, "unchanged");
assert!(unchanged.history_version.is_none());
assert_eq!(versions("claude-desktop", &paths).unwrap().0.len(), 4);
}