feat(ui): per-task agent settings in DetailsIsland
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -150,6 +150,47 @@
|
||||
LostFocus="NotesLostFocus"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Agent settings overrides -->
|
||||
<Expander Header="Agent settings (overrides)"
|
||||
IsExpanded="False"
|
||||
IsEnabled="{Binding IsAgentSectionEnabled}"
|
||||
Margin="18,0,18,12">
|
||||
<StackPanel Spacing="8" Margin="0,8,0,0">
|
||||
|
||||
<StackPanel Spacing="2">
|
||||
<TextBlock Text="Model" />
|
||||
<ComboBox ItemsSource="{Binding TaskModelOptions}"
|
||||
SelectedItem="{Binding TaskModelSelection, Mode=TwoWay}"
|
||||
MinWidth="160" HorizontalAlignment="Left" />
|
||||
<TextBlock Text="{Binding EffectiveModelHint, StringFormat='Effective if inherited: {0}'}"
|
||||
Opacity="0.6" FontSize="11" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Spacing="2">
|
||||
<TextBlock Text="System prompt (appended)" />
|
||||
<TextBox Text="{Binding TaskSystemPrompt, Mode=TwoWay}"
|
||||
AcceptsReturn="True" TextWrapping="Wrap" MinHeight="60"
|
||||
Watermark="{Binding EffectiveSystemPromptHint}" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Spacing="2">
|
||||
<TextBlock Text="Agent file" />
|
||||
<ComboBox ItemsSource="{Binding TaskAgentOptions}"
|
||||
SelectedItem="{Binding TaskSelectedAgent, Mode=TwoWay}"
|
||||
MinWidth="240" HorizontalAlignment="Left">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Name}" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
<TextBlock Text="{Binding EffectiveAgentHint, StringFormat='Effective if inherited: {0}'}"
|
||||
Opacity="0.6" FontSize="11" />
|
||||
</StackPanel>
|
||||
|
||||
</StackPanel>
|
||||
</Expander>
|
||||
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
|
||||
Reference in New Issue
Block a user