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'.
This commit is contained in:
@@ -38,6 +38,8 @@ public sealed partial class AgentConfigEditorViewModel : ViewModelBase, IDisposa
|
||||
[ObservableProperty] private decimal? _maxTurns;
|
||||
[ObservableProperty] private string _systemPrompt = "";
|
||||
[ObservableProperty] private AgentInfo? _selectedAgent;
|
||||
// Null = inherit (list → global). Restricted to PermissionModeRegistry.Modes by the ComboBox.
|
||||
[ObservableProperty] private string? _permissionMode;
|
||||
|
||||
[ObservableProperty] private string _modelBadge = "";
|
||||
[ObservableProperty] private string _modelInheritedHint = "";
|
||||
@@ -45,6 +47,8 @@ public sealed partial class AgentConfigEditorViewModel : ViewModelBase, IDisposa
|
||||
[ObservableProperty] private string _turnsInheritedHint = "";
|
||||
[ObservableProperty] private string _turnsCeilingHint = "";
|
||||
[ObservableProperty] private string _agentBadge = "";
|
||||
[ObservableProperty] private string _permissionBadge = "";
|
||||
[ObservableProperty] private string _permissionInheritedHint = "";
|
||||
[ObservableProperty] private string _effectiveSystemPromptHint = "";
|
||||
|
||||
private string _globalModel = ModelRegistry.DefaultAlias;
|
||||
@@ -54,9 +58,11 @@ public sealed partial class AgentConfigEditorViewModel : ViewModelBase, IDisposa
|
||||
private int _maxTurnsCeiling = 80;
|
||||
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
|
||||
private string _globalPermissionMode = PermissionModeRegistry.DefaultMode;
|
||||
private string? _listModel; // Task scope only
|
||||
private int? _listMaxTurns; // Task scope only
|
||||
private string? _listAgentName; // Task scope only
|
||||
private string? _listPermissionMode; // Task scope only
|
||||
|
||||
private bool _suppressSave;
|
||||
private CancellationTokenSource? _saveCts;
|
||||
@@ -65,6 +71,7 @@ public sealed partial class AgentConfigEditorViewModel : ViewModelBase, IDisposa
|
||||
MaxTurns is decimal t ? (int)t : (_listMaxTurns ?? GlobalMaxTurns);
|
||||
|
||||
public ObservableCollection<string> ModelOptions { get; } = new(ModelRegistry.Aliases);
|
||||
public IReadOnlyList<string> PermissionModeOptions { get; } = PermissionModeRegistry.Modes;
|
||||
public ObservableCollection<AgentInfo> Agents { get; } = new();
|
||||
public ObservableCollection<SelectableSkillViewModel> SessionSkills { get; } = new();
|
||||
|
||||
@@ -121,12 +128,14 @@ public sealed partial class AgentConfigEditorViewModel : ViewModelBase, IDisposa
|
||||
|
||||
partial void OnSystemPromptChanged(string value) => QueueSave();
|
||||
partial void OnSelectedAgentChanged(AgentInfo? value) { RecomputeAgentBadge(); QueueSave(); }
|
||||
partial void OnPermissionModeChanged(string? value) { RecomputePermissionBadge(); QueueSave(); }
|
||||
|
||||
private void RecomputeBadges()
|
||||
{
|
||||
RecomputeModelBadge();
|
||||
RecomputeTurnsBadge();
|
||||
RecomputeAgentBadge();
|
||||
RecomputePermissionBadge();
|
||||
}
|
||||
|
||||
private void RecomputeModelBadge()
|
||||
@@ -162,6 +171,16 @@ public sealed partial class AgentConfigEditorViewModel : ViewModelBase, IDisposa
|
||||
AgentBadge = BadgeFor(source, agentSet);
|
||||
}
|
||||
|
||||
private void RecomputePermissionBadge()
|
||||
{
|
||||
var own = string.IsNullOrWhiteSpace(PermissionMode) ? null : PermissionMode;
|
||||
var (value, source) = _scope == AgentConfigScope.Task
|
||||
? InheritanceResolver.Resolve(own, _listPermissionMode, _globalPermissionMode)
|
||||
: InheritanceResolver.ResolveList(own, _globalPermissionMode);
|
||||
PermissionInheritedHint = value;
|
||||
PermissionBadge = BadgeFor(source, own is not null);
|
||||
}
|
||||
|
||||
private static string BadgeFor(InheritSource source, bool isSet) => isSet
|
||||
? Loc.T("settings.inherit.overrideBadge")
|
||||
: source == InheritSource.List
|
||||
@@ -202,11 +221,12 @@ public sealed partial class AgentConfigEditorViewModel : ViewModelBase, IDisposa
|
||||
var ap = SelectedAgent is null || string.IsNullOrWhiteSpace(SelectedAgent.Path) ? null : SelectedAgent.Path;
|
||||
var turns = MaxTurns is decimal d ? (int?)d : null;
|
||||
var skills = SelectedSessionSkillNames();
|
||||
var permission = string.IsNullOrWhiteSpace(PermissionMode) ? null : PermissionMode;
|
||||
|
||||
if (_scope == AgentConfigScope.Task)
|
||||
await _worker.UpdateTaskAgentSettingsAsync(new UpdateTaskAgentSettingsDto(TargetId, model, sp, ap, turns, skills));
|
||||
await _worker.UpdateTaskAgentSettingsAsync(new UpdateTaskAgentSettingsDto(TargetId, model, sp, ap, turns, skills, permission));
|
||||
else
|
||||
await _worker.UpdateListConfigAsync(new UpdateListConfigDto(TargetId, model, sp, ap, turns, skills, verifyCommand, serializeOnFileOverlap));
|
||||
await _worker.UpdateListConfigAsync(new UpdateListConfigDto(TargetId, model, sp, ap, turns, skills, verifyCommand, serializeOnFileOverlap, permission));
|
||||
}
|
||||
|
||||
private List<string>? SelectedSessionSkillNames()
|
||||
@@ -225,10 +245,10 @@ public sealed partial class AgentConfigEditorViewModel : ViewModelBase, IDisposa
|
||||
await LoadGlobalDefaultsAsync();
|
||||
|
||||
var cfg = await _worker.GetListConfigAsync(listId);
|
||||
ApplyConfig(cfg?.Model, cfg?.MaxTurns, cfg?.SystemPrompt, cfg?.AgentPath);
|
||||
ApplyConfig(cfg?.Model, cfg?.MaxTurns, cfg?.SystemPrompt, cfg?.AgentPath, cfg?.PermissionMode);
|
||||
await ReloadSessionSkillsAsync(cfg?.SessionSkills);
|
||||
|
||||
_listModel = null; _listMaxTurns = null; _listAgentName = null;
|
||||
_listModel = null; _listMaxTurns = null; _listAgentName = null; _listPermissionMode = null;
|
||||
EffectiveSystemPromptHint = "";
|
||||
RecomputeBadges();
|
||||
OnPropertyChanged(nameof(EffectiveMaxTurns));
|
||||
@@ -243,13 +263,14 @@ public sealed partial class AgentConfigEditorViewModel : ViewModelBase, IDisposa
|
||||
{
|
||||
TargetId = entity.Id;
|
||||
await ReloadAgentsAsync("(inherited)");
|
||||
ApplyConfig(entity.Model, entity.MaxTurns, entity.SystemPrompt, entity.AgentPath);
|
||||
ApplyConfig(entity.Model, entity.MaxTurns, entity.SystemPrompt, entity.AgentPath, entity.PermissionMode);
|
||||
await ReloadSessionSkillsAsync(ParseSessionSkills(entity.SessionSkills));
|
||||
|
||||
var listCfg = await _worker.GetListConfigAsync(entity.ListId);
|
||||
await LoadGlobalDefaultsAsync();
|
||||
_listModel = listCfg?.Model;
|
||||
_listMaxTurns = listCfg?.MaxTurns;
|
||||
_listPermissionMode = listCfg?.PermissionMode;
|
||||
_listAgentName = string.IsNullOrWhiteSpace(listCfg?.AgentPath)
|
||||
? null : System.IO.Path.GetFileName(listCfg!.AgentPath!);
|
||||
EffectiveSystemPromptHint = string.IsNullOrWhiteSpace(listCfg?.SystemPrompt)
|
||||
@@ -270,6 +291,7 @@ public sealed partial class AgentConfigEditorViewModel : ViewModelBase, IDisposa
|
||||
MaxTurns = null;
|
||||
SystemPrompt = "";
|
||||
SelectedAgent = null;
|
||||
PermissionMode = null;
|
||||
foreach (var s in SessionSkills) s.IsSelected = false;
|
||||
}
|
||||
finally { _suppressSave = false; }
|
||||
@@ -309,13 +331,16 @@ public sealed partial class AgentConfigEditorViewModel : ViewModelBase, IDisposa
|
||||
? rows.Select(r => new ModelPreset(r.Model, r.Effort, r.MaxTurns)).ToList()
|
||||
: ModelPresets.Defaults;
|
||||
_maxTurnsCeiling = app?.MaxTurnsCeiling ?? 80;
|
||||
_globalPermissionMode = app?.DefaultPermissionMode ?? PermissionModeRegistry.DefaultMode;
|
||||
}
|
||||
|
||||
private void ApplyConfig(string? model, int? maxTurns, string? systemPrompt, string? agentPath)
|
||||
private void ApplyConfig(string? model, int? maxTurns, string? systemPrompt, string? agentPath,
|
||||
string? permissionMode)
|
||||
{
|
||||
Model = string.IsNullOrWhiteSpace(model) ? null : model!;
|
||||
MaxTurns = maxTurns is int mt ? mt : (decimal?)null;
|
||||
SystemPrompt = systemPrompt ?? "";
|
||||
PermissionMode = string.IsNullOrWhiteSpace(permissionMode) ? null : permissionMode!;
|
||||
SelectedAgent = string.IsNullOrWhiteSpace(agentPath)
|
||||
? Agents[0]
|
||||
: (Agents.FirstOrDefault(a => a.Path == agentPath) ?? Agents[0]);
|
||||
@@ -324,6 +349,7 @@ public sealed partial class AgentConfigEditorViewModel : ViewModelBase, IDisposa
|
||||
[RelayCommand] private void ResetModel() => Model = null;
|
||||
[RelayCommand] private void ResetTurns() => MaxTurns = null;
|
||||
[RelayCommand] private void ResetAgent() => SelectedAgent = Agents.Count > 0 ? Agents[0] : null;
|
||||
[RelayCommand] private void ResetPermissionMode() => PermissionMode = null;
|
||||
|
||||
[RelayCommand]
|
||||
private void ResetAll()
|
||||
@@ -332,6 +358,7 @@ public sealed partial class AgentConfigEditorViewModel : ViewModelBase, IDisposa
|
||||
MaxTurns = null;
|
||||
SystemPrompt = "";
|
||||
SelectedAgent = Agents.Count > 0 ? Agents[0] : null;
|
||||
PermissionMode = null;
|
||||
foreach (var s in SessionSkills) s.IsSelected = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,20 @@
|
||||
IsVisible="{Binding TurnsCeilingHint, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Permission mode -->
|
||||
<StackPanel Spacing="4">
|
||||
<Grid ColumnDefinitions="Auto,Auto,*,Auto" ColumnSpacing="6">
|
||||
<TextBlock Grid.Column="0" Classes="field-label" Text="{loc:Tr settings.agentEditor.permissionMode}" VerticalAlignment="Center"/>
|
||||
<ctl:InheritedBadge Grid.Column="1" BadgeText="{Binding PermissionBadge}"/>
|
||||
<Button Grid.Column="3" Classes="icon-btn" Content="↺" ToolTip.Tip="{loc:Tr settings.inherit.resetToInherited}"
|
||||
Command="{Binding ResetPermissionModeCommand}"/>
|
||||
</Grid>
|
||||
<ComboBox ItemsSource="{Binding PermissionModeOptions}"
|
||||
SelectedItem="{Binding PermissionMode, Mode=TwoWay}"
|
||||
PlaceholderText="{Binding PermissionInheritedHint}"
|
||||
HorizontalAlignment="Stretch"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- System prompt -->
|
||||
<StackPanel Spacing="4">
|
||||
<TextBlock Classes="field-label" Text="{loc:Tr settings.agentEditor.systemPrompt}"/>
|
||||
|
||||
Reference in New Issue
Block a user