feat(settings): per-model effort and turn presets

ClaudeDo never passed --effort, so every session inherited whatever effortLevel
the user's Claude Code config happened to carry. Settings -> General now holds one
row per model alias (haiku medium/20, sonnet high/30, opus high/40, fable high/25)
supplying the global effort and turn defaults; list- and task-level max-turns
overrides still win, and the agent editor's inherited badge follows the model.

--effort is applied to autonomous runs and to every ConPTY spec (task session,
planning start/resume, ad-hoc, list handler). The model itself is deliberately not
forced on interactive sessions. The single global 'Max turns' field is replaced by
the table, and 'fable' joins ModelRegistry.Aliases.

The migration also adds the is_manual columns used by the next commit.
This commit is contained in:
Mika Kuns
2026-07-27 15:02:51 +02:00
parent c93a20f20c
commit fde9615b34
27 changed files with 1318 additions and 40 deletions
@@ -47,7 +47,11 @@ public sealed partial class AgentConfigEditorViewModel : ViewModelBase, IDisposa
[ObservableProperty] private string _effectiveSystemPromptHint = "";
private string _globalModel = ModelRegistry.DefaultAlias;
private int _globalMaxTurns = 100;
// The global max-turns default is per-model (Settings -> General), so it moves with whichever
// model actually ends up in effect here.
private IReadOnlyList<ModelPreset> _presets = ModelPresets.Defaults;
private string EffectiveModel => Model ?? _listModel ?? _globalModel;
private int GlobalMaxTurns => ModelPresets.For(_presets, EffectiveModel).MaxTurns;
private string? _listModel; // Task scope only
private int? _listMaxTurns; // Task scope only
private string? _listAgentName; // Task scope only
@@ -56,7 +60,7 @@ public sealed partial class AgentConfigEditorViewModel : ViewModelBase, IDisposa
private CancellationTokenSource? _saveCts;
public int EffectiveMaxTurns =>
MaxTurns is decimal t ? (int)t : (_listMaxTurns ?? _globalMaxTurns);
MaxTurns is decimal t ? (int)t : (_listMaxTurns ?? GlobalMaxTurns);
public ObservableCollection<string> ModelOptions { get; } = new(ModelRegistry.Aliases);
public ObservableCollection<AgentInfo> Agents { get; } = new();
@@ -97,7 +101,14 @@ public sealed partial class AgentConfigEditorViewModel : ViewModelBase, IDisposa
foreach (var s in SessionSkills) s.PropertyChanged -= OnSkillItemPropertyChanged;
}
partial void OnModelChanged(string? value) { RecomputeModelBadge(); QueueSave(); }
partial void OnModelChanged(string? value)
{
RecomputeModelBadge();
// A different model means a different global turn default.
RecomputeTurnsBadge();
OnPropertyChanged(nameof(EffectiveMaxTurns));
QueueSave();
}
partial void OnMaxTurnsChanged(decimal? value)
{
@@ -130,8 +141,8 @@ public sealed partial class AgentConfigEditorViewModel : ViewModelBase, IDisposa
{
var own = MaxTurns?.ToString();
var (value, source) = _scope == AgentConfigScope.Task
? InheritanceResolver.Resolve(own, _listMaxTurns?.ToString(), _globalMaxTurns.ToString())
: InheritanceResolver.ResolveList(own, _globalMaxTurns.ToString());
? InheritanceResolver.Resolve(own, _listMaxTurns?.ToString(), GlobalMaxTurns.ToString())
: InheritanceResolver.ResolveList(own, GlobalMaxTurns.ToString());
TurnsInheritedHint = value;
TurnsBadge = BadgeFor(source, MaxTurns is not null);
}
@@ -284,7 +295,9 @@ public sealed partial class AgentConfigEditorViewModel : ViewModelBase, IDisposa
{
var app = await _worker.GetAppSettingsAsync();
_globalModel = app?.DefaultModel ?? ModelRegistry.DefaultAlias;
_globalMaxTurns = app?.DefaultMaxTurns ?? 100;
_presets = app?.ModelPresets is { Count: > 0 } rows
? rows.Select(r => new ModelPreset(r.Model, r.Effort, r.MaxTurns)).ToList()
: ModelPresets.Defaults;
}
private void ApplyConfig(string? model, int? maxTurns, string? systemPrompt, string? agentPath)