Files
ClaudeDo/src/ClaudeDo.Worker/Runner/EffectiveRunConfigResolver.cs
T
mika kuns 4464dc6ff1 feat(config): Permission-Modus pro Liste und pro Task ueberschreibbar
Bisher gab es nur AppSettings.DefaultPermissionMode global — ein Task, der plan
oder acceptEdits braucht, erzwang das Umstellen der globalen Einstellung. Neue
Spalten tasks.permission_mode und list_config.permission_mode, Auflösung
task -> list -> global im EffectiveRunConfigResolver (den TaskRunner und
get_effective_run_config gemeinsam nutzen), ComboBox mit Inherited-Badge im
geteilten Agent-Editor.

MigrationBaselineTests: das Fixture baute per EnsureCreated das heutige Schema
und stempelte Legacy-History darauf — jede Migration nach dem Squash lief damit
in 'duplicate column name'. Es migriert jetzt gezielt bis InitialCreate und
prueft 'nichts pending' statt 'InitialCreate ist die einzige Zeile'.
2026-08-27 10:28:57 +02:00

55 lines
3.0 KiB
C#

using ClaudeDo.Data.Models;
namespace ClaudeDo.Worker.Runner;
/// <summary>The task/list/preset/global resolution <see cref="TaskRunner"/> actually runs with,
/// plus where each value came from — so a caller can tell a genuine default from an override
/// without re-deriving the resolution order itself.</summary>
public sealed record EffectiveRunConfig(
string Model, string ModelSource,
int MaxTurns, string MaxTurnsSource, int RequestedMaxTurns, bool MaxTurnsClamped,
string Effort,
string? AgentPath, string? AgentPathSource,
string PermissionMode,
bool SystemPromptSet, IReadOnlyList<string> SystemPromptSources);
/// <summary>Single source of truth for "which value wins" — shared by <see cref="TaskRunner"/>'s
/// actual run path and any read-only reporting of the same resolution (e.g. the
/// <c>get_effective_run_config</c> MCP tool), so the two can never drift apart.</summary>
public static class EffectiveRunConfigResolver
{
public static EffectiveRunConfig Resolve(
TaskEntity task, ListConfigEntity? listConfig, AppSettingsEntity global,
string? systemFile, string? improvementPrompt)
{
var model = task.Model ?? listConfig?.Model ?? global.DefaultModel;
var modelSource = task.Model is not null ? "task" : listConfig?.Model is not null ? "list" : "global";
var preset = ModelPresets.For(global.ModelPresets, model, global.DefaultMaxTurns);
var requestedMaxTurns = task.MaxTurns ?? listConfig?.MaxTurns ?? preset.MaxTurns;
var maxTurnsSource = task.MaxTurns is not null ? "task" : listConfig?.MaxTurns is not null ? "list" : "preset";
// ResolveMaxTurns is declared int? but always returns a value (Math.Min of non-null inputs).
var maxTurns = TaskRunner.ResolveMaxTurns(task.MaxTurns, listConfig?.MaxTurns, preset.MaxTurns, global.MaxTurnsCeiling)!.Value;
var agentPath = task.AgentPath ?? listConfig?.AgentPath;
var agentPathSource = task.AgentPath is not null ? "task" : listConfig?.AgentPath is not null ? "list" : null;
var systemPromptSources = new List<string>();
if (!string.IsNullOrWhiteSpace(systemFile)) systemPromptSources.Add("systemFile");
if (!string.IsNullOrWhiteSpace(improvementPrompt)) systemPromptSources.Add("improvementPrompt");
if (!string.IsNullOrWhiteSpace(global.DefaultClaudeInstructions)) systemPromptSources.Add("global");
if (!string.IsNullOrWhiteSpace(listConfig?.SystemPrompt)) systemPromptSources.Add("list");
if (!string.IsNullOrWhiteSpace(task.SystemPrompt)) systemPromptSources.Add("task");
return new EffectiveRunConfig(
model, modelSource,
maxTurns, maxTurnsSource, requestedMaxTurns, maxTurns < requestedMaxTurns,
preset.Effort,
agentPath, agentPathSource,
PermissionModeResolver.Resolve(
model, task.PermissionMode ?? listConfig?.PermissionMode ?? global.DefaultPermissionMode),
systemPromptSources.Count > 0, systemPromptSources);
}
}