refactor: drop the remaining single-implementation interfaces
IBaseDirtyChecker, IInteractiveLaunchSpecService and the LaunchSpec wrapper had one implementation and one caller each; ProcessRunnerAdapter existed only to give a static class an interface, and InstallArtifactLocator used inheritance for two constructor arguments. The external MCP container now shares its singletons through a Share<T> helper instead of 17 near-identical registrations.
This commit is contained in:
@@ -29,7 +29,7 @@ namespace ClaudeDo.Worker.Runner;
|
||||
// hand. The always-on `mcp__claudedo__*` tools remain available via the globally-registered
|
||||
// MCP server (installer's RegisterMcpStep), exactly as they already are for a plain
|
||||
// `--resume` pickup in a Windows Terminal window.
|
||||
public sealed class InteractiveLaunchSpecService : IInteractiveLaunchSpecService
|
||||
public sealed class InteractiveLaunchSpecService
|
||||
{
|
||||
// Claude Code caps HTTP MCP tool calls at 60s unless raised; every ConPTY spec built by this
|
||||
// service lifts it well past wait_for_task_change's 900s server-side cap. Keep in sync with
|
||||
@@ -59,6 +59,16 @@ public sealed class InteractiveLaunchSpecService : IInteractiveLaunchSpecService
|
||||
_claudePath = cfg.ClaudeBin;
|
||||
}
|
||||
|
||||
/// <summary>Builds a LaunchSpec for opening an interactive session in a task's worktree.
|
||||
/// Throws KeyNotFoundException if the task doesn't exist, InvalidOperationException
|
||||
/// if it's Running/Queued. If the task has no usable worktree yet, one is created on
|
||||
/// demand (same mechanism as an autonomous run) provided the task's list has a working
|
||||
/// directory pointing at a git repo -- otherwise throws InvalidOperationException. Resumes
|
||||
/// (--resume) this task's own last interactive session (TaskEntity.InteractiveSessionId) if
|
||||
/// it has one, else the latest autonomous run's session; a task that has never run either
|
||||
/// way, or whose worktree was just created fresh, gets a fresh-start spec instead -- pre-
|
||||
/// assigned a new session id via --session-id and persisted to InteractiveSessionId before
|
||||
/// launch, so a closed/aborted session can be resumed next time.</summary>
|
||||
public async Task<LaunchSpec> BuildForTaskAsync(string taskId, CancellationToken ct)
|
||||
{
|
||||
await using var ctx = await _dbFactory.CreateDbContextAsync(ct);
|
||||
@@ -172,6 +182,9 @@ public sealed class InteractiveLaunchSpecService : IInteractiveLaunchSpecService
|
||||
return new LaunchSpec(sessionDir, resolvedClaude, args, env);
|
||||
}
|
||||
|
||||
/// <summary>Maps an already-prepared planning START context (worktree + prompt files + token,
|
||||
/// produced by PlanningSessionManager.StartAsync) into a LaunchSpec for an embedded ConPTY
|
||||
/// planning session — same planning CLI args as the wt launcher, planning env carried in Env.</summary>
|
||||
public LaunchSpec BuildPlanningStart(PlanningSessionStartContext ctx)
|
||||
{
|
||||
var resolvedClaude = WindowsTerminalLauncher.Resolve(_claudePath)
|
||||
@@ -194,6 +207,8 @@ public sealed class InteractiveLaunchSpecService : IInteractiveLaunchSpecService
|
||||
env);
|
||||
}
|
||||
|
||||
/// <summary>Maps a planning RESUME context (from PlanningSessionManager.ResumeAsync) into a
|
||||
/// LaunchSpec for an embedded ConPTY planning session (--permission-mode plan --resume).</summary>
|
||||
public LaunchSpec BuildPlanningResume(PlanningSessionResumeContext ctx)
|
||||
{
|
||||
var resolvedClaude = WindowsTerminalLauncher.Resolve(_claudePath)
|
||||
@@ -212,6 +227,9 @@ public sealed class InteractiveLaunchSpecService : IInteractiveLaunchSpecService
|
||||
env);
|
||||
}
|
||||
|
||||
/// <summary>Builds a LaunchSpec for an ad-hoc interactive session in an arbitrary directory --
|
||||
/// no task, no worktree, no session-skills seeding. Throws InvalidOperationException if the
|
||||
/// directory doesn't exist.</summary>
|
||||
public async Task<LaunchSpec> BuildForDirectoryAsync(string directory, CancellationToken ct)
|
||||
{
|
||||
if (!Directory.Exists(directory))
|
||||
@@ -241,6 +259,14 @@ public sealed class InteractiveLaunchSpecService : IInteractiveLaunchSpecService
|
||||
private const string MergeHelperAllowedTools =
|
||||
"mcp__claudedo__*,Read,Grep,Glob,Edit,Bash,WebFetch,WebSearch,Skill,Task";
|
||||
|
||||
/// <summary>Builds a LaunchSpec for an embedded ConPTY "merge helper" session that drives the
|
||||
/// given tasks to a merged/Done state via the mcp__claudedo__* tools. Writes a per-session
|
||||
/// system prompt + task brief under ~/.todo-app/merge-helper-sessions/<guid> and exposes
|
||||
/// that dir plus the list's repo dir via --add-dir. cwd is the list's working directory.
|
||||
/// handlerTaskId (the id returned by CreateMergeHelperTaskAsync) is rendered into the brief so
|
||||
/// the session can call handoff_list_handler/submit_task_for_review on its own handler task.
|
||||
/// Throws KeyNotFoundException if the list doesn't exist; InvalidOperationException if
|
||||
/// taskIds is empty or the list has no existing working directory.</summary>
|
||||
public async Task<LaunchSpec> BuildForMergeHelperAsync(IReadOnlyList<string> taskIds, string listId, string handlerTaskId, CancellationToken ct)
|
||||
{
|
||||
if (taskIds.Count == 0)
|
||||
@@ -322,6 +348,15 @@ public sealed class InteractiveLaunchSpecService : IInteractiveLaunchSpecService
|
||||
// instructions for the role it is actually about to run, never the Triage dedupe/enhance ones.
|
||||
// Writes a fresh handoff kickoff file in a NEW session dir -- the old ConPTY tile keeps running
|
||||
// against its own session-dir files untouched.
|
||||
/// <summary>Builds a LaunchSpec for the fresh ConPTY session a merge-helper run hands off to for
|
||||
/// the given phase -- reuses the SAME handler task id (no new task, HandlerBaseCommit untouched),
|
||||
/// writing only a fresh handoff kickoff naming the surviving tasks. nextPhase selects both the
|
||||
/// system prompt and the model: "wait"/"wait_final" -> MergeHelperWait + HandlerWaitAlias,
|
||||
/// "merge"/"merge_final" -> MergeHelperMerge + HandlerMergeAlias; the "_final" variants render an
|
||||
/// extra line in the handoff kickoff marking the final round and forbidding further reruns. Throws
|
||||
/// ArgumentException for an unrecognized nextPhase; KeyNotFoundException if the task/list doesn't
|
||||
/// exist; InvalidOperationException if survivingTaskIds is empty or the list has no working
|
||||
/// directory.</summary>
|
||||
public async Task<LaunchSpec> BuildForMergeHelperHandoffAsync(
|
||||
string taskId, IReadOnlyList<string> survivingTaskIds, string nextPhase, CancellationToken ct)
|
||||
{
|
||||
@@ -450,6 +485,11 @@ public sealed class InteractiveLaunchSpecService : IInteractiveLaunchSpecService
|
||||
// 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.
|
||||
/// <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>
|
||||
public async Task<string> CreateMergeHelperTaskAsync(
|
||||
IReadOnlyList<string> taskIds, string listId, string title, string descriptionHeader, CancellationToken ct)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user