The resolver restarts the merge via StartConflictMerge, which had no autostash. After a stash-retry approve reported 'conflict', the restart hit the same dirty target, got 'dirty_target' and the resolver silently never opened. Thread the consent through the hub, client, coordinator and resolver; surface a refusal in the footer instead of dropping it. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
834 lines
39 KiB
C#
834 lines
39 KiB
C#
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 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<ActiveTaskDto> ActiveTasks { get; } = new();
|
|
|
|
public event Action<string, string, DateTime>? TaskStartedEvent;
|
|
public event Action<string, string, string, DateTime>? TaskFinishedEvent;
|
|
public event Action<string, string>? TaskMessageEvent;
|
|
public event Action<string>? TaskUpdatedEvent;
|
|
public event Action<string, string, string>? TaskQuestionAskedEvent;
|
|
public event Action<string, string>? TaskQuestionResolvedEvent;
|
|
public event Action<string, IReadOnlyList<string>, string>? HandoffRequestedEvent;
|
|
public event Action<string, string>? SessionHandoffRequestedEvent;
|
|
public event Action<string, IReadOnlyList<string>>? ListHandlerStartRequestedEvent;
|
|
public event Action<string, string>? HandlerUsageWarningEvent;
|
|
public event Action<string, DateTimeOffset>? HandlerHandoffDeferredEvent;
|
|
public event Action? ConnectionRestoredEvent;
|
|
public event Action<string>? WorktreeUpdatedEvent;
|
|
public event Action<string>? ListUpdatedEvent;
|
|
public event Action<WorkerLogEntry>? WorkerLogReceivedEvent;
|
|
|
|
public event Action? NotesUpdatedEvent;
|
|
public event Action? PrepStartedEvent;
|
|
public event Action<string>? PrepLineEvent;
|
|
public event Action<bool>? PrepFinishedEvent;
|
|
|
|
public event Action<string>? RefineStartedEvent;
|
|
public event Action<string, bool, string?>? RefineFinishedEvent;
|
|
|
|
public event Action<UsageSnapshotDto>? UsageUpdatedEvent;
|
|
|
|
public event Action<string, string, int, int>? OperationProgressEvent;
|
|
public event Action<RunningOperationDto>? OperationStartedEvent;
|
|
public event Action<string>? OperationEndedEvent;
|
|
|
|
public event Action<string, string>? PlanningMergeStartedEvent;
|
|
public event Action<string, string>? PlanningSubtaskMergedEvent;
|
|
public event Action<string, string, IReadOnlyList<string>, bool>? PlanningMergeConflictEvent;
|
|
public event Action<string, string?>? PlanningMergeAbortedEvent;
|
|
public event Action<string>? PlanningCompletedEvent;
|
|
|
|
public event Action<PrimeFiredEvent>? PrimeFired;
|
|
|
|
public string? LastApproveTarget { get; private set; }
|
|
|
|
public IReadOnlyList<ActiveTaskDto> 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<string, string, DateTime>("TaskStarted", (slot, taskId, startedAt) =>
|
|
{
|
|
Dispatcher.UIThread.Post(() =>
|
|
{
|
|
ActiveTasks.Add(new ActiveTaskDto(slot, taskId, startedAt));
|
|
TaskStartedEvent?.Invoke(slot, taskId, startedAt);
|
|
});
|
|
});
|
|
|
|
_hub.On<string, string, string, DateTime>("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<string, string>("TaskMessage", (taskId, line) =>
|
|
{
|
|
Dispatcher.UIThread.Post(() => TaskMessageEvent?.Invoke(taskId, line));
|
|
});
|
|
|
|
_hub.On<string>("TaskUpdated", taskId =>
|
|
{
|
|
Dispatcher.UIThread.Post(() => TaskUpdatedEvent?.Invoke(taskId));
|
|
});
|
|
|
|
_hub.On<string, string, string>("TaskQuestionAsked", (taskId, questionId, question) =>
|
|
{
|
|
Dispatcher.UIThread.Post(() => TaskQuestionAskedEvent?.Invoke(taskId, questionId, question));
|
|
});
|
|
|
|
_hub.On<string, string>("TaskQuestionResolved", (taskId, questionId) =>
|
|
{
|
|
Dispatcher.UIThread.Post(() => TaskQuestionResolvedEvent?.Invoke(taskId, questionId));
|
|
});
|
|
|
|
_hub.On<string, IReadOnlyList<string>, string>("HandoffRequested", (taskId, survivingTaskIds, nextPhase) =>
|
|
{
|
|
Dispatcher.UIThread.Post(() => HandoffRequestedEvent?.Invoke(taskId, survivingTaskIds, nextPhase));
|
|
});
|
|
|
|
_hub.On<string, string>("SessionHandoffRequested", (paneId, briefPath) =>
|
|
{
|
|
Dispatcher.UIThread.Post(() => SessionHandoffRequestedEvent?.Invoke(paneId, briefPath));
|
|
});
|
|
|
|
_hub.On<string, IReadOnlyList<string>>("ListHandlerStartRequested", (listId, taskIds) =>
|
|
{
|
|
Dispatcher.UIThread.Post(() => ListHandlerStartRequestedEvent?.Invoke(listId, taskIds));
|
|
});
|
|
|
|
_hub.On<string, string>("HandlerUsageWarning", (taskId, message) =>
|
|
{
|
|
Dispatcher.UIThread.Post(() => HandlerUsageWarningEvent?.Invoke(taskId, message));
|
|
});
|
|
|
|
_hub.On<string, DateTimeOffset>("HandlerHandoffDeferred", (taskId, dueAt) =>
|
|
{
|
|
Dispatcher.UIThread.Post(() => HandlerHandoffDeferredEvent?.Invoke(taskId, dueAt));
|
|
});
|
|
|
|
_hub.On<string>("WorktreeUpdated", taskId =>
|
|
{
|
|
Dispatcher.UIThread.Post(() => WorktreeUpdatedEvent?.Invoke(taskId));
|
|
});
|
|
|
|
_hub.On<string>("ListUpdated", listId =>
|
|
{
|
|
Dispatcher.UIThread.Post(() => ListUpdatedEvent?.Invoke(listId));
|
|
});
|
|
|
|
_hub.On<string, WorkerLogLevel, DateTime>("WorkerLog", (message, level, timestampUtc) =>
|
|
{
|
|
Dispatcher.UIThread.Post(() =>
|
|
WorkerLogReceivedEvent?.Invoke(new WorkerLogEntry(message, level, timestampUtc)));
|
|
});
|
|
|
|
_hub.On<string, string, int, int>("OperationProgress", (opKey, phase, current, total) =>
|
|
{
|
|
Dispatcher.UIThread.Post(() => OperationProgressEvent?.Invoke(opKey, phase, current, total));
|
|
});
|
|
|
|
_hub.On<RunningOperationDto>("OperationStarted", op =>
|
|
{
|
|
Dispatcher.UIThread.Post(() => OperationStartedEvent?.Invoke(op));
|
|
});
|
|
|
|
_hub.On<string>("OperationEnded", id =>
|
|
{
|
|
Dispatcher.UIThread.Post(() => OperationEndedEvent?.Invoke(id));
|
|
});
|
|
|
|
_hub.On<string, string>("PlanningMergeStarted", (planningTaskId, targetBranch) =>
|
|
{
|
|
Dispatcher.UIThread.Post(() => PlanningMergeStartedEvent?.Invoke(planningTaskId, targetBranch));
|
|
});
|
|
|
|
_hub.On<string, string>("PlanningSubtaskMerged", (planningTaskId, subtaskId) =>
|
|
{
|
|
Dispatcher.UIThread.Post(() => PlanningSubtaskMergedEvent?.Invoke(planningTaskId, subtaskId));
|
|
});
|
|
|
|
_hub.On<string, string, IReadOnlyList<string>, bool>("PlanningMergeConflict", (planningTaskId, subtaskId, conflictedFiles, externallyDriven) =>
|
|
{
|
|
Dispatcher.UIThread.Post(() => PlanningMergeConflictEvent?.Invoke(planningTaskId, subtaskId, conflictedFiles, externallyDriven));
|
|
});
|
|
|
|
_hub.On<string, string?>("PlanningMergeAborted", (planningTaskId, reason) =>
|
|
{
|
|
Dispatcher.UIThread.Post(() => PlanningMergeAbortedEvent?.Invoke(planningTaskId, reason));
|
|
});
|
|
|
|
_hub.On<string>("PlanningCompleted", planningTaskId =>
|
|
{
|
|
Dispatcher.UIThread.Post(() => PlanningCompletedEvent?.Invoke(planningTaskId));
|
|
});
|
|
|
|
_hub.On<Guid, bool, string, DateTimeOffset>("PrimeFired", (id, ok, msg, when) =>
|
|
{
|
|
Dispatcher.UIThread.Post(() => PrimeFired?.Invoke(new PrimeFiredEvent(id, ok, msg, when)));
|
|
});
|
|
|
|
_hub.On("NotesUpdated", () => Dispatcher.UIThread.Post(() => NotesUpdatedEvent?.Invoke()));
|
|
_hub.On("PrepStarted", () => Dispatcher.UIThread.Post(() => PrepStartedEvent?.Invoke()));
|
|
_hub.On<string>("PrepLine", line => Dispatcher.UIThread.Post(() => PrepLineEvent?.Invoke(line)));
|
|
_hub.On<bool>("PrepFinished", ok => Dispatcher.UIThread.Post(() => PrepFinishedEvent?.Invoke(ok)));
|
|
|
|
_hub.On<string>("RefineStarted", id =>
|
|
Dispatcher.UIThread.Post(() => RefineStartedEvent?.Invoke(id)));
|
|
_hub.On<string, bool, string?>("RefineFinished", (id, ok, err) =>
|
|
Dispatcher.UIThread.Post(() => RefineFinishedEvent?.Invoke(id, ok, err)));
|
|
|
|
_hub.On<UsageSnapshotDto>("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 */ }
|
|
}
|
|
|
|
/// <summary>Invoke a hub method, returning default (null) when the worker is offline or errors.</summary>
|
|
private async Task<T?> TryInvokeAsync<T>(string method, params object?[] args)
|
|
{
|
|
var sw = System.Diagnostics.Stopwatch.StartNew();
|
|
var ok = false;
|
|
try
|
|
{
|
|
var result = await _hub.InvokeCoreAsync<T>(method, args);
|
|
ok = true;
|
|
return result;
|
|
}
|
|
catch (Exception ex) when (ex is not OperationCanceledException)
|
|
{
|
|
Serilog.Log.Warning(ex, "Hub call {method_name} failed", method);
|
|
return default;
|
|
}
|
|
catch { return default; }
|
|
finally { OperationTiming.Shared.Record("hub", method, sw.Elapsed, ok); }
|
|
}
|
|
|
|
/// <summary>Times a void hub invoke without changing its exception/cancellation behavior.</summary>
|
|
private async Task InvokeTimedAsync(string method, Func<Task> invoke)
|
|
{
|
|
var sw = System.Diagnostics.Stopwatch.StartNew();
|
|
var ok = false;
|
|
try
|
|
{
|
|
await invoke();
|
|
ok = true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Serilog.Log.Warning(ex, "Hub call {method_name} failed", method);
|
|
throw;
|
|
}
|
|
finally { OperationTiming.Shared.Record("hub", method, sw.Elapsed, ok); }
|
|
}
|
|
|
|
/// <summary>Times a hub invoke that returns a value, without changing its exception/cancellation behavior.</summary>
|
|
private async Task<T> InvokeTimedAsync<T>(string method, Func<Task<T>> invoke)
|
|
{
|
|
var sw = System.Diagnostics.Stopwatch.StartNew();
|
|
var ok = false;
|
|
try
|
|
{
|
|
var result = await invoke();
|
|
ok = true;
|
|
return result;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Serilog.Log.Warning(ex, "Hub call {method_name} failed", method);
|
|
throw;
|
|
}
|
|
finally { OperationTiming.Shared.Record("hub", method, sw.Elapsed, ok); }
|
|
}
|
|
|
|
public async Task RunNowAsync(string taskId)
|
|
{
|
|
await InvokeTimedAsync("RunNow", () => _hub.InvokeAsync("RunNow", taskId));
|
|
}
|
|
|
|
public async Task ContinueTaskAsync(string taskId, string followUpPrompt)
|
|
{
|
|
await InvokeTimedAsync("ContinueTask", () => _hub.InvokeAsync("ContinueTask", taskId, followUpPrompt));
|
|
}
|
|
|
|
public async Task AnswerTaskQuestionAsync(string taskId, string questionId, string answer)
|
|
{
|
|
try { await InvokeTimedAsync<bool>("AnswerTaskQuestion", () => _hub.InvokeAsync<bool>("AnswerTaskQuestion", taskId, questionId, answer)); }
|
|
catch { /* offline or already resolved — the UI clears optimistically */ }
|
|
}
|
|
|
|
public Task<PendingQuestionDto?> GetPendingQuestionAsync(string taskId)
|
|
=> TryInvokeAsync<PendingQuestionDto>("GetPendingQuestion", taskId);
|
|
|
|
public async Task ResetTaskAsync(string taskId)
|
|
{
|
|
await InvokeTimedAsync("ResetTask", () => _hub.InvokeAsync("ResetTask", taskId));
|
|
}
|
|
|
|
public async Task<MergeResultDto> MergeTaskAsync(
|
|
string taskId, string targetBranch, bool removeWorktree, string commitMessage, bool autostash = false)
|
|
{
|
|
return await InvokeTimedAsync<MergeResultDto>("MergeTask", () => _hub.InvokeAsync<MergeResultDto>(
|
|
"MergeTask", taskId, targetBranch, removeWorktree, commitMessage, autostash));
|
|
}
|
|
|
|
public Task<MergeResultDto> StartConflictMergeAsync(string taskId, string targetBranch, bool autostash = false)
|
|
=> InvokeTimedAsync<MergeResultDto>("StartConflictMerge", () => _hub.InvokeAsync<MergeResultDto>("StartConflictMerge", taskId, targetBranch, autostash));
|
|
|
|
public Task<MergeConflictDocumentsDto> GetMergeConflictDocumentsAsync(string taskId)
|
|
=> InvokeTimedAsync<MergeConflictDocumentsDto>("GetMergeConflictDocuments", () => _hub.InvokeAsync<MergeConflictDocumentsDto>("GetMergeConflictDocuments", taskId));
|
|
|
|
public Task WriteConflictResolutionAsync(string taskId, string path, string resolvedContent)
|
|
=> InvokeTimedAsync("WriteConflictResolution", () => _hub.InvokeAsync("WriteConflictResolution", taskId, path, resolvedContent));
|
|
|
|
public Task<MergeResultDto> ContinueConflictMergeAsync(string taskId)
|
|
=> InvokeTimedAsync<MergeResultDto>("ContinueConflictMerge", () => _hub.InvokeAsync<MergeResultDto>("ContinueConflictMerge", taskId));
|
|
|
|
public Task AbortConflictMergeAsync(string taskId)
|
|
=> InvokeTimedAsync("AbortConflictMerge", () => _hub.InvokeAsync("AbortConflictMerge", taskId));
|
|
|
|
public Task<MergeTargetsDto?> GetMergeTargetsAsync(string taskId)
|
|
=> TryInvokeAsync<MergeTargetsDto>("GetMergeTargets", taskId);
|
|
|
|
public async Task CancelTaskAsync(string taskId)
|
|
{
|
|
await InvokeTimedAsync("CancelTask", () => _hub.InvokeAsync("CancelTask", taskId));
|
|
}
|
|
|
|
public async Task<(bool Ok, string? Error)> DeleteTaskAsync(string taskId)
|
|
{
|
|
try
|
|
{
|
|
await InvokeTimedAsync("DeleteTask", () => _hub.InvokeAsync("DeleteTask", taskId, CancellationToken.None));
|
|
return (true, null);
|
|
}
|
|
catch (HubException ex)
|
|
{
|
|
return (false, ex.Message);
|
|
}
|
|
catch (InvalidOperationException)
|
|
{
|
|
// Hub connection is not active (worker offline / not yet connected).
|
|
return (false, "Worker is offline. Reconnect and try again.");
|
|
}
|
|
}
|
|
|
|
public async Task WakeQueueAsync()
|
|
{
|
|
await InvokeTimedAsync("WakeQueue", () => _hub.InvokeAsync("WakeQueue"));
|
|
}
|
|
|
|
public async Task<List<AgentInfo>> GetAgentsAsync()
|
|
=> await TryInvokeAsync<List<AgentInfo>>("GetAgents") ?? [];
|
|
|
|
public async Task RefreshAgentsAsync()
|
|
{
|
|
await InvokeTimedAsync("RefreshAgents", () => _hub.InvokeAsync("RefreshAgents"));
|
|
}
|
|
|
|
public async Task<SeedResultDto?> RestoreDefaultAgentsAsync()
|
|
=> await InvokeTimedAsync<SeedResultDto>("RestoreDefaultAgents", () => _hub.InvokeAsync<SeedResultDto>("RestoreDefaultAgents"));
|
|
|
|
private async Task SeedActiveTasksAsync()
|
|
{
|
|
try
|
|
{
|
|
var active = await InvokeTimedAsync<List<ActiveTaskDto>>("GetActive", () => _hub.InvokeAsync<List<ActiveTaskDto>>("GetActive"));
|
|
Dispatcher.UIThread.Post(() =>
|
|
{
|
|
ActiveTasks.Clear();
|
|
foreach (var a in active)
|
|
ActiveTasks.Add(new ActiveTaskDto(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<AppSettingsDto?> GetAppSettingsAsync()
|
|
=> TryInvokeAsync<AppSettingsDto>("GetAppSettings");
|
|
|
|
public async Task UpdateAppSettingsAsync(AppSettingsDto dto)
|
|
{
|
|
await InvokeTimedAsync("UpdateAppSettings", () => _hub.InvokeAsync("UpdateAppSettings", dto));
|
|
}
|
|
|
|
public async Task<List<PrimeScheduleDto>> GetPrimeSchedulesAsync()
|
|
=> await TryInvokeAsync<List<PrimeScheduleDto>>("ListPrimeSchedules") ?? new List<PrimeScheduleDto>();
|
|
|
|
public async Task<PrimeScheduleDto?> UpsertPrimeScheduleAsync(PrimeScheduleDto dto)
|
|
=> await InvokeTimedAsync<PrimeScheduleDto>("UpsertPrimeSchedule", () => _hub.InvokeAsync<PrimeScheduleDto>("UpsertPrimeSchedule", dto));
|
|
|
|
public async Task DeletePrimeScheduleAsync(Guid id)
|
|
{
|
|
try { await InvokeTimedAsync("DeletePrimeSchedule", () => _hub.InvokeAsync("DeletePrimeSchedule", id)); }
|
|
catch { /* offline */ }
|
|
}
|
|
|
|
private static string IsoDay(DateOnly d) => d.ToString("yyyy-MM-dd");
|
|
|
|
public Task<string?> GetWeekReportAsync(DateOnly start, DateOnly end)
|
|
=> TryInvokeAsync<string>("GetWeekReport", IsoDay(start), IsoDay(end));
|
|
|
|
public Task<string> GenerateWeekReportAsync(DateOnly start, DateOnly end)
|
|
=> InvokeTimedAsync<string>("GenerateWeekReport", () => _hub.InvokeAsync<string>("GenerateWeekReport", IsoDay(start), IsoDay(end)));
|
|
|
|
public Task<bool> RunDailyPrepNowAsync()
|
|
=> InvokeTimedAsync<bool>("RunDailyPrepNow", () => _hub.InvokeAsync<bool>("RunDailyPrepNow"));
|
|
|
|
public Task<bool> RunTicketImportNowAsync()
|
|
=> InvokeTimedAsync<bool>("RunTicketImportNow", () => _hub.InvokeAsync<bool>("RunTicketImportNow"));
|
|
|
|
public Task RefineTaskAsync(string taskId) => InvokeTimedAsync("RefineTask", () => _hub.InvokeAsync("RefineTask", taskId));
|
|
|
|
public Task ClearMyDayAsync()
|
|
=> InvokeTimedAsync("ClearMyDay", () => _hub.InvokeAsync("ClearMyDay"));
|
|
|
|
public async Task<List<DailyNoteDto>> GetDailyNotesAsync(DateOnly day)
|
|
=> await TryInvokeAsync<List<DailyNoteDto>>("GetDailyNotes", IsoDay(day)) ?? new List<DailyNoteDto>();
|
|
|
|
public async Task<List<DailyNoteDto>> GetDailyNotesBetweenAsync(DateOnly start, DateOnly end)
|
|
=> await TryInvokeAsync<List<DailyNoteDto>>("GetDailyNotesBetween", IsoDay(start), IsoDay(end)) ?? new List<DailyNoteDto>();
|
|
|
|
public async Task<DailyNoteDto?> AddDailyNoteAsync(DateOnly day, string text)
|
|
=> await InvokeTimedAsync<DailyNoteDto>("AddDailyNote", () => _hub.InvokeAsync<DailyNoteDto>("AddDailyNote", IsoDay(day), text));
|
|
|
|
public async Task UpdateDailyNoteAsync(string id, string text)
|
|
=> await InvokeTimedAsync("UpdateDailyNote", () => _hub.InvokeAsync("UpdateDailyNote", id, text));
|
|
|
|
public async Task DeleteDailyNoteAsync(string id)
|
|
=> await InvokeTimedAsync("DeleteDailyNote", () => _hub.InvokeAsync("DeleteDailyNote", id));
|
|
|
|
public async Task<string> GetLastPrepLogAsync()
|
|
=> await TryInvokeAsync<string>("GetLastPrepLog") ?? string.Empty;
|
|
|
|
public async Task<IReadOnlyList<RunningOperationDto>> GetActiveOperationsAsync()
|
|
=> await TryInvokeAsync<List<RunningOperationDto>>("GetActiveOperations") ?? new List<RunningOperationDto>();
|
|
|
|
public async Task<IReadOnlyList<WorkerLogEntry>> GetRecentLogsAsync()
|
|
=> await TryInvokeAsync<List<WorkerLogEntry>>("GetRecentLogs") ?? new List<WorkerLogEntry>();
|
|
|
|
public async Task UpdateListAsync(UpdateListDto dto)
|
|
{
|
|
await InvokeTimedAsync("UpdateList", () => _hub.InvokeAsync("UpdateList", dto));
|
|
}
|
|
|
|
public async Task UpdateListConfigAsync(UpdateListConfigDto dto)
|
|
{
|
|
await InvokeTimedAsync("UpdateListConfig", () => _hub.InvokeAsync("UpdateListConfig", dto));
|
|
}
|
|
|
|
public Task<ListConfigDto?> GetListConfigAsync(string listId)
|
|
=> TryInvokeAsync<ListConfigDto>("GetListConfig", listId);
|
|
|
|
public async Task UpdateTaskAgentSettingsAsync(UpdateTaskAgentSettingsDto dto)
|
|
{
|
|
await InvokeTimedAsync("UpdateTaskAgentSettings", () => _hub.InvokeAsync("UpdateTaskAgentSettings", dto));
|
|
}
|
|
|
|
public async Task<List<string>> GetRepoImportFoldersAsync()
|
|
=> await InvokeTimedAsync<List<string>>("GetRepoImportFolders", () => _hub.InvokeAsync<List<string>>("GetRepoImportFolders"));
|
|
|
|
public Task SetRepoImportFoldersAsync(List<string> folders)
|
|
=> InvokeTimedAsync("SetRepoImportFolders", () => _hub.InvokeAsync("SetRepoImportFolders", folders));
|
|
|
|
public async Task<List<SshWorkspace>> GetSshWorkspacesAsync()
|
|
=> await InvokeTimedAsync<List<SshWorkspace>>("GetSshWorkspaces", () => _hub.InvokeAsync<List<SshWorkspace>>("GetSshWorkspaces"));
|
|
|
|
public Task SetSshWorkspacesAsync(List<SshWorkspace> workspaces)
|
|
=> InvokeTimedAsync("SetSshWorkspaces", () => _hub.InvokeAsync("SetSshWorkspaces", workspaces));
|
|
|
|
public async Task<List<SessionSkillDto>> GetSessionSkillsAsync()
|
|
=> await TryInvokeAsync<List<SessionSkillDto>>("GetSessionSkills") ?? [];
|
|
|
|
public Task<List<string>> InstallSessionSkillAsync(string url)
|
|
=> InvokeTimedAsync<List<string>>("InstallSessionSkill", () => _hub.InvokeAsync<List<string>>("InstallSessionSkill", url));
|
|
|
|
public Task UpdateSessionSkillAsync(string sourceUrl)
|
|
=> InvokeTimedAsync("UpdateSessionSkill", () => _hub.InvokeAsync("UpdateSessionSkill", sourceUrl));
|
|
|
|
public Task RemoveSessionSkillAsync(string sourceUrl)
|
|
=> InvokeTimedAsync("RemoveSessionSkill", () => _hub.InvokeAsync("RemoveSessionSkill", sourceUrl));
|
|
|
|
public async Task<BaseDirtyWarningDto?> SetTaskStatusAsync(string taskId, ClaudeDo.Data.Models.TaskStatus status)
|
|
{
|
|
var result = await InvokeTimedAsync<SetTaskStatusResultDto>("SetTaskStatus", () => _hub.InvokeAsync<SetTaskStatusResultDto>("SetTaskStatus", taskId, status.ToString()));
|
|
return result?.BaseDirty;
|
|
}
|
|
|
|
public Task SetTaskDoneAsync(string taskId)
|
|
=> InvokeTimedAsync("SetTaskDone", () => _hub.InvokeAsync("SetTaskDone", taskId));
|
|
|
|
public Task UnsetTaskDoneAsync(string taskId)
|
|
=> InvokeTimedAsync("UnsetTaskDone", () => _hub.InvokeAsync("UnsetTaskDone", taskId));
|
|
|
|
public Task DequeueTaskAsync(string taskId)
|
|
=> InvokeTimedAsync("DequeueTask", () => _hub.InvokeAsync("DequeueTask", taskId));
|
|
|
|
public async Task<MergeResultDto?> ApproveReviewAsync(string taskId, string targetBranch, bool autostash = false)
|
|
{
|
|
LastApproveTarget = targetBranch;
|
|
return await InvokeTimedAsync<MergeResultDto>("ApproveReview", () => _hub.InvokeAsync<MergeResultDto>("ApproveReview", taskId, targetBranch, autostash));
|
|
}
|
|
|
|
public Task<MergePreviewDto?> PreviewMergeAsync(string taskId, string targetBranch)
|
|
=> TryInvokeAsync<MergePreviewDto>("PreviewMerge", taskId, targetBranch);
|
|
|
|
public async Task RejectReviewToQueueAsync(string taskId, string feedback)
|
|
{
|
|
await InvokeTimedAsync("RejectReviewToQueue", () => _hub.InvokeAsync("RejectReviewToQueue", taskId, feedback));
|
|
}
|
|
|
|
public async Task RejectReviewToIdleAsync(string taskId)
|
|
{
|
|
await InvokeTimedAsync("RejectReviewToIdle", () => _hub.InvokeAsync("RejectReviewToIdle", taskId));
|
|
}
|
|
|
|
public async Task CancelReviewAsync(string taskId)
|
|
{
|
|
await InvokeTimedAsync("CancelReview", () => _hub.InvokeAsync("CancelReview", taskId));
|
|
}
|
|
|
|
public async Task<WorktreeCleanupDto?> CleanupFinishedWorktreesAsync(string? listId = null)
|
|
=> await InvokeTimedAsync<WorktreeCleanupDto>("CleanupFinishedWorktrees", () => _hub.InvokeAsync<WorktreeCleanupDto>("CleanupFinishedWorktrees", listId));
|
|
|
|
public async Task<WorktreeResetDto?> ResetAllWorktreesAsync()
|
|
=> await InvokeTimedAsync<WorktreeResetDto>("ResetAllWorktrees", () => _hub.InvokeAsync<WorktreeResetDto>("ResetAllWorktrees"));
|
|
|
|
public async Task<List<WorktreeOverviewDto>> GetWorktreesOverviewAsync(string? listId)
|
|
=> await TryInvokeAsync<List<WorktreeOverviewDto>>("GetWorktreesOverview", listId)
|
|
?? new List<WorktreeOverviewDto>();
|
|
|
|
public async Task<(bool Ok, string? Error)> SetWorktreeStateAsync(string taskId, WorktreeState newState)
|
|
{
|
|
try
|
|
{
|
|
var ok = await InvokeTimedAsync<bool>("SetWorktreeState", () => _hub.InvokeAsync<bool>("SetWorktreeState", taskId, newState));
|
|
return (ok, null);
|
|
}
|
|
catch (HubException ex)
|
|
{
|
|
return (false, ex.Message);
|
|
}
|
|
catch (Exception)
|
|
{
|
|
return (false, "Worker offline.");
|
|
}
|
|
}
|
|
|
|
public async Task<ForceRemoveResultDto?> ForceRemoveWorktreeAsync(string taskId)
|
|
=> await InvokeTimedAsync<ForceRemoveResultDto>("ForceRemoveWorktree", () => _hub.InvokeAsync<ForceRemoveResultDto>("ForceRemoveWorktree", taskId));
|
|
|
|
public async Task<PlanningSessionStartInfo> StartPlanningSessionAsync(string taskId, CancellationToken ct = default)
|
|
=> await InvokeTimedAsync<PlanningSessionStartInfo>("StartPlanningSessionAsync", () => _hub.InvokeAsync<PlanningSessionStartInfo>("StartPlanningSessionAsync", taskId, ct));
|
|
|
|
public async Task<PlanningSessionResumeInfo> ResumePlanningSessionAsync(string taskId, CancellationToken ct = default)
|
|
=> await InvokeTimedAsync<PlanningSessionResumeInfo>("ResumePlanningSessionAsync", () => _hub.InvokeAsync<PlanningSessionResumeInfo>("ResumePlanningSessionAsync", taskId, ct));
|
|
|
|
public async Task SubmitTaskForReviewAsync(string taskId, CancellationToken ct = default)
|
|
=> await InvokeTimedAsync("SubmitTaskForReview", () => _hub.InvokeAsync("SubmitTaskForReview", taskId, ct));
|
|
|
|
public async Task<LaunchSpec> GetInteractiveLaunchSpecAsync(string taskId, CancellationToken ct = default)
|
|
=> await InvokeTimedAsync<LaunchSpec>("GetInteractiveLaunchSpec", () => _hub.InvokeAsync<LaunchSpec>("GetInteractiveLaunchSpec", taskId, ct));
|
|
|
|
public async Task<LaunchSpec> GetAdHocLaunchSpecAsync(string directory, string paneId, CancellationToken ct = default)
|
|
=> await InvokeTimedAsync<LaunchSpec>("GetAdHocLaunchSpec", () => _hub.InvokeAsync<LaunchSpec>("GetAdHocLaunchSpec", directory, paneId, ct));
|
|
|
|
public async Task<LaunchSpec> GetSshLaunchSpecAsync(string workspaceId, string paneId, CancellationToken ct = default)
|
|
=> await InvokeTimedAsync<LaunchSpec>("GetSshLaunchSpec", () => _hub.InvokeAsync<LaunchSpec>("GetSshLaunchSpec", workspaceId, paneId, ct));
|
|
|
|
public async Task<LaunchSpec> GetSessionHandoffLaunchSpecAsync(string paneId, string cwd, string briefPath, string? taskId, CancellationToken ct = default)
|
|
=> await InvokeTimedAsync<LaunchSpec>("GetSessionHandoffLaunchSpec", () => _hub.InvokeAsync<LaunchSpec>("GetSessionHandoffLaunchSpec", paneId, cwd, briefPath, taskId, ct));
|
|
|
|
public async Task<LaunchSpec> GetResumeSessionLaunchSpecAsync(string sessionId, string paneId, CancellationToken ct = default)
|
|
=> await InvokeTimedAsync<LaunchSpec>("GetResumeSessionLaunchSpec", () => _hub.InvokeAsync<LaunchSpec>("GetResumeSessionLaunchSpec", sessionId, paneId, ct));
|
|
|
|
public async Task<LaunchSpec> GetMergeHelperLaunchSpecAsync(IReadOnlyList<string> taskIds, string listId, string handlerTaskId, string? model = null, CancellationToken ct = default)
|
|
=> await InvokeTimedAsync<LaunchSpec>("GetMergeHelperLaunchSpec", () => _hub.InvokeAsync<LaunchSpec>("GetMergeHelperLaunchSpec", taskIds, listId, handlerTaskId, model, ct));
|
|
|
|
public async Task<string> CreateMergeHelperTaskAsync(
|
|
IReadOnlyList<string> taskIds, string listId, string title, string descriptionHeader, CancellationToken ct = default)
|
|
=> await InvokeTimedAsync<string>("CreateMergeHelperTask", () => _hub.InvokeAsync<string>("CreateMergeHelperTask", taskIds, listId, title, descriptionHeader, ct));
|
|
|
|
public async Task<LaunchSpec> GetMergeHelperHandoffLaunchSpecAsync(
|
|
string taskId, IReadOnlyList<string> survivingTaskIds, string nextPhase, CancellationToken ct = default)
|
|
=> await InvokeTimedAsync<LaunchSpec>("GetMergeHelperHandoffLaunchSpec", () => _hub.InvokeAsync<LaunchSpec>("GetMergeHelperHandoffLaunchSpec", taskId, survivingTaskIds, nextPhase, ct));
|
|
|
|
public Task ReportInteractiveSessionExitedAsync(string taskId, int exitCode, CancellationToken ct = default)
|
|
=> InvokeTimedAsync("ReportInteractiveSessionExited", () => _hub.InvokeAsync("ReportInteractiveSessionExited", taskId, exitCode, ct));
|
|
|
|
public async Task<IReadOnlyList<HandlerOpenTaskDto>> GetHandlerOpenTasksAsync(string handlerTaskId, CancellationToken ct = default)
|
|
=> await InvokeTimedAsync<List<HandlerOpenTaskDto>>("GetHandlerOpenTasks", () => _hub.InvokeAsync<List<HandlerOpenTaskDto>>("GetHandlerOpenTasks", handlerTaskId, ct));
|
|
|
|
public async Task<LaunchSpec> GetPlanningStartLaunchSpecAsync(string taskId, CancellationToken ct = default)
|
|
=> await InvokeTimedAsync<LaunchSpec>("GetPlanningStartLaunchSpec", () => _hub.InvokeAsync<LaunchSpec>("GetPlanningStartLaunchSpec", taskId, ct));
|
|
|
|
public async Task<LaunchSpec> GetPlanningResumeLaunchSpecAsync(string taskId, CancellationToken ct = default)
|
|
=> await InvokeTimedAsync<LaunchSpec>("GetPlanningResumeLaunchSpec", () => _hub.InvokeAsync<LaunchSpec>("GetPlanningResumeLaunchSpec", taskId, ct));
|
|
|
|
public async Task<DiscardPlanningOutcome> DiscardPlanningSessionAsync(string taskId, bool dequeueQueuedChildren = false, CancellationToken ct = default)
|
|
=> await InvokeTimedAsync<DiscardPlanningOutcome>("DiscardPlanningSessionAsync", () => _hub.InvokeAsync<DiscardPlanningOutcome>("DiscardPlanningSessionAsync", taskId, dequeueQueuedChildren, ct));
|
|
|
|
public async Task<int> FinalizePlanningSessionAsync(string taskId, bool queueAgentTasks = true, CancellationToken ct = default)
|
|
=> await InvokeTimedAsync<int>("FinalizePlanningSessionAsync", () => _hub.InvokeAsync<int>("FinalizePlanningSessionAsync", taskId, queueAgentTasks, ct));
|
|
|
|
public async Task<int> GetPendingDraftCountAsync(string taskId, CancellationToken ct = default)
|
|
=> await InvokeTimedAsync<int>("GetPendingDraftCountAsync", () => _hub.InvokeAsync<int>("GetPendingDraftCountAsync", taskId, ct));
|
|
|
|
public async Task<IReadOnlyList<SubtaskDiffDto>> GetPlanningAggregateAsync(string planningTaskId)
|
|
=> await TryInvokeAsync<List<SubtaskDiffDto>>("GetPlanningAggregate", planningTaskId) ?? [];
|
|
|
|
public async Task<CombinedDiffResultDto?> BuildPlanningIntegrationBranchAsync(string planningTaskId, string targetBranch)
|
|
=> await InvokeTimedAsync<CombinedDiffResultDto>("BuildPlanningIntegrationBranch", () => _hub.InvokeAsync<CombinedDiffResultDto>("BuildPlanningIntegrationBranch", planningTaskId, targetBranch));
|
|
|
|
public async Task ContinuePlanningMergeAsync(string planningTaskId)
|
|
{
|
|
await InvokeTimedAsync("ContinuePlanningMerge", () => _hub.InvokeAsync("ContinuePlanningMerge", planningTaskId));
|
|
}
|
|
|
|
public async Task AbortPlanningMergeAsync(string planningTaskId)
|
|
{
|
|
await InvokeTimedAsync("AbortPlanningMerge", () => _hub.InvokeAsync("AbortPlanningMerge", planningTaskId));
|
|
}
|
|
|
|
public async Task<IReadOnlyList<PlanningMergeConflictStateDto>> GetActiveExternalPlanningMergeConflictsAsync()
|
|
=> await TryInvokeAsync<List<PlanningMergeConflictStateDto>>("GetActiveExternalPlanningMergeConflicts")
|
|
?? [];
|
|
|
|
public async Task QueuePlanningSubtasksAsync(string parentTaskId, CancellationToken ct = default)
|
|
{
|
|
await InvokeTimedAsync("QueuePlanningSubtasksAsync", () => _hub.InvokeAsync("QueuePlanningSubtasksAsync", parentTaskId, ct));
|
|
}
|
|
|
|
public Task<ClaudeBinDto?> GetClaudeBinAsync()
|
|
=> TryInvokeAsync<ClaudeBinDto>("GetClaudeBin");
|
|
|
|
public Task<ClaudeBinDto?> SetClaudeBinAsync(string? value)
|
|
=> TryInvokeAsync<ClaudeBinDto>("SetClaudeBin", value);
|
|
|
|
public Task<OnlineInboxStateDto?> GetOnlineInboxStateAsync()
|
|
=> TryInvokeAsync<OnlineInboxStateDto>("GetOnlineInboxState");
|
|
|
|
public async Task SetOnlineInboxConfigAsync(OnlineInboxConfigInputDto input)
|
|
=> await InvokeTimedAsync("SetOnlineInboxConfig", () => _hub.InvokeAsync("SetOnlineInboxConfig", input));
|
|
|
|
public async Task SetOnlineInboxAuthAsync(string refreshToken)
|
|
=> await InvokeTimedAsync("SetOnlineInboxAuth", () => _hub.InvokeAsync("SetOnlineInboxAuth", refreshToken));
|
|
|
|
public async Task ClearOnlineInboxAuthAsync()
|
|
=> await InvokeTimedAsync("ClearOnlineInboxAuth", () => _hub.InvokeAsync("ClearOnlineInboxAuth"));
|
|
|
|
public Task<UsageSnapshotDto?> GetUsageSnapshotAsync()
|
|
=> TryInvokeAsync<UsageSnapshotDto>("GetUsageSnapshot");
|
|
|
|
public Task<UsageSnapshotDto?> RefreshUsageAsync()
|
|
=> TryInvokeAsync<UsageSnapshotDto>("RefreshUsage");
|
|
|
|
public async Task<IReadOnlyList<ModelUsageRowDto>> GetModelUsageAsync(DateOnly from, DateOnly to)
|
|
=> await TryInvokeAsync<List<ModelUsageRowDto>>("GetModelUsage", from, to) ?? [];
|
|
|
|
public async Task<IReadOnlyList<TaskUsageRowDto>> GetTaskUsageAsync(DateOnly from, DateOnly to)
|
|
=> await TryInvokeAsync<List<TaskUsageRowDto>>("GetTaskUsage", from, to) ?? [];
|
|
|
|
public Task<TokenTrackerStatusDto?> GetTokenTrackerStatusAsync()
|
|
=> TryInvokeAsync<TokenTrackerStatusDto>("GetTokenTrackerStatus");
|
|
|
|
public Task<TokenTrackerStatusDto?> RefreshTokenTrackerAsync()
|
|
=> TryInvokeAsync<TokenTrackerStatusDto>("RefreshTokenTracker");
|
|
|
|
public Task<TokenTrackerStatusDto?> InstallTokenTrackerAsync()
|
|
=> TryInvokeAsync<TokenTrackerStatusDto>("InstallTokenTracker");
|
|
|
|
public Task<TokenTrackerDashboardDto?> OpenTokenTrackerDashboardAsync()
|
|
=> TryInvokeAsync<TokenTrackerDashboardDto>("OpenTokenTrackerDashboard");
|
|
|
|
public Task<TicketSettingsDto?> GetTicketSettingsAsync()
|
|
=> TryInvokeAsync<TicketSettingsDto>("GetTicketSettings");
|
|
|
|
public Task SetTicketFallbackListAsync(string? listId)
|
|
=> InvokeTimedAsync("SetTicketFallbackList", () => _hub.InvokeAsync("SetTicketFallbackList", listId));
|
|
|
|
public Task SetTicketApiBaseUrlAsync(string? baseUrl)
|
|
=> InvokeTimedAsync("SetTicketApiBaseUrl", () => _hub.InvokeAsync("SetTicketApiBaseUrl", baseUrl));
|
|
|
|
public Task SetTicketTokenAsync(string token)
|
|
=> InvokeTimedAsync("SetTicketToken", () => _hub.InvokeAsync("SetTicketToken", token));
|
|
|
|
public Task ClearTicketTokenAsync()
|
|
=> InvokeTimedAsync("ClearTicketToken", () => _hub.InvokeAsync("ClearTicketToken"));
|
|
|
|
public Task<TicketConnectionDto?> TestTicketConnectionAsync()
|
|
=> TryInvokeAsync<TicketConnectionDto>("TestTicketConnection");
|
|
|
|
public Task<AzureDevOpsSettingsDto?> GetAzureDevOpsSettingsAsync()
|
|
=> TryInvokeAsync<AzureDevOpsSettingsDto>("GetAzureDevOpsSettings");
|
|
|
|
public Task SetAzureDevOpsTokenAsync(string token)
|
|
=> InvokeTimedAsync("SetAzureDevOpsToken", () => _hub.InvokeAsync("SetAzureDevOpsToken", token));
|
|
|
|
public Task ClearAzureDevOpsTokenAsync()
|
|
=> InvokeTimedAsync("ClearAzureDevOpsToken", () => _hub.InvokeAsync("ClearAzureDevOpsToken"));
|
|
|
|
public Task<AzureDevOpsConnectionDto?> TestAzureDevOpsConnectionAsync()
|
|
=> TryInvokeAsync<AzureDevOpsConnectionDto>("TestAzureDevOpsConnection");
|
|
|
|
public async Task<List<TicketProjectDto>> GetTicketProjectsAsync()
|
|
=> await TryInvokeAsync<List<TicketProjectDto>>("GetTicketProjects") ?? [];
|
|
|
|
public Task<TicketImportResultDto> ImportTicketsAsync(string listId)
|
|
=> InvokeTimedAsync<TicketImportResultDto>("ImportTickets", () => _hub.InvokeAsync<TicketImportResultDto>("ImportTickets", listId));
|
|
|
|
// 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<DiscardPlanningOutcome> 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<int> IWorkerClient.GetPendingDraftCountAsync(string taskId, CancellationToken ct)
|
|
=> await GetPendingDraftCountAsync(taskId, ct);
|
|
}
|
|
|