using System.Collections.ObjectModel; using System.Linq; using Avalonia.Threading; using ClaudeDo.Data.Models; using ClaudeDo.Data.Repositories; using CommunityToolkit.Mvvm.ComponentModel; using Microsoft.AspNetCore.SignalR; using Microsoft.AspNetCore.SignalR.Client; using Microsoft.Extensions.DependencyInjection; namespace ClaudeDo.Ui.Services; public record ActiveTask(string Slot, string TaskId, DateTime StartedAt); public sealed record WorkerLogEntry(string Message, WorkerLogLevel Level, DateTime TimestampUtc); sealed class IndefiniteRetryPolicy : IRetryPolicy { private static readonly TimeSpan[] _delays = [ TimeSpan.Zero, TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(30), ]; public TimeSpan? NextRetryDelay(RetryContext retryContext) => _delays[Math.Min(retryContext.PreviousRetryCount, _delays.Length - 1)]; } public partial class WorkerClient : ObservableObject, IAsyncDisposable, IWorkerClient { private readonly HubConnection _hub; private CancellationTokenSource? _startCts; private Task _retryLoopTask = Task.CompletedTask; private readonly object _startLock = new(); [ObservableProperty] private bool _isConnected; [ObservableProperty] private bool _isReconnecting; public ObservableCollection ActiveTasks { get; } = new(); public event Action? TaskStartedEvent; public event Action? TaskFinishedEvent; public event Action? TaskMessageEvent; public event Action? TaskUpdatedEvent; public event Action? TaskQuestionAskedEvent; public event Action? TaskQuestionResolvedEvent; public event Action>? HandoffRequestedEvent; public event Action? ConnectionRestoredEvent; public event Action? WorktreeUpdatedEvent; public event Action? ListUpdatedEvent; public event Action? WorkerLogReceivedEvent; public event Action? PrepStartedEvent; public event Action? PrepLineEvent; public event Action? PrepFinishedEvent; public event Action? RefineStartedEvent; public event Action? RefineFinishedEvent; public event Action? UsageUpdatedEvent; public event Action? PlanningMergeStartedEvent; public event Action? PlanningSubtaskMergedEvent; public event Action, bool>? PlanningMergeConflictEvent; public event Action? PlanningMergeAbortedEvent; public event Action? PlanningCompletedEvent; public event Action? PrimeFired; public string? LastApproveTarget { get; private set; } public IReadOnlyList GetActiveTasks() => ActiveTasks.ToList(); public WorkerClient(string signalRUrl) { _hub = new HubConnectionBuilder() .WithUrl(signalRUrl) .WithAutomaticReconnect(new IndefiniteRetryPolicy()) .AddJsonProtocol(options => { options.PayloadSerializerOptions.Converters.Add(new System.Text.Json.Serialization.JsonStringEnumConverter()); }) .Build(); _hub.Reconnected += async _ => { Dispatcher.UIThread.Post(() => { IsConnected = true; IsReconnecting = false; }); await SeedActiveTasksAsync(); Dispatcher.UIThread.Post(() => ConnectionRestoredEvent?.Invoke()); }; _hub.Reconnecting += _ => { Dispatcher.UIThread.Post(() => { IsConnected = false; IsReconnecting = true; }); return Task.CompletedTask; }; _hub.Closed += _ => { Dispatcher.UIThread.Post(() => { IsConnected = false; IsReconnecting = false; ActiveTasks.Clear(); }); return Task.CompletedTask; }; _hub.On("TaskStarted", (slot, taskId, startedAt) => { Dispatcher.UIThread.Post(() => { ActiveTasks.Add(new ActiveTask(slot, taskId, startedAt)); TaskStartedEvent?.Invoke(slot, taskId, startedAt); }); }); _hub.On("TaskFinished", (slot, taskId, status, finishedAt) => { Dispatcher.UIThread.Post(() => { var existing = ActiveTasks.FirstOrDefault(t => t.TaskId == taskId); if (existing is not null) ActiveTasks.Remove(existing); TaskFinishedEvent?.Invoke(slot, taskId, status, finishedAt); }); }); _hub.On("TaskMessage", (taskId, line) => { Dispatcher.UIThread.Post(() => TaskMessageEvent?.Invoke(taskId, line)); }); _hub.On("TaskUpdated", taskId => { Dispatcher.UIThread.Post(() => TaskUpdatedEvent?.Invoke(taskId)); }); _hub.On("TaskQuestionAsked", (taskId, questionId, question) => { Dispatcher.UIThread.Post(() => TaskQuestionAskedEvent?.Invoke(taskId, questionId, question)); }); _hub.On("TaskQuestionResolved", (taskId, questionId) => { Dispatcher.UIThread.Post(() => TaskQuestionResolvedEvent?.Invoke(taskId, questionId)); }); _hub.On>("HandoffRequested", (taskId, survivingTaskIds) => { Dispatcher.UIThread.Post(() => HandoffRequestedEvent?.Invoke(taskId, survivingTaskIds)); }); _hub.On("WorktreeUpdated", taskId => { Dispatcher.UIThread.Post(() => WorktreeUpdatedEvent?.Invoke(taskId)); }); _hub.On("ListUpdated", listId => { Dispatcher.UIThread.Post(() => ListUpdatedEvent?.Invoke(listId)); }); _hub.On("WorkerLog", (message, level, timestampUtc) => { Dispatcher.UIThread.Post(() => WorkerLogReceivedEvent?.Invoke(new WorkerLogEntry(message, level, timestampUtc))); }); _hub.On("PlanningMergeStarted", (planningTaskId, targetBranch) => { Dispatcher.UIThread.Post(() => PlanningMergeStartedEvent?.Invoke(planningTaskId, targetBranch)); }); _hub.On("PlanningSubtaskMerged", (planningTaskId, subtaskId) => { Dispatcher.UIThread.Post(() => PlanningSubtaskMergedEvent?.Invoke(planningTaskId, subtaskId)); }); _hub.On, bool>("PlanningMergeConflict", (planningTaskId, subtaskId, conflictedFiles, externallyDriven) => { Dispatcher.UIThread.Post(() => PlanningMergeConflictEvent?.Invoke(planningTaskId, subtaskId, conflictedFiles, externallyDriven)); }); _hub.On("PlanningMergeAborted", planningTaskId => { Dispatcher.UIThread.Post(() => PlanningMergeAbortedEvent?.Invoke(planningTaskId)); }); _hub.On("PlanningCompleted", planningTaskId => { Dispatcher.UIThread.Post(() => PlanningCompletedEvent?.Invoke(planningTaskId)); }); _hub.On("PrimeFired", (id, ok, msg, when) => { Dispatcher.UIThread.Post(() => PrimeFired?.Invoke(new PrimeFiredEvent(id, ok, msg, when))); }); _hub.On("PrepStarted", () => Dispatcher.UIThread.Post(() => PrepStartedEvent?.Invoke())); _hub.On("PrepLine", line => Dispatcher.UIThread.Post(() => PrepLineEvent?.Invoke(line))); _hub.On("PrepFinished", ok => Dispatcher.UIThread.Post(() => PrepFinishedEvent?.Invoke(ok))); _hub.On("RefineStarted", id => Dispatcher.UIThread.Post(() => RefineStartedEvent?.Invoke(id))); _hub.On("RefineFinished", (id, ok, err) => Dispatcher.UIThread.Post(() => RefineFinishedEvent?.Invoke(id, ok, err))); _hub.On("UsageUpdated", snapshot => Dispatcher.UIThread.Post(() => UsageUpdatedEvent?.Invoke(snapshot))); } public Task StartAsync() { lock (_startLock) { if (!_retryLoopTask.IsCompleted) return Task.CompletedTask; var old = _startCts; _startCts = new CancellationTokenSource(); old?.Cancel(); old?.Dispose(); _retryLoopTask = ConnectWithRetryAsync(_startCts.Token); } return Task.CompletedTask; } private async Task ConnectWithRetryAsync(CancellationToken ct) { var delays = new[] { 0, 2, 5, 10, 30 }; int attempt = 0; Dispatcher.UIThread.Post(() => IsReconnecting = true); while (!ct.IsCancellationRequested) { try { await _hub.StartAsync(ct); Dispatcher.UIThread.Post(() => { IsConnected = true; IsReconnecting = false; }); await SeedActiveTasksAsync(); Dispatcher.UIThread.Post(() => ConnectionRestoredEvent?.Invoke()); return; } catch (OperationCanceledException) { return; } catch { var delay = delays[Math.Min(attempt++, delays.Length - 1)]; try { await Task.Delay(TimeSpan.FromSeconds(delay), ct); } catch (OperationCanceledException) { return; } } } } public async Task StopAsync() { _startCts?.Cancel(); try { await _retryLoopTask; } catch (OperationCanceledException) { } catch { /* swallow */ } try { await _hub.StopAsync(); } catch { /* swallow */ } } /// Invoke a hub method, returning default (null) when the worker is offline or errors. private async Task TryInvokeAsync(string method, params object?[] args) { try { return await _hub.InvokeCoreAsync(method, args); } catch { return default; } } public async Task RunNowAsync(string taskId) { await _hub.InvokeAsync("RunNow", taskId); } public async Task ContinueTaskAsync(string taskId, string followUpPrompt) { await _hub.InvokeAsync("ContinueTask", taskId, followUpPrompt); } public async Task AnswerTaskQuestionAsync(string taskId, string questionId, string answer) { try { await _hub.InvokeAsync("AnswerTaskQuestion", taskId, questionId, answer); } catch { /* offline or already resolved — the UI clears optimistically */ } } public Task GetPendingQuestionAsync(string taskId) => TryInvokeAsync("GetPendingQuestion", taskId); public async Task ResetTaskAsync(string taskId) { await _hub.InvokeAsync("ResetTask", taskId); } public async Task MergeTaskAsync(string taskId, string targetBranch, bool removeWorktree, string commitMessage) { return await _hub.InvokeAsync( "MergeTask", taskId, targetBranch, removeWorktree, commitMessage); } public Task StartConflictMergeAsync(string taskId, string targetBranch) => _hub.InvokeAsync("StartConflictMerge", taskId, targetBranch); public Task GetMergeConflictDocumentsAsync(string taskId) => _hub.InvokeAsync("GetMergeConflictDocuments", taskId); public Task WriteConflictResolutionAsync(string taskId, string path, string resolvedContent) => _hub.InvokeAsync("WriteConflictResolution", taskId, path, resolvedContent); public Task ContinueConflictMergeAsync(string taskId) => _hub.InvokeAsync("ContinueConflictMerge", taskId); public Task AbortConflictMergeAsync(string taskId) => _hub.InvokeAsync("AbortConflictMerge", taskId); public Task GetMergeTargetsAsync(string taskId) => TryInvokeAsync("GetMergeTargets", taskId); public async Task CancelTaskAsync(string taskId) { await _hub.InvokeAsync("CancelTask", taskId); } public async Task WakeQueueAsync() { await _hub.InvokeAsync("WakeQueue"); } public async Task> GetAgentsAsync() => await TryInvokeAsync>("GetAgents") ?? []; public async Task RefreshAgentsAsync() { await _hub.InvokeAsync("RefreshAgents"); } public async Task RestoreDefaultAgentsAsync() => await _hub.InvokeAsync("RestoreDefaultAgents"); private async Task SeedActiveTasksAsync() { try { var active = await _hub.InvokeAsync>("GetActive"); Dispatcher.UIThread.Post(() => { ActiveTasks.Clear(); foreach (var a in active) ActiveTasks.Add(new ActiveTask(a.Slot, a.TaskId, a.StartedAt)); }); } catch (HubException) { // Expected: worker doesn't support GetActive yet } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"SeedActiveTasksAsync failed: {ex}"); } } public async ValueTask DisposeAsync() { _startCts?.Cancel(); try { await _retryLoopTask; } catch (OperationCanceledException) { } catch { /* swallow */ } await _hub.DisposeAsync(); } public Task GetAppSettingsAsync() => TryInvokeAsync("GetAppSettings"); public async Task UpdateAppSettingsAsync(AppSettingsDto dto) { await _hub.InvokeAsync("UpdateAppSettings", dto); } public async Task> GetPrimeSchedulesAsync() => await TryInvokeAsync>("ListPrimeSchedules") ?? new List(); public async Task UpsertPrimeScheduleAsync(PrimeScheduleDto dto) => await _hub.InvokeAsync("UpsertPrimeSchedule", dto); public async Task DeletePrimeScheduleAsync(Guid id) { try { await _hub.InvokeAsync("DeletePrimeSchedule", id); } catch { /* offline */ } } private static string IsoDay(DateOnly d) => d.ToString("yyyy-MM-dd"); public Task GetWeekReportAsync(DateOnly start, DateOnly end) => TryInvokeAsync("GetWeekReport", IsoDay(start), IsoDay(end)); public Task GenerateWeekReportAsync(DateOnly start, DateOnly end) => _hub.InvokeAsync("GenerateWeekReport", IsoDay(start), IsoDay(end)); public Task RunDailyPrepNowAsync() => _hub.InvokeAsync("RunDailyPrepNow"); public Task RefineTaskAsync(string taskId) => _hub.InvokeAsync("RefineTask", taskId); public Task ClearMyDayAsync() => _hub.InvokeAsync("ClearMyDay"); public async Task> GetDailyNotesAsync(DateOnly day) => await TryInvokeAsync>("GetDailyNotes", IsoDay(day)) ?? new List(); public async Task AddDailyNoteAsync(DateOnly day, string text) => await _hub.InvokeAsync("AddDailyNote", IsoDay(day), text); public async Task UpdateDailyNoteAsync(string id, string text) => await _hub.InvokeAsync("UpdateDailyNote", id, text); public async Task DeleteDailyNoteAsync(string id) => await _hub.InvokeAsync("DeleteDailyNote", id); public async Task GetLastPrepLogAsync() => await TryInvokeAsync("GetLastPrepLog") ?? string.Empty; public Task GetWorkerBuildInfoAsync() => TryInvokeAsync("GetWorkerBuildInfo"); public async Task> GetRecentLogsAsync() => await TryInvokeAsync>("GetRecentLogs") ?? new List(); public async Task UpdateListAsync(UpdateListDto dto) { await _hub.InvokeAsync("UpdateList", dto); } public async Task UpdateListConfigAsync(UpdateListConfigDto dto) { await _hub.InvokeAsync("UpdateListConfig", dto); } public Task GetListConfigAsync(string listId) => TryInvokeAsync("GetListConfig", listId); public async Task UpdateTaskAgentSettingsAsync(UpdateTaskAgentSettingsDto dto) { await _hub.InvokeAsync("UpdateTaskAgentSettings", dto); } public async Task> GetRepoImportFoldersAsync() => await _hub.InvokeAsync>("GetRepoImportFolders"); public Task SetRepoImportFoldersAsync(List folders) => _hub.InvokeAsync("SetRepoImportFolders", folders); public async Task> GetSessionSkillsAsync() => await TryInvokeAsync>("GetSessionSkills") ?? []; public Task> InstallSessionSkillAsync(string url) => _hub.InvokeAsync>("InstallSessionSkill", url); public Task UpdateSessionSkillAsync(string sourceUrl) => _hub.InvokeAsync("UpdateSessionSkill", sourceUrl); public Task RemoveSessionSkillAsync(string sourceUrl) => _hub.InvokeAsync("RemoveSessionSkill", sourceUrl); public async Task SetTaskStatusAsync(string taskId, ClaudeDo.Data.Models.TaskStatus status) { await _hub.InvokeAsync("SetTaskStatus", taskId, status.ToString()); } public async Task ApproveReviewAsync(string taskId, string targetBranch) { LastApproveTarget = targetBranch; return await _hub.InvokeAsync("ApproveReview", taskId, targetBranch); } public Task PreviewMergeAsync(string taskId, string targetBranch) => TryInvokeAsync("PreviewMerge", taskId, targetBranch); public async Task RejectReviewToQueueAsync(string taskId, string feedback) { await _hub.InvokeAsync("RejectReviewToQueue", taskId, feedback); } public async Task RejectReviewToIdleAsync(string taskId) { await _hub.InvokeAsync("RejectReviewToIdle", taskId); } public async Task CancelReviewAsync(string taskId) { await _hub.InvokeAsync("CancelReview", taskId); } public async Task CleanupFinishedWorktreesAsync(string? listId = null) => await _hub.InvokeAsync("CleanupFinishedWorktrees", listId); public async Task ResetAllWorktreesAsync() => await _hub.InvokeAsync("ResetAllWorktrees"); public async Task> GetWorktreesOverviewAsync(string? listId) => await TryInvokeAsync>("GetWorktreesOverview", listId) ?? new List(); public async Task<(bool Ok, string? Error)> SetWorktreeStateAsync(string taskId, WorktreeState newState) { try { var ok = await _hub.InvokeAsync("SetWorktreeState", taskId, newState); return (ok, null); } catch (HubException ex) { return (false, ex.Message); } catch (Exception) { return (false, "Worker offline."); } } public async Task ForceRemoveWorktreeAsync(string taskId) => await _hub.InvokeAsync("ForceRemoveWorktree", taskId); public async Task StartPlanningSessionAsync(string taskId, CancellationToken ct = default) => await _hub.InvokeAsync("StartPlanningSessionAsync", taskId, ct); public async Task ResumePlanningSessionAsync(string taskId, CancellationToken ct = default) => await _hub.InvokeAsync("ResumePlanningSessionAsync", taskId, ct); public async Task SubmitTaskForReviewAsync(string taskId, CancellationToken ct = default) => await _hub.InvokeAsync("SubmitTaskForReview", taskId, ct); public async Task GetInteractiveLaunchSpecAsync(string taskId, CancellationToken ct = default) => await _hub.InvokeAsync("GetInteractiveLaunchSpec", taskId, ct); public async Task GetAdHocLaunchSpecAsync(string directory, CancellationToken ct = default) => await _hub.InvokeAsync("GetAdHocLaunchSpec", directory, ct); public async Task GetMergeHelperLaunchSpecAsync(IReadOnlyList taskIds, string listId, CancellationToken ct = default) => await _hub.InvokeAsync("GetMergeHelperLaunchSpec", taskIds, listId, ct); public async Task CreateMergeHelperTaskAsync( IReadOnlyList taskIds, string listId, string title, string descriptionHeader, CancellationToken ct = default) => await _hub.InvokeAsync("CreateMergeHelperTask", taskIds, listId, title, descriptionHeader, ct); public async Task GetMergeHelperHandoffLaunchSpecAsync( string taskId, IReadOnlyList survivingTaskIds, CancellationToken ct = default) => await _hub.InvokeAsync("GetMergeHelperHandoffLaunchSpec", taskId, survivingTaskIds, ct); public async Task GetPlanningStartLaunchSpecAsync(string taskId, CancellationToken ct = default) => await _hub.InvokeAsync("GetPlanningStartLaunchSpec", taskId, ct); public async Task GetPlanningResumeLaunchSpecAsync(string taskId, CancellationToken ct = default) => await _hub.InvokeAsync("GetPlanningResumeLaunchSpec", taskId, ct); public async Task DiscardPlanningSessionAsync(string taskId, bool dequeueQueuedChildren = false, CancellationToken ct = default) => await _hub.InvokeAsync("DiscardPlanningSessionAsync", taskId, dequeueQueuedChildren, ct); public async Task FinalizePlanningSessionAsync(string taskId, bool queueAgentTasks = true, CancellationToken ct = default) => await _hub.InvokeAsync("FinalizePlanningSessionAsync", taskId, queueAgentTasks, ct); public async Task GetPendingDraftCountAsync(string taskId, CancellationToken ct = default) => await _hub.InvokeAsync("GetPendingDraftCountAsync", taskId, ct); public async Task> GetPlanningAggregateAsync(string planningTaskId) => await TryInvokeAsync>("GetPlanningAggregate", planningTaskId) ?? []; public async Task BuildPlanningIntegrationBranchAsync(string planningTaskId, string targetBranch) => await _hub.InvokeAsync("BuildPlanningIntegrationBranch", planningTaskId, targetBranch); public async Task ContinuePlanningMergeAsync(string planningTaskId) { await _hub.InvokeAsync("ContinuePlanningMerge", planningTaskId); } public async Task AbortPlanningMergeAsync(string planningTaskId) { await _hub.InvokeAsync("AbortPlanningMerge", planningTaskId); } public async Task> GetActiveExternalPlanningMergeConflictsAsync() => await TryInvokeAsync>("GetActiveExternalPlanningMergeConflicts") ?? []; public async Task QueuePlanningSubtasksAsync(string parentTaskId, CancellationToken ct = default) { await _hub.InvokeAsync("QueuePlanningSubtasksAsync", parentTaskId, ct); } public Task GetOnlineInboxStateAsync() => TryInvokeAsync("GetOnlineInboxState"); public async Task SetOnlineInboxConfigAsync(OnlineInboxConfigInputDto input) => await _hub.InvokeAsync("SetOnlineInboxConfig", input); public async Task SetOnlineInboxAuthAsync(string refreshToken) => await _hub.InvokeAsync("SetOnlineInboxAuth", refreshToken); public async Task ClearOnlineInboxAuthAsync() => await _hub.InvokeAsync("ClearOnlineInboxAuth"); public Task GetUsageSnapshotAsync() => TryInvokeAsync("GetUsageSnapshot"); public Task RefreshUsageAsync() => TryInvokeAsync("RefreshUsage"); public async Task> GetModelUsageAsync(DateOnly from, DateOnly to) => await TryInvokeAsync>("GetModelUsage", from, to) ?? []; public async Task> GetTaskUsageAsync(DateOnly from, DateOnly to) => await TryInvokeAsync>("GetTaskUsage", from, to) ?? []; // IWorkerClient explicit implementations (drop typed return values) async Task IWorkerClient.StartPlanningSessionAsync(string taskId, CancellationToken ct) => await StartPlanningSessionAsync(taskId, ct); async Task IWorkerClient.ResumePlanningSessionAsync(string taskId, CancellationToken ct) => await ResumePlanningSessionAsync(taskId, ct); async Task IWorkerClient.DiscardPlanningSessionAsync(string taskId, bool dequeueQueuedChildren, CancellationToken ct) => await DiscardPlanningSessionAsync(taskId, dequeueQueuedChildren, ct); async Task IWorkerClient.FinalizePlanningSessionAsync(string taskId, bool queueAgentTasks, CancellationToken ct) => await FinalizePlanningSessionAsync(taskId, queueAgentTasks, ct); async Task IWorkerClient.GetPendingDraftCountAsync(string taskId, CancellationToken ct) => await GetPendingDraftCountAsync(taskId, ct); // DTOs for deserializing hub responses private sealed class ActiveTaskDto { public string Slot { get; set; } = ""; public string TaskId { get; set; } = ""; public DateTime StartedAt { get; set; } } } public sealed record AppSettingsDto( string DefaultClaudeInstructions, string DefaultModel, int DefaultMaxTurns, string DefaultPermissionMode, int MaxParallelExecutions, string WorktreeStrategy, string? CentralWorktreeRoot, bool WorktreeAutoCleanupEnabled, int WorktreeAutoCleanupDays, string? ReportExcludedPaths, int StandupWeekday, int DailyPrepMaxTasks, List? SessionSkills = null, List? ModelPresets = null, int UsageGateFiveHourPct = 80, int UsageGateSevenDayPct = 90, int MaxTurnsCeiling = 80); // Per-model run defaults (effort + turn budget) edited in Settings → General. public sealed record ModelPresetDto(string Model, string Effort, int MaxTurns); public sealed record SessionSkillDto( string Name, string Description, string SourceUrl, string PinnedRef, DateTimeOffset AddedAt); public sealed record WorktreeCleanupDto(int Removed); public sealed record WorktreeResetDto(int Removed, int TasksAffected, bool Blocked, int RunningTasks); public record MergeResultDto(string Status, IReadOnlyList ConflictFiles, string? ErrorMessage); public record MergePreviewDto(string Status, IReadOnlyList ConflictFiles, int ChangedFileCount); public record MergeTargetsDto(string DefaultBranch, IReadOnlyList LocalBranches); public record MergeConflictDocumentsDto(string TaskId, IReadOnlyList Files); public record ConflictDocumentDto(string Path, bool IsBinary, IReadOnlyList Segments); public record MergeSegmentDto(bool IsConflict, string Text, string Ours, string? Base, string Theirs); public sealed record UpdateListDto(string Id, string Name, string? WorkingDir, string DefaultCommitType, bool IsManual = false); public sealed record UpdateListConfigDto(string ListId, string? Model, string? SystemPrompt, string? AgentPath, int? MaxTurns = null, List? SessionSkills = null, string? VerifyCommand = null); public sealed record UpdateTaskAgentSettingsDto(string TaskId, string? Model, string? SystemPrompt, string? AgentPath, int? MaxTurns = null, List? SessionSkills = null); public sealed record ListConfigDto(string? Model, string? SystemPrompt, string? AgentPath, int? MaxTurns = null, List? SessionSkills = null, string? VerifyCommand = null); public sealed record SeedResultDto(int Copied, int Skipped); public sealed record WorktreeOverviewDto( string TaskId, string TaskTitle, ClaudeDo.Data.Models.TaskStatus TaskStatus, string ListId, string ListName, string Path, string BranchName, string BaseCommit, WorktreeState State, string? DiffStat, DateTime CreatedAt, bool PathExistsOnDisk); public sealed record LaunchSpec( string Cwd, string Exe, IReadOnlyList Args, IReadOnlyDictionary Env); public sealed record ForceRemoveResultDto(bool Removed, string? Reason); public sealed record PlanningMergeConflictStateDto(string PlanningTaskId, string SubtaskId); public sealed record PendingQuestionDto(string TaskId, string QuestionId, string Question); public sealed record WorkerBuildInfoDto(string? BuildSha); public sealed record OnlineInboxStateDto( bool Enabled, string ApiBaseUrl, string Authority, string ClientId, string Scopes, string RedirectUri, bool SignedIn, int PollIntervalSeconds); public sealed record OnlineInboxConfigInputDto( bool Enabled, string ApiBaseUrl, int PollIntervalSeconds, string Authority, string ClientId, string Scopes, string RedirectUri); public sealed record UsageLimitDto( string Kind, string Group, double Percent, string Severity, DateTimeOffset? ResetsAt, string? ScopeModelDisplayName, bool IsActive); public sealed record UsageSnapshotDto( double? FiveHourPercent, DateTimeOffset? FiveHourResetsAt, double? SevenDayPercent, DateTimeOffset? SevenDayResetsAt, IReadOnlyList Limits, int FiveHourThresholdPct, int SevenDayThresholdPct, bool IsGateBlocked, string? GateReason, DateTime? FetchedAtUtc, bool IsStale, string? LastError, int ConfiguredSlots, int EffectiveSlots, string? ThrottleBucket); public sealed record ModelUsageRowDto( DateOnly Date, string Model, string Scope, long InputTokens, long OutputTokens, long CacheReadTokens, long CacheCreationTokens, int Messages); public sealed record TaskUsageRowDto( string TaskId, string TaskTitle, string ListId, string ListName, string? Model, int Runs, long TokensIn, long TokensOut);