feat(worker): Ticket-Status-Rueckmeldung ueber TaskStateService

This commit is contained in:
mika kuns
2026-08-27 12:53:38 +02:00
parent 1065278021
commit 5aafeb6c3d
7 changed files with 418 additions and 26 deletions
+39 -24
View File
@@ -5,6 +5,7 @@ using ClaudeDo.Worker.Git;
using ClaudeDo.Worker.Hub;
using ClaudeDo.Worker.Planning;
using ClaudeDo.Worker.Queue;
using ClaudeDo.Worker.Tickets;
using Microsoft.EntityFrameworkCore;
using TaskStatus = ClaudeDo.Data.Models.TaskStatus;
@@ -20,6 +21,7 @@ public sealed class TaskStateService : ITaskStateService
private readonly Func<IActiveMergeState> _mergeState;
private readonly BaseDirtyChecker _baseDirtyChecker;
private readonly ILogger<TaskStateService> _logger;
private readonly TicketStatusSync _ticketSync;
public TaskStateService(
IDbContextFactory<ClaudeDoDbContext> dbFactory,
@@ -29,7 +31,8 @@ public sealed class TaskStateService : ITaskStateService
RunCancellationRegistry runCancels,
Func<IActiveMergeState> mergeState,
BaseDirtyChecker baseDirtyChecker,
ILogger<TaskStateService> logger)
ILogger<TaskStateService> logger,
TicketStatusSync ticketSync)
{
_dbFactory = dbFactory;
_broadcaster = broadcaster;
@@ -39,6 +42,7 @@ public sealed class TaskStateService : ITaskStateService
_mergeState = mergeState;
_baseDirtyChecker = baseDirtyChecker;
_logger = logger;
_ticketSync = ticketSync;
}
public async Task<TransitionResult> EnqueueAsync(string taskId, CancellationToken ct)
@@ -59,7 +63,7 @@ public sealed class TaskStateService : ITaskStateService
return new TransitionResult(false, "Task not found or already running.");
_waker.Wake();
await _broadcaster.TaskUpdated(taskId);
await NotifyAsync(taskId);
// Heads-up only: a worktree forks from the commit tip (WorktreeManager.ResolveBaseCommitAsync),
// not the working tree, so uncommitted changes sitting in the list's repo right now are
@@ -92,7 +96,7 @@ public sealed class TaskStateService : ITaskStateService
if (affected == 0)
return new TransitionResult(false, "Task already running or not found.");
await _broadcaster.TaskUpdated(taskId);
await NotifyAsync(taskId);
return new TransitionResult(true, null);
}
@@ -112,7 +116,7 @@ public sealed class TaskStateService : ITaskStateService
}
await OnChildTerminalAsync(taskId, TaskStatus.Done);
await _broadcaster.TaskUpdated(taskId);
await NotifyAsync(taskId);
return new TransitionResult(true, null);
}
@@ -129,7 +133,7 @@ public sealed class TaskStateService : ITaskStateService
if (affected == 0)
return new TransitionResult(false, "Task not running; cannot submit for review.");
await _broadcaster.TaskUpdated(taskId);
await NotifyAsync(taskId);
return new TransitionResult(true, null);
}
@@ -149,7 +153,7 @@ public sealed class TaskStateService : ITaskStateService
if (affected == 0)
return new TransitionResult(false, "Task is not Idle or Failed; cannot submit for review.");
await _broadcaster.TaskUpdated(taskId);
await NotifyAsync(taskId);
return new TransitionResult(true, null);
}
@@ -166,7 +170,7 @@ public sealed class TaskStateService : ITaskStateService
if (affected == 0)
return new TransitionResult(false, "Task not running; cannot submit for children.");
await _broadcaster.TaskUpdated(taskId);
await NotifyAsync(taskId);
return new TransitionResult(true, null);
}
@@ -186,7 +190,7 @@ public sealed class TaskStateService : ITaskStateService
}
await OnChildTerminalAsync(taskId, TaskStatus.Done);
await _broadcaster.TaskUpdated(taskId);
await NotifyAsync(taskId);
return new TransitionResult(true, null);
}
@@ -208,7 +212,7 @@ public sealed class TaskStateService : ITaskStateService
return new TransitionResult(false, "Task is not waiting for review; cannot reject.");
_waker.Wake();
await _broadcaster.TaskUpdated(taskId);
await NotifyAsync(taskId);
return new TransitionResult(true, null);
}
@@ -224,7 +228,7 @@ public sealed class TaskStateService : ITaskStateService
if (affected == 0)
return new TransitionResult(false, "Task is not waiting for review; cannot park.");
await _broadcaster.TaskUpdated(taskId);
await NotifyAsync(taskId);
return new TransitionResult(true, null);
}
@@ -265,7 +269,7 @@ public sealed class TaskStateService : ITaskStateService
}
await OnChildTerminalAsync(taskId, TaskStatus.Failed);
await _broadcaster.TaskUpdated(taskId);
await NotifyAsync(taskId);
return new TransitionResult(true, null);
}
@@ -329,9 +333,9 @@ public sealed class TaskStateService : ITaskStateService
_runCancels.TryCancel(childId);
await OnChildTerminalAsync(taskId, TaskStatus.Cancelled);
await _broadcaster.TaskUpdated(taskId);
await NotifyAsync(taskId);
foreach (var childId in cancelledChildIds)
await _broadcaster.TaskUpdated(childId);
await NotifyAsync(childId);
return new TransitionResult(true, null);
}
@@ -349,7 +353,7 @@ public sealed class TaskStateService : ITaskStateService
if (affected == 0)
return new TransitionResult(false, "Task is running; cannot reset.");
await _broadcaster.TaskUpdated(taskId);
await NotifyAsync(taskId);
return new TransitionResult(true, null);
}
@@ -375,7 +379,7 @@ public sealed class TaskStateService : ITaskStateService
return new TransitionResult(false, "Task not found.");
if (status == TaskStatus.Queued) _waker.Wake();
await _broadcaster.TaskUpdated(taskId);
await NotifyAsync(taskId);
return new TransitionResult(true, null);
}
@@ -396,7 +400,7 @@ public sealed class TaskStateService : ITaskStateService
if (affected == 0)
return new TransitionResult(false, "Task is running or no longer exists; cannot mark done.");
await _broadcaster.TaskUpdated(taskId);
await NotifyAsync(taskId);
return new TransitionResult(true, null);
}
@@ -410,7 +414,7 @@ public sealed class TaskStateService : ITaskStateService
if (affected == 0)
return new TransitionResult(false, "Task is not Done; cannot unmark.");
await _broadcaster.TaskUpdated(taskId);
await NotifyAsync(taskId);
return new TransitionResult(true, null);
}
@@ -426,7 +430,7 @@ public sealed class TaskStateService : ITaskStateService
if (affected == 0)
return new TransitionResult(false, "Task is not queued; cannot remove from queue.");
await _broadcaster.TaskUpdated(taskId);
await NotifyAsync(taskId);
return new TransitionResult(true, null);
}
@@ -443,7 +447,7 @@ public sealed class TaskStateService : ITaskStateService
if (affected == 0)
return new TransitionResult(false, "Task not in plannable state.");
await _broadcaster.TaskUpdated(parentId);
await NotifyAsync(parentId);
return new TransitionResult(true, null);
}
@@ -464,7 +468,7 @@ public sealed class TaskStateService : ITaskStateService
if (affected == 0)
return new TransitionResult(false, "No active planning session.");
await _broadcaster.TaskUpdated(parentId);
await NotifyAsync(parentId);
return new TransitionResult(true, null);
}
@@ -478,7 +482,7 @@ public sealed class TaskStateService : ITaskStateService
if (affected == 0)
return new TransitionResult(false, "Task not found.");
await _broadcaster.TaskUpdated(taskId);
await NotifyAsync(taskId);
return new TransitionResult(true, null);
}
@@ -493,7 +497,7 @@ public sealed class TaskStateService : ITaskStateService
return new TransitionResult(false, "Task not found.");
_waker.Wake();
await _broadcaster.TaskUpdated(taskId);
await NotifyAsync(taskId);
return new TransitionResult(true, null);
}
@@ -537,7 +541,7 @@ public sealed class TaskStateService : ITaskStateService
// Clearing a dependency may free up a Queued task the picker was skipping.
if (dependsOnTaskId is null) _waker.Wake();
await _broadcaster.TaskUpdated(taskId);
await NotifyAsync(taskId);
return new TransitionResult(true, null);
}
@@ -672,6 +676,17 @@ public sealed class TaskStateService : ITaskStateService
.ExecuteUpdateAsync(s => s
.SetProperty(t => t.Status, TaskStatus.WaitingForReview)
.SetProperty(t => t.Result, newResult), CancellationToken.None);
await _broadcaster.TaskUpdated(parentId);
await NotifyAsync(parentId);
}
/// <summary>
/// Der einzige Broadcast-Pfad dieser Klasse. Zusätzlich zum UI-Update meldet er den neuen
/// Status ans verknüpfte Ticket. Nie direkt _broadcaster.TaskUpdated aufrufen — sonst
/// fällt genau dieser Übergang aus dem Ticket-Sync heraus.
/// </summary>
private async Task NotifyAsync(string taskId)
{
await _broadcaster.TaskUpdated(taskId);
await _ticketSync.SyncAsync(taskId, CancellationToken.None);
}
}