diff --git a/src/ClaudeDo.Ui/ViewModels/Islands/DetailsIslandViewModel.cs b/src/ClaudeDo.Ui/ViewModels/Islands/DetailsIslandViewModel.cs index 646b10b..ce15016 100644 --- a/src/ClaudeDo.Ui/ViewModels/Islands/DetailsIslandViewModel.cs +++ b/src/ClaudeDo.Ui/ViewModels/Islands/DetailsIslandViewModel.cs @@ -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 TaskModelOptions { get; } = new() + { + "(inherit)", "sonnet", "opus", "haiku", + }; + + public System.Collections.ObjectModel.ObservableCollection 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); diff --git a/src/ClaudeDo.Ui/Views/Islands/DetailsIslandView.axaml b/src/ClaudeDo.Ui/Views/Islands/DetailsIslandView.axaml index 154cd7e..e9c69cc 100644 --- a/src/ClaudeDo.Ui/Views/Islands/DetailsIslandView.axaml +++ b/src/ClaudeDo.Ui/Views/Islands/DetailsIslandView.axaml @@ -150,6 +150,47 @@ LostFocus="NotesLostFocus"/> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +