feat(ui): per-task agent settings in DetailsIsland

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Mika Kuns
2026-04-22 13:29:57 +02:00
parent 5784dbee94
commit bba577888b
2 changed files with 142 additions and 0 deletions

View File

@@ -3,6 +3,7 @@ using System.Text;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using ClaudeDo.Data;
using ClaudeDo.Data.Models;
using ClaudeDo.Data.Repositories;
using ClaudeDo.Ui.Helpers;
using ClaudeDo.Ui.Services;
@@ -53,9 +54,32 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase
OnPropertyChanged(nameof(IsRunning));
OnPropertyChanged(nameof(IsDone));
OnPropertyChanged(nameof(IsFailed));
OnPropertyChanged(nameof(IsAgentSectionEnabled));
ShowFailedActions = value == "Failed";
}
[ObservableProperty] private string? _model;
// Agent settings overrides
[ObservableProperty] private string _taskModelSelection = "(inherit)";
[ObservableProperty] private string _taskSystemPrompt = "";
[ObservableProperty] private AgentInfo? _taskSelectedAgent;
[ObservableProperty] private string _effectiveModelHint = "";
[ObservableProperty] private string _effectiveSystemPromptHint = "";
[ObservableProperty] private string _effectiveAgentHint = "";
public System.Collections.ObjectModel.ObservableCollection<string> TaskModelOptions { get; } = new()
{
"(inherit)", "sonnet", "opus", "haiku",
};
public System.Collections.ObjectModel.ObservableCollection<AgentInfo> TaskAgentOptions { get; } = new();
private bool _suppressAgentSave;
private CancellationTokenSource? _agentSaveCts;
public bool IsAgentSectionEnabled => !IsRunning;
[ObservableProperty] private string? _worktreePath;
[ObservableProperty] private string? _worktreeBaseCommit;
[ObservableProperty] private string? _worktreeStateLabel;
@@ -212,6 +236,67 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase
Log.Add(new LogLineViewModel { Kind = LogKind.Claude, Text = piece });
}
partial void OnTaskModelSelectionChanged(string value) => QueueAgentSave();
partial void OnTaskSystemPromptChanged(string value) => QueueAgentSave();
partial void OnTaskSelectedAgentChanged(AgentInfo? value) => QueueAgentSave();
private void QueueAgentSave()
{
if (_suppressAgentSave || Task is null) return;
_agentSaveCts?.Cancel();
_agentSaveCts = new CancellationTokenSource();
var ct = _agentSaveCts.Token;
_ = SaveAgentSettingsAsync(ct);
}
private async System.Threading.Tasks.Task SaveAgentSettingsAsync(CancellationToken ct)
{
try
{
await System.Threading.Tasks.Task.Delay(300, ct);
if (Task is null) return;
var model = TaskModelSelection == "(inherit)" ? null : TaskModelSelection;
var sp = string.IsNullOrWhiteSpace(TaskSystemPrompt) ? null : TaskSystemPrompt;
var ap = TaskSelectedAgent is null || string.IsNullOrWhiteSpace(TaskSelectedAgent.Path)
? null : TaskSelectedAgent.Path;
await _worker.UpdateTaskAgentSettingsAsync(
new ClaudeDo.Ui.Services.UpdateTaskAgentSettingsDto(Task.Id, model, sp, ap));
}
catch (OperationCanceledException) { }
catch { }
}
private async System.Threading.Tasks.Task LoadAgentSettingsAsync(
ClaudeDo.Data.Models.TaskEntity entity, CancellationToken ct)
{
_suppressAgentSave = true;
try
{
TaskAgentOptions.Clear();
TaskAgentOptions.Add(new AgentInfo("(inherit)", "", ""));
var agents = await _worker.GetAgentsAsync();
foreach (var a in agents) TaskAgentOptions.Add(a);
TaskModelSelection = string.IsNullOrWhiteSpace(entity.Model) ? "(inherit)" : entity.Model!;
TaskSystemPrompt = entity.SystemPrompt ?? "";
TaskSelectedAgent = string.IsNullOrWhiteSpace(entity.AgentPath)
? TaskAgentOptions[0]
: (TaskAgentOptions.FirstOrDefault(a => a.Path == entity.AgentPath) ?? TaskAgentOptions[0]);
var listCfg = await _worker.GetListConfigAsync(entity.ListId);
EffectiveModelHint = string.IsNullOrWhiteSpace(listCfg?.Model) ? "(global default)" : listCfg!.Model!;
EffectiveSystemPromptHint = string.IsNullOrWhiteSpace(listCfg?.SystemPrompt) ? "(none)" : listCfg!.SystemPrompt!;
EffectiveAgentHint = string.IsNullOrWhiteSpace(listCfg?.AgentPath)
? "(none)" : System.IO.Path.GetFileName(listCfg!.AgentPath!);
}
finally
{
_suppressAgentSave = false;
}
}
public void Bind(TaskRowViewModel? row)
{
_loadCts?.Cancel();
@@ -237,6 +322,20 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase
AgentStatusLabel = "Idle";
LatestRunSessionId = null;
ShowFailedActions = false;
_suppressAgentSave = true;
try
{
TaskModelSelection = "(inherit)";
TaskSystemPrompt = "";
TaskSelectedAgent = null;
}
finally
{
_suppressAgentSave = false;
}
EffectiveModelHint = "";
EffectiveSystemPromptHint = "";
EffectiveAgentHint = "";
return;
}
@@ -266,6 +365,8 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase
WorktreeStateLabel = entity.Worktree?.State.ToString();
BranchLine = entity.Worktree is { } w ? $"{w.BranchName} \u2190 main" : null;
AgentStatusLabel = entity.Status.ToString();
await LoadAgentSettingsAsync(entity, ct);
ct.ThrowIfCancellationRequested();
var runRepo = new TaskRunRepository(ctx);
var latestRun = await runRepo.GetLatestByTaskIdAsync(row.Id, ct);