fix(tui): a default-model persist failure no longer reverts the live model
Persist-failure ≠ switch-failure. The default_model rollback arm re-ran
set_default_model_inner(prev) AND issued a reverse SwitchModel whenever
the config.toml write failed — the only mechanism in the codebase that
deliberately re-shows the ORIGINAL model after a successful pick. On
Windows, where AV/indexer file locks routinely failed that write (until
27d009c), every /model selection appeared to not take.
The session switch succeeds independently and reports its own failures
via handle_switch_model_complete; a disk-persist failure now keeps the
live model, logs, and surfaces only the 'Could not save' toast — the
same policy PersistPreferredModel already ships ('still active for this
session').
Verified: kigi-tui 6874 tests green, clippy clean.
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
use super::setters::{
|
||||
pr13_effective_default, set_ask_user_question_timeout_enabled_inner, set_auto_dark_theme_inner,
|
||||
set_auto_light_theme_inner, set_auto_update_inner, set_collapsed_edit_blocks_inner,
|
||||
set_compact_mode, set_compact_mode_inner, set_contextual_hint_inner, set_default_model_inner,
|
||||
set_compact_mode, set_compact_mode_inner, set_contextual_hint_inner,
|
||||
set_default_selected_permission_inner, set_display_refresh_auto_cadence_inner,
|
||||
set_fork_secondary_model_inner, set_group_tool_verbs_inner, set_hunk_tracker_mode_inner,
|
||||
set_invert_scroll_inner, set_keep_text_selection_inner, set_max_thoughts_width_inner,
|
||||
@@ -844,7 +844,7 @@ pub(in crate::app::dispatch) fn apply_setting_rollback(
|
||||
rollback_value: &crate::settings::SettingValue,
|
||||
) -> Vec<Effect> {
|
||||
use crate::settings::SettingValue;
|
||||
let mut companion_effects: Vec<Effect> = Vec::new();
|
||||
let companion_effects: Vec<Effect> = Vec::new();
|
||||
match (key, rollback_value) {
|
||||
("compact_mode", SettingValue::Bool(b)) => set_compact_mode_inner(app, *b),
|
||||
("show_timestamps", SettingValue::Bool(b)) => set_timestamps_inner(app, *b),
|
||||
@@ -934,65 +934,24 @@ pub(in crate::app::dispatch) fn apply_setting_rollback(
|
||||
// other rollback arms must not clobber it from the global canonical.
|
||||
sync_active_auto_flag(app);
|
||||
}
|
||||
// default_model: best-effort rollback. If the prior model no
|
||||
// longer resolves, leave optimistic value + log.
|
||||
("default_model", SettingValue::String(s)) => {
|
||||
if s.is_empty() {
|
||||
tracing::warn!(
|
||||
target: "settings",
|
||||
key = "default_model",
|
||||
"rollback to empty string requested but no \
|
||||
'clear current model' API exists — leaving live \
|
||||
state at optimistic value (next session reload \
|
||||
will resolve via shell default-resolution chain)",
|
||||
);
|
||||
} else {
|
||||
// Resolve the prior model ID back to a ModelId
|
||||
// and call the typed inner. If resolution fails
|
||||
// (catalog changed mid-flight), log + leave
|
||||
// optimistic.
|
||||
let (resolved, session_id) = if let ActiveView::Agent(aid) = app.active_view
|
||||
&& let Some(agent) = app.agents.get(&aid)
|
||||
{
|
||||
(
|
||||
agent.session.models.resolve_by_name_or_id(s),
|
||||
agent.session.session_id.clone(),
|
||||
)
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
match resolved {
|
||||
Some(id) => {
|
||||
let _ = set_default_model_inner(app, &id);
|
||||
// Emit reverse SwitchModel so the ACP session
|
||||
// matches the rolled-back pager mirror.
|
||||
if let ActiveView::Agent(aid) = app.active_view
|
||||
&& let Some(sid) = session_id
|
||||
{
|
||||
if let Some(agent) = app.agents.get_mut(&aid) {
|
||||
agent.session.model_switch_pending = true;
|
||||
}
|
||||
companion_effects.push(Effect::SwitchModel {
|
||||
agent_id: aid,
|
||||
session_id: sid,
|
||||
model_id: id,
|
||||
effort: None,
|
||||
prev_model_id: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
None => {
|
||||
tracing::warn!(
|
||||
target: "settings",
|
||||
key = "default_model",
|
||||
value = %s,
|
||||
"rollback model id no longer resolves in catalog — \
|
||||
in-memory state stays at optimistic value; ACP session \
|
||||
may diverge from pager mirror until next setter dispatch",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
// default_model: deliberately NO revert. The session switch already
|
||||
// succeeded independently (its own failure path reports via
|
||||
// `handle_switch_model_complete`); this arm fires when only the
|
||||
// DISK write of the next-launch default failed — which must not
|
||||
// undo a working switch. Same policy as `PersistPreferredModel`
|
||||
// ("still active for this session"). Regression: reverting here
|
||||
// (plus a reverse SwitchModel) made every picker selection appear
|
||||
// to not take on Windows, where AV/indexer file locks routinely
|
||||
// fail config.toml persists.
|
||||
("default_model", SettingValue::String(prior)) => {
|
||||
tracing::warn!(
|
||||
target: "settings",
|
||||
key = "default_model",
|
||||
prior = %prior,
|
||||
"default-model persist failed; keeping the live session's \
|
||||
model (the switch succeeded) — only the next-launch \
|
||||
default is unsaved",
|
||||
);
|
||||
}
|
||||
// max_thoughts_width: direct inner call.
|
||||
("max_thoughts_width", SettingValue::Int(i)) => set_max_thoughts_width_inner(app, *i),
|
||||
|
||||
Reference in New Issue
Block a user