feat(mission-control): give the list handler its own review task
"Let Claude handle it" now creates one ClaudeDo task per run to host the ConPTY session (Idle/IsManual, never queued) instead of an untracked ad-hoc tile, so the run has a real title, diff, and review outcome. Since the handler merges its own changes straight into the list's working dir, the task never gets a WorktreeEntity; its review range lives as new HandlerBaseCommit/HandlerHeadCommit columns on TaskEntity instead, reusing the existing commit-range diff machinery and keeping it out of the worktrees overview entirely.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using ClaudeDo.Data;
|
||||
using ClaudeDo.Data.Git;
|
||||
using ClaudeDo.Data.Models;
|
||||
using ClaudeDo.Data.Repositories;
|
||||
using ClaudeDo.Worker.Config;
|
||||
@@ -29,6 +30,7 @@ public sealed class InteractiveLaunchSpecService : IInteractiveLaunchSpecService
|
||||
private readonly ISessionSkillSeeder _skillSeeder;
|
||||
private readonly ISessionSkillRegistry _skillRegistry;
|
||||
private readonly WorktreeManager _wtManager;
|
||||
private readonly GitService _git;
|
||||
private readonly string _claudePath;
|
||||
|
||||
public InteractiveLaunchSpecService(
|
||||
@@ -36,12 +38,14 @@ public sealed class InteractiveLaunchSpecService : IInteractiveLaunchSpecService
|
||||
ISessionSkillSeeder skillSeeder,
|
||||
ISessionSkillRegistry skillRegistry,
|
||||
WorktreeManager wtManager,
|
||||
GitService git,
|
||||
WorkerConfig cfg)
|
||||
{
|
||||
_dbFactory = dbFactory;
|
||||
_skillSeeder = skillSeeder;
|
||||
_skillRegistry = skillRegistry;
|
||||
_wtManager = wtManager;
|
||||
_git = git;
|
||||
_claudePath = cfg.ClaudeBin;
|
||||
}
|
||||
|
||||
@@ -248,6 +252,57 @@ public sealed class InteractiveLaunchSpecService : IInteractiveLaunchSpecService
|
||||
return new LaunchSpec(repoDir, resolvedClaude, args, env);
|
||||
}
|
||||
|
||||
// Creates the ClaudeDo task that hosts a list-handler run (Mission Control's "Let Claude
|
||||
// handle it") and stamps the list repo's current HEAD as the review range's base commit.
|
||||
// The handler never gets its own worktree -- it commits straight to the list's working
|
||||
// dir -- so this HandlerBaseCommit/HandlerHeadCommit pair (see TaskEntity) is what lets the
|
||||
// normal diff/get_task_diff paths show what the run changed once it submits for review.
|
||||
// IsManual=true so the queue picker, daily prep, and the "send to queue"/"refine" UI
|
||||
// affordances all skip it, matching the "reminder only a human/ConPTY session can act on"
|
||||
// semantics IsManual already carries elsewhere; the ConPTY session itself is still allowed.
|
||||
public async Task<string> CreateMergeHelperTaskAsync(
|
||||
IReadOnlyList<string> taskIds, string listId, string title, string descriptionHeader, CancellationToken ct)
|
||||
{
|
||||
if (taskIds.Count == 0)
|
||||
throw new InvalidOperationException("No tasks selected for the list handler.");
|
||||
|
||||
await using var ctx = await _dbFactory.CreateDbContextAsync(ct);
|
||||
var listRepo = new ListRepository(ctx);
|
||||
var taskRepo = new TaskRepository(ctx);
|
||||
|
||||
var list = await listRepo.GetByIdAsync(listId, ct)
|
||||
?? throw new KeyNotFoundException($"List not found: {listId}");
|
||||
|
||||
var repoDir = list.WorkingDir;
|
||||
if (string.IsNullOrEmpty(repoDir) || !Directory.Exists(repoDir))
|
||||
throw new InvalidOperationException($"list '{list.Name}' has no existing working directory");
|
||||
|
||||
var descriptionLines = new List<string>();
|
||||
foreach (var id in taskIds)
|
||||
{
|
||||
var task = await taskRepo.GetByIdAsync(id, ct);
|
||||
if (task is not null) descriptionLines.Add($"- [{task.Status}] {task.Title} (id: {task.Id})");
|
||||
}
|
||||
|
||||
var baseCommit = await _git.RevParseHeadAsync(repoDir, ct);
|
||||
|
||||
var handlerTask = new TaskEntity
|
||||
{
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
ListId = listId,
|
||||
Title = title,
|
||||
Description = descriptionLines.Count > 0
|
||||
? $"{descriptionHeader}\n{string.Join("\n", descriptionLines)}"
|
||||
: descriptionHeader,
|
||||
IsManual = true,
|
||||
HandlerBaseCommit = baseCommit,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
};
|
||||
await taskRepo.AddAsync(handlerTask, ct);
|
||||
|
||||
return handlerTask.Id;
|
||||
}
|
||||
|
||||
// The reasoning effort configured for a model in Settings → General. Falls back to the shipped
|
||||
// preset for that model, so a missing/malformed settings row can never block a launch.
|
||||
private static string EffortFor(AppSettingsEntity settings, string? model)
|
||||
|
||||
@@ -34,4 +34,12 @@ public interface IInteractiveLaunchSpecService
|
||||
/// Throws KeyNotFoundException if the list doesn't exist; InvalidOperationException if
|
||||
/// taskIds is empty or the list has no existing working directory.</summary>
|
||||
Task<LaunchSpec> BuildForMergeHelperAsync(IReadOnlyList<string> taskIds, string listId, CancellationToken ct);
|
||||
|
||||
/// <summary>Creates the ClaudeDo task that hosts a list-handler run (Mission Control's
|
||||
/// "Let Claude handle it") and stamps the list repo's current HEAD as the review range's
|
||||
/// base commit (see TaskEntity.HandlerBaseCommit). Returns the new task's id. Throws
|
||||
/// KeyNotFoundException if the list doesn't exist; InvalidOperationException if taskIds
|
||||
/// is empty or the list has no existing working directory.</summary>
|
||||
Task<string> CreateMergeHelperTaskAsync(
|
||||
IReadOnlyList<string> taskIds, string listId, string title, string descriptionHeader, CancellationToken ct);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user