Merge branch 'claudedo/9f963a215bb34ca4a28050d4f39178d3'

This commit is contained in:
mika kuns
2026-08-10 15:19:57 +02:00
23 changed files with 481 additions and 32 deletions
@@ -75,7 +75,10 @@ public interface IWorkerClient : INotifyPropertyChanged
Task<List<string>> InstallSessionSkillAsync(string url);
Task UpdateSessionSkillAsync(string sourceUrl);
Task RemoveSessionSkillAsync(string sourceUrl);
Task SetTaskStatusAsync(string taskId, TaskStatus status);
/// <summary>Returns the base-dirty heads-up when this transition just queued the task against
/// a list whose working dir has uncommitted changes (null otherwise) — a new worktree forks
/// from the last commit, not the working tree, so those changes won't be included.</summary>
Task<BaseDirtyWarningDto?> SetTaskStatusAsync(string taskId, TaskStatus status);
Task<MergeResultDto?> ApproveReviewAsync(string taskId, string targetBranch);
Task<MergePreviewDto?> PreviewMergeAsync(string taskId, string targetBranch);
Task<MergeResultDto> MergeTaskAsync(string taskId, string targetBranch, bool removeWorktree, string commitMessage);
+5 -2
View File
@@ -483,9 +483,10 @@ public partial class WorkerClient : ObservableObject, IAsyncDisposable, IWorkerC
public Task RemoveSessionSkillAsync(string sourceUrl)
=> _hub.InvokeAsync("RemoveSessionSkill", sourceUrl);
public async Task SetTaskStatusAsync(string taskId, ClaudeDo.Data.Models.TaskStatus status)
public async Task<BaseDirtyWarningDto?> SetTaskStatusAsync(string taskId, ClaudeDo.Data.Models.TaskStatus status)
{
await _hub.InvokeAsync("SetTaskStatus", taskId, status.ToString());
var result = await _hub.InvokeAsync<SetTaskStatusResultDto>("SetTaskStatus", taskId, status.ToString());
return result?.BaseDirty;
}
public async Task<MergeResultDto?> ApproveReviewAsync(string taskId, string targetBranch)
@@ -690,6 +691,8 @@ public sealed record SessionSkillDto(
public sealed record WorktreeCleanupDto(int Removed);
public sealed record WorktreeResetDto(int Removed, int TasksAffected, bool Blocked, int RunningTasks);
public record MergeResultDto(string Status, IReadOnlyList<string> ConflictFiles, string? ErrorMessage);
public record BaseDirtyWarningDto(int ModifiedCount, int UntrackedCount);
public record SetTaskStatusResultDto(BaseDirtyWarningDto? BaseDirty);
public record MergePreviewDto(string Status, IReadOnlyList<string> ConflictFiles, int ChangedFileCount);
public record MergeTargetsDto(string DefaultBranch, IReadOnlyList<string> LocalBranches);
public record MergeConflictDocumentsDto(string TaskId, IReadOnlyList<ConflictDocumentDto> Files);
@@ -1097,12 +1097,20 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase, IDisposable
if (Task == null) return;
try
{
await _worker.SetTaskStatusAsync(Task.Id, ClaudeDo.Data.Models.TaskStatus.Queued);
var baseDirty = await _worker.SetTaskStatusAsync(Task.Id, ClaudeDo.Data.Models.TaskStatus.Queued);
AgentState = "queued";
ReportBaseDirty(baseDirty);
}
catch { /* offline */ }
}
private void ReportBaseDirty(BaseDirtyWarningDto? warning)
{
if (warning is null) return;
ErrorReported?.Invoke(Loc.T(
"vm.queue.baseDirtyWarning", warning.ModifiedCount, warning.UntrackedCount));
}
private bool CanEnqueue() =>
Task != null && _worker.IsConnected && IsIdle
&& (!Task.IsChild || Task.ParentFinalized);
@@ -1168,8 +1176,9 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase, IDisposable
try
{
await _worker.SetTaskStatusAsync(Task.Id, ClaudeDo.Data.Models.TaskStatus.Queued);
var baseDirty = await _worker.SetTaskStatusAsync(Task.Id, ClaudeDo.Data.Models.TaskStatus.Queued);
AgentState = "queued";
ReportBaseDirty(baseDirty);
}
catch { /* offline */ }
}
@@ -887,10 +887,21 @@ public sealed partial class TasksIslandViewModel : ViewModelBase, IDisposable
public async Task SetStatusOnRowAsync(TaskRowViewModel row, TaskStatus status)
{
if (_worker is null) return;
try { await _worker.SetTaskStatusAsync(row.Id, status); }
try
{
var baseDirty = await _worker.SetTaskStatusAsync(row.Id, status);
ReportBaseDirty(baseDirty);
}
catch { /* offline; broadcast won't fire */ }
}
private void ReportBaseDirty(BaseDirtyWarningDto? warning)
{
if (warning is null) return;
ErrorReported?.Invoke(Loc.T(
"vm.queue.baseDirtyWarning", warning.ModifiedCount, warning.UntrackedCount));
}
[RelayCommand]
private async Task SendToQueueAsync(TaskRowViewModel? row)
{
@@ -917,7 +928,11 @@ public sealed partial class TasksIslandViewModel : ViewModelBase, IDisposable
// Goes through the worker hub (TaskStateService.EnqueueAsync) rather than a raw EF write
// so the manual/draft-child guards apply here too; the row refreshes from the resulting
// TaskUpdated broadcast.
try { await _worker.SetTaskStatusAsync(row.Id, TaskStatus.Queued); }
try
{
var baseDirty = await _worker.SetTaskStatusAsync(row.Id, TaskStatus.Queued);
ReportBaseDirty(baseDirty);
}
catch (Exception ex) { ErrorReported?.Invoke(Loc.T("vm.tasksIsland.sendToQueueFailed", ex.Message)); }
}
@@ -131,7 +131,13 @@ public sealed partial class MissionControlViewModel : ViewModelBase, IDisposable
{
if (string.IsNullOrEmpty(taskId)) return;
if (ConPtySessions.Any(s => s.TaskId == taskId)) return;
try { await _worker.SetTaskStatusAsync(taskId, ClaudeDo.Data.Models.TaskStatus.Queued); }
try
{
var baseDirty = await _worker.SetTaskStatusAsync(taskId, ClaudeDo.Data.Models.TaskStatus.Queued);
if (baseDirty is not null)
ErrorReported?.Invoke(Loc.T(
"vm.queue.baseDirtyWarning", baseDirty.ModifiedCount, baseDirty.UntrackedCount));
}
catch { /* best-effort enqueue */ }
await RefreshQueueAsync();
}