diff --git a/src/ClaudeDo.Localization/locales/de.json b/src/ClaudeDo.Localization/locales/de.json
index 0f75cedc..ba68ab2f 100644
--- a/src/ClaudeDo.Localization/locales/de.json
+++ b/src/ClaudeDo.Localization/locales/de.json
@@ -497,6 +497,8 @@
"windowTitle": "Claude machen lassen",
"scopeList": "Liste: {0}",
"scopeReview": "Alle Listen — Tasks im Review",
+ "modelLabel": "Modell",
+ "modelTip": "Modell für die Triage-Session (Dedupe & Verfeinern). Spätere Merge-Phasen laufen immer auf Opus.",
"confirm": "Claude machen lassen",
"cancel": "Abbrechen",
"selectAll": "Alle auswählen",
diff --git a/src/ClaudeDo.Localization/locales/en.json b/src/ClaudeDo.Localization/locales/en.json
index 504a4ffc..8affc2b4 100644
--- a/src/ClaudeDo.Localization/locales/en.json
+++ b/src/ClaudeDo.Localization/locales/en.json
@@ -497,6 +497,8 @@
"windowTitle": "Let Claude handle it",
"scopeList": "List: {0}",
"scopeReview": "All lists — tasks waiting for review",
+ "modelLabel": "Model",
+ "modelTip": "Model for the triage session (dedupe & refine). Later merge phases always run on opus.",
"confirm": "Let Claude handle it",
"cancel": "Cancel",
"selectAll": "Select all",
diff --git a/src/ClaudeDo.Ui/Services/Interfaces/IWorkerClient.cs b/src/ClaudeDo.Ui/Services/Interfaces/IWorkerClient.cs
index ea823223..fdb3058e 100644
--- a/src/ClaudeDo.Ui/Services/Interfaces/IWorkerClient.cs
+++ b/src/ClaudeDo.Ui/Services/Interfaces/IWorkerClient.cs
@@ -143,8 +143,9 @@ public interface IWorkerClient : INotifyPropertyChanged
/// Launch spec for an embedded ConPTY "merge helper" session that drives the given
/// tasks to a merged/Done state. listId scopes the session (and cwd) to that list.
/// handlerTaskId (from CreateMergeHelperTaskAsync) is rendered into the brief so the session
- /// can call handoff_list_handler on its own handler task.
- Task GetMergeHelperLaunchSpecAsync(IReadOnlyList taskIds, string listId, string handlerTaskId, CancellationToken ct = default);
+ /// can call handoff_list_handler on its own handler task. model overrides the triage
+ /// session's model (null = default).
+ Task GetMergeHelperLaunchSpecAsync(IReadOnlyList taskIds, string listId, string handlerTaskId, string? model = null, CancellationToken ct = default);
/// Creates the ClaudeDo task that owns a list-handler run (one per run, Idle/IsManual,
/// never queued) so the ConPTY tile can be task-based instead of ad-hoc. Returns the new task id.
Task CreateMergeHelperTaskAsync(
diff --git a/src/ClaudeDo.Ui/Services/WorkerClient.cs b/src/ClaudeDo.Ui/Services/WorkerClient.cs
index b0230d65..a28a452e 100644
--- a/src/ClaudeDo.Ui/Services/WorkerClient.cs
+++ b/src/ClaudeDo.Ui/Services/WorkerClient.cs
@@ -627,8 +627,8 @@ public partial class WorkerClient : ObservableObject, IAsyncDisposable, IWorkerC
public async Task GetAdHocLaunchSpecAsync(string directory, CancellationToken ct = default)
=> await InvokeTimedAsync("GetAdHocLaunchSpec", () => _hub.InvokeAsync("GetAdHocLaunchSpec", directory, ct));
- public async Task GetMergeHelperLaunchSpecAsync(IReadOnlyList taskIds, string listId, string handlerTaskId, CancellationToken ct = default)
- => await InvokeTimedAsync("GetMergeHelperLaunchSpec", () => _hub.InvokeAsync("GetMergeHelperLaunchSpec", taskIds, listId, handlerTaskId, ct));
+ public async Task GetMergeHelperLaunchSpecAsync(IReadOnlyList taskIds, string listId, string handlerTaskId, string? model = null, CancellationToken ct = default)
+ => await InvokeTimedAsync("GetMergeHelperLaunchSpec", () => _hub.InvokeAsync("GetMergeHelperLaunchSpec", taskIds, listId, handlerTaskId, model, ct));
public async Task CreateMergeHelperTaskAsync(
IReadOnlyList taskIds, string listId, string title, string descriptionHeader, CancellationToken ct = default)
diff --git a/src/ClaudeDo.Ui/ViewModels/Islands/ListsIslandViewModel.cs b/src/ClaudeDo.Ui/ViewModels/Islands/ListsIslandViewModel.cs
index cdb6c261..7953b72e 100644
--- a/src/ClaudeDo.Ui/ViewModels/Islands/ListsIslandViewModel.cs
+++ b/src/ClaudeDo.Ui/ViewModels/Islands/ListsIslandViewModel.cs
@@ -17,8 +17,9 @@ namespace ClaudeDo.Ui.ViewModels.Islands;
public enum ListKind { Smart, Virtual, User }
-/// Confirmed handler run: the scope list and the ordered selected task ids.
-public sealed record MergeHelperRequest(string ListId, IReadOnlyList TaskIds);
+/// Confirmed handler run: the scope list, the ordered selected task ids, and the model
+/// picked for the triage session (null = default; review-mode runs skip triage entirely).
+public sealed record MergeHelperRequest(string ListId, IReadOnlyList TaskIds, string? TriageModel = null);
public sealed partial class ListsIslandViewModel : ViewModelBase, IDisposable
{
@@ -114,7 +115,7 @@ public sealed partial class ListsIslandViewModel : ViewModelBase, IDisposable
await vm.LoadAsync();
var ids = await Dialogs.ShowMergeHelperSelectionAsync(vm);
if (ids is { Count: > 0 })
- LetClaudeHandleRequested?.Invoke(new MergeHelperRequest(rawId, ids));
+ LetClaudeHandleRequested?.Invoke(new MergeHelperRequest(rawId, ids, vm.SelectedModel));
}
/// Raised once per list after the review-scoped selection dialog is confirmed;
diff --git a/src/ClaudeDo.Ui/ViewModels/IslandsShellViewModel.cs b/src/ClaudeDo.Ui/ViewModels/IslandsShellViewModel.cs
index b1ef68c2..8229ca2e 100644
--- a/src/ClaudeDo.Ui/ViewModels/IslandsShellViewModel.cs
+++ b/src/ClaudeDo.Ui/ViewModels/IslandsShellViewModel.cs
@@ -370,7 +370,7 @@ public sealed partial class IslandsShellViewModel : ViewModelBase, IDisposable
Lists.LetClaudeHandleRequested += req =>
{
OpenMissionControl();
- _ = MissionControl.OpenMergeHelperConPtySessionAsync(req.ListId, req.TaskIds);
+ _ = MissionControl.OpenMergeHelperConPtySessionAsync(req.ListId, req.TaskIds, req.TriageModel);
};
Lists.MergeReviewsRequested += req =>
{
diff --git a/src/ClaudeDo.Ui/ViewModels/MissionControlViewModel.cs b/src/ClaudeDo.Ui/ViewModels/MissionControlViewModel.cs
index 5f769455..11ec8819 100644
--- a/src/ClaudeDo.Ui/ViewModels/MissionControlViewModel.cs
+++ b/src/ClaudeDo.Ui/ViewModels/MissionControlViewModel.cs
@@ -331,10 +331,10 @@ public sealed partial class MissionControlViewModel : ViewModelBase, IDisposable
// creates one new ClaudeDo task per run to own the session (title/diff/result), deduped by
// TaskId like OpenConPtySessionAsync. The handler still merges the tasks it handles itself
// (no worktree of its own) — see TaskEntity.HandlerBaseCommit/HandlerHeadCommit.
- public System.Threading.Tasks.Task OpenMergeHelperConPtySessionAsync(string listId, IReadOnlyList taskIds)
+ public System.Threading.Tasks.Task OpenMergeHelperConPtySessionAsync(string listId, IReadOnlyList taskIds, string? triageModel = null)
=> OpenListHandlerConPtySessionAsync(listId, taskIds,
"missionControl.mergeHelperTitle", "missionControl.mergeHelperTaskTitle",
- taskId => _worker.GetMergeHelperLaunchSpecAsync(taskIds, listId, taskId));
+ taskId => _worker.GetMergeHelperLaunchSpecAsync(taskIds, listId, taskId, triageModel));
// Merge-only list-handler session over a list's WaitingForReview tasks (the review list's
// "Let Claude merge the reviews"). Same host-task shape as the full run, but the session
diff --git a/src/ClaudeDo.Ui/ViewModels/Modals/MergeHelperSelectionModalViewModel.cs b/src/ClaudeDo.Ui/ViewModels/Modals/MergeHelperSelectionModalViewModel.cs
index c8878362..e8f3bdb5 100644
--- a/src/ClaudeDo.Ui/ViewModels/Modals/MergeHelperSelectionModalViewModel.cs
+++ b/src/ClaudeDo.Ui/ViewModels/Modals/MergeHelperSelectionModalViewModel.cs
@@ -67,6 +67,15 @@ public sealed partial class MergeHelperSelectionModalViewModel : ViewModelBase
public ObservableCollection Tasks { get; } = new();
+ /// Model choices for the triage session. Only opus/fable are offered on purpose —
+ /// triage is the dedupe/refine leverage point; cheaper models aren't worth a picker slot.
+ public IReadOnlyList ModelOptions { get; } = new[] { "opus", "fable" };
+
+ [ObservableProperty] private string _selectedModel = Data.Models.ModelRegistry.HandlerTriageAlias;
+
+ /// Hidden in review mode — those runs start directly at the merge phase, no triage.
+ public bool ShowModelPicker => !_reviewMode;
+
[ObservableProperty] private string _scopeLabel = "";
public bool HasTasks => Tasks.Count > 0;
@@ -100,6 +109,7 @@ public sealed partial class MergeHelperSelectionModalViewModel : ViewModelBase
{
_reviewMode = true;
ScopeLabel = Loc.T("modals.mergeHelper.scopeReview");
+ OnPropertyChanged(nameof(ShowModelPicker));
}
/// Groups a confirmed review-mode selection by owning list, preserving row order —
diff --git a/src/ClaudeDo.Ui/Views/Modals/MergeHelperSelectionModal.axaml b/src/ClaudeDo.Ui/Views/Modals/MergeHelperSelectionModal.axaml
index 0d3f02b7..6f4d72e6 100644
--- a/src/ClaudeDo.Ui/Views/Modals/MergeHelperSelectionModal.axaml
+++ b/src/ClaudeDo.Ui/Views/Modals/MergeHelperSelectionModal.axaml
@@ -29,11 +29,18 @@
-
+
-
diff --git a/src/ClaudeDo.Worker/Hub/WorkerHub.cs b/src/ClaudeDo.Worker/Hub/WorkerHub.cs
index 92096817..5e682e0a 100644
--- a/src/ClaudeDo.Worker/Hub/WorkerHub.cs
+++ b/src/ClaudeDo.Worker/Hub/WorkerHub.cs
@@ -770,12 +770,13 @@ public sealed class WorkerHub : Microsoft.AspNetCore.SignalR.Hub
// Builds the launch spec for an embedded ConPTY "merge helper" session that drives the given
// tasks to a merged/Done state via the mcp__claudedo__* tools. listId scopes the brief label
// and cwd to that list. handlerTaskId is the id returned by CreateMergeHelperTask -- it is
- // rendered into the brief so the session can call handoff_list_handler on itself.
- public Task GetMergeHelperLaunchSpec(string[] taskIds, string listId, string handlerTaskId) => HubGuard(() =>
+ // rendered into the brief so the session can call handoff_list_handler on itself. model
+ // overrides the triage session's model (null = default opus).
+ public Task GetMergeHelperLaunchSpec(string[] taskIds, string listId, string handlerTaskId, string? model) => HubGuard(() =>
{
if (_interactiveLaunchSpec is null)
throw new InvalidOperationException("Interactive launch spec service is not configured.");
- return _interactiveLaunchSpec.BuildForMergeHelperAsync(taskIds, listId, handlerTaskId, Context.ConnectionAborted);
+ return _interactiveLaunchSpec.BuildForMergeHelperAsync(taskIds, listId, handlerTaskId, model, Context.ConnectionAborted);
});
// Creates the ClaudeDo task that owns a list-handler run, before the ConPTY tile opens --
diff --git a/src/ClaudeDo.Worker/Runner/InteractiveLaunchSpecService.cs b/src/ClaudeDo.Worker/Runner/InteractiveLaunchSpecService.cs
index 59718e84..f41b394f 100644
--- a/src/ClaudeDo.Worker/Runner/InteractiveLaunchSpecService.cs
+++ b/src/ClaudeDo.Worker/Runner/InteractiveLaunchSpecService.cs
@@ -290,8 +290,10 @@ public sealed class InteractiveLaunchSpecService
/// 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.
- public async Task BuildForMergeHelperAsync(IReadOnlyList taskIds, string listId, string handlerTaskId, CancellationToken ct)
+ /// taskIds is empty or the list has no existing working directory.
+ /// model overrides the triage session's model (start-modal dropdown); null/unknown falls
+ /// back to . Handoff phases are unaffected.
+ public async Task BuildForMergeHelperAsync(IReadOnlyList taskIds, string listId, string handlerTaskId, string? model, CancellationToken ct)
{
if (taskIds.Count == 0)
throw new InvalidOperationException("No tasks selected for the list handler.");
@@ -344,12 +346,13 @@ public sealed class InteractiveLaunchSpecService
// longer influences a handler session at all, it would otherwise apply the LIST's effort
// preset to a completely different model.
var settings = await new AppSettingsRepository(ctx).GetAsync(ct);
+ var triageModel = ModelRegistry.TryNormalizeAlias(model) ?? ModelRegistry.HandlerTriageAlias;
var args = new List
{
- "--model", ModelRegistry.HandlerTriageAlias,
- "--effort", EffortFor(settings, ModelRegistry.HandlerTriageAlias),
- "--permission-mode", PermissionModeResolver.Resolve(ModelRegistry.HandlerTriageAlias, "auto"),
+ "--model", triageModel,
+ "--effort", EffortFor(settings, triageModel),
+ "--permission-mode", PermissionModeResolver.Resolve(triageModel, "auto"),
"--allowedTools", MergeHelperAllowedTools,
"--add-dir", sessionDir, repoDir,
"--append-system-prompt-file", systemPromptPath,
diff --git a/tests/ClaudeDo.Ui.Tests/StubWorkerClient.cs b/tests/ClaudeDo.Ui.Tests/StubWorkerClient.cs
index 7a219379..21de02b8 100644
--- a/tests/ClaudeDo.Ui.Tests/StubWorkerClient.cs
+++ b/tests/ClaudeDo.Ui.Tests/StubWorkerClient.cs
@@ -128,7 +128,7 @@ public abstract class StubWorkerClient : IWorkerClient
=> Task.FromResult(new LaunchSpec(".", "claude", Array.Empty(), new Dictionary()));
public virtual Task GetAdHocLaunchSpecAsync(string directory, CancellationToken ct = default)
=> Task.FromResult(new LaunchSpec(directory, "claude", Array.Empty(), new Dictionary()));
- public virtual Task GetMergeHelperLaunchSpecAsync(IReadOnlyList taskIds, string listId, string handlerTaskId, CancellationToken ct = default)
+ public virtual Task GetMergeHelperLaunchSpecAsync(IReadOnlyList taskIds, string listId, string handlerTaskId, string? model = null, CancellationToken ct = default)
=> Task.FromResult(new LaunchSpec(".", "claude", Array.Empty(), new Dictionary()));
public virtual Task CreateMergeHelperTaskAsync(
IReadOnlyList taskIds, string listId, string title, string descriptionHeader, CancellationToken ct = default)
diff --git a/tests/ClaudeDo.Ui.Tests/ViewModels/MissionControlViewModelTests.cs b/tests/ClaudeDo.Ui.Tests/ViewModels/MissionControlViewModelTests.cs
index d5c44bdd..a3d181d1 100644
--- a/tests/ClaudeDo.Ui.Tests/ViewModels/MissionControlViewModelTests.cs
+++ b/tests/ClaudeDo.Ui.Tests/ViewModels/MissionControlViewModelTests.cs
@@ -556,7 +556,7 @@ public class MissionControlViewModelTests : IDisposable
private sealed class ThrowingMergeHelperLaunchSpecWorker : StubWorkerClient
{
- public override Task GetMergeHelperLaunchSpecAsync(IReadOnlyList taskIds, string listId, string handlerTaskId, CancellationToken ct = default)
+ public override Task GetMergeHelperLaunchSpecAsync(IReadOnlyList taskIds, string listId, string handlerTaskId, string? model = null, CancellationToken ct = default)
=> throw new InvalidOperationException("spec failed");
}
diff --git a/tests/ClaudeDo.Worker.Tests/Runner/InteractiveLaunchSpecServiceTests.cs b/tests/ClaudeDo.Worker.Tests/Runner/InteractiveLaunchSpecServiceTests.cs
index e3c500af..1c704ca4 100644
--- a/tests/ClaudeDo.Worker.Tests/Runner/InteractiveLaunchSpecServiceTests.cs
+++ b/tests/ClaudeDo.Worker.Tests/Runner/InteractiveLaunchSpecServiceTests.cs
@@ -721,7 +721,7 @@ public sealed class InteractiveLaunchSpecServiceTests : IDisposable
{
var svc = BuildService(now: now);
var spec = await svc.BuildForMergeHelperAsync(
- new[] { taskId }, listId, Guid.NewGuid().ToString(), CancellationToken.None);
+ new[] { taskId }, listId, Guid.NewGuid().ToString(), null, CancellationToken.None);
var sessionDir = TrackSessionDir(spec);
return await File.ReadAllTextAsync(Path.Combine(sessionDir, "system-prompt.md"));
}
@@ -851,7 +851,7 @@ public sealed class InteractiveLaunchSpecServiceTests : IDisposable
var listId = await SeedListAsync(workingDir: _tempDir);
var svc = BuildService();
await Assert.ThrowsAsync(
- () => svc.BuildForMergeHelperAsync(Array.Empty(), listId, Guid.NewGuid().ToString(), CancellationToken.None));
+ () => svc.BuildForMergeHelperAsync(Array.Empty(), listId, Guid.NewGuid().ToString(), null, CancellationToken.None));
}
[Fact]
@@ -863,7 +863,7 @@ public sealed class InteractiveLaunchSpecServiceTests : IDisposable
var svc = BuildService();
var ex = await Assert.ThrowsAsync(
- () => svc.BuildForMergeHelperAsync(new[] { taskId }, listId, Guid.NewGuid().ToString(), CancellationToken.None));
+ () => svc.BuildForMergeHelperAsync(new[] { taskId }, listId, Guid.NewGuid().ToString(), null, CancellationToken.None));
Assert.Contains("working directory", ex.Message);
}
@@ -876,7 +876,7 @@ public sealed class InteractiveLaunchSpecServiceTests : IDisposable
var svc = BuildService();
await Assert.ThrowsAsync(
- () => svc.BuildForMergeHelperAsync(new[] { taskId }, "no-such-list", Guid.NewGuid().ToString(), CancellationToken.None));
+ () => svc.BuildForMergeHelperAsync(new[] { taskId }, "no-such-list", Guid.NewGuid().ToString(), null, CancellationToken.None));
}
[Fact]
@@ -892,7 +892,7 @@ public sealed class InteractiveLaunchSpecServiceTests : IDisposable
await SeedTaskAsync(t2, listId, TaskStatus.Idle, title: "Second task");
var svc = BuildService();
- var spec = await svc.BuildForMergeHelperAsync(new[] { t1, t2 }, listId, Guid.NewGuid().ToString(), CancellationToken.None);
+ var spec = await svc.BuildForMergeHelperAsync(new[] { t1, t2 }, listId, Guid.NewGuid().ToString(), null, CancellationToken.None);
var sessionDir = TrackSessionDir(spec);
Assert.Equal(repo, spec.Cwd);
@@ -934,6 +934,27 @@ public sealed class InteractiveLaunchSpecServiceTests : IDisposable
Assert.Equal(InteractiveLaunchSpecService.McpToolTimeoutMs, spec.Env["MCP_TOOL_TIMEOUT"]);
}
+ [Theory]
+ [InlineData("fable", "fable")]
+ [InlineData("opus", "opus")]
+ [InlineData("not-a-model", ModelRegistry.HandlerTriageAlias)] // unknown → default, never blocks the launch
+ public async Task BuildForMergeHelperAsync_ModelOverride_DrivesModelAndEffortArgs(string requested, string expected)
+ {
+ var repo = Path.Combine(_tempDir, $"repoModel-{requested}");
+ Directory.CreateDirectory(repo);
+ var listId = await SeedListAsync(workingDir: repo, name: "Alpha");
+ var t1 = Guid.NewGuid().ToString();
+ await SeedTaskAsync(t1, listId, TaskStatus.Idle, title: "First task");
+
+ var svc = BuildService();
+ var spec = await svc.BuildForMergeHelperAsync(new[] { t1 }, listId, Guid.NewGuid().ToString(), requested, CancellationToken.None);
+ TrackSessionDir(spec);
+
+ var args = spec.Args.ToList();
+ Assert.Equal(expected, args[args.IndexOf("--model") + 1]);
+ Assert.Equal(ModelPresets.For(ModelPresets.Defaults, expected).Effort, args[args.IndexOf("--effort") + 1]);
+ }
+
// A directory argument that keeps its trailing separator escapes its own closing quote once the
// ConPTY host flattens Args into a single Windows command line ("C:\repo\" -> \" is a literal
// quote), so --add-dir's variadic list swallows every following argument -- including the
@@ -949,7 +970,7 @@ public sealed class InteractiveLaunchSpecServiceTests : IDisposable
await SeedTaskAsync(t1, listId, TaskStatus.WaitingForReview, title: "First task");
var svc = BuildService();
- var spec = await svc.BuildForMergeHelperAsync(new[] { t1 }, listId, Guid.NewGuid().ToString(), CancellationToken.None);
+ var spec = await svc.BuildForMergeHelperAsync(new[] { t1 }, listId, Guid.NewGuid().ToString(), null, CancellationToken.None);
var sessionDir = TrackSessionDir(spec);
var args = spec.Args.ToList();
@@ -972,7 +993,7 @@ public sealed class InteractiveLaunchSpecServiceTests : IDisposable
await SeedTaskAsync(t2, listId, TaskStatus.Idle, title: "Second task");
var svc = BuildService();
- var spec = await svc.BuildForMergeHelperAsync(new[] { t1, t2 }, listId, Guid.NewGuid().ToString(), CancellationToken.None);
+ var spec = await svc.BuildForMergeHelperAsync(new[] { t1, t2 }, listId, Guid.NewGuid().ToString(), null, CancellationToken.None);
var sessionDir = TrackSessionDir(spec);
var brief = File.ReadAllText(Path.Combine(sessionDir, "brief.md"));
@@ -999,7 +1020,7 @@ public sealed class InteractiveLaunchSpecServiceTests : IDisposable
var handlerTaskId = Guid.NewGuid().ToString();
var svc = BuildService();
- var spec = await svc.BuildForMergeHelperAsync(new[] { t1 }, listId, handlerTaskId, CancellationToken.None);
+ var spec = await svc.BuildForMergeHelperAsync(new[] { t1 }, listId, handlerTaskId, null, CancellationToken.None);
var sessionDir = TrackSessionDir(spec);
var brief = File.ReadAllText(Path.Combine(sessionDir, "brief.md"));
@@ -1018,7 +1039,7 @@ public sealed class InteractiveLaunchSpecServiceTests : IDisposable
description: "Do the thing carefully and report back.");
var svc = BuildService();
- var spec = await svc.BuildForMergeHelperAsync(new[] { t1 }, listId, Guid.NewGuid().ToString(), CancellationToken.None);
+ var spec = await svc.BuildForMergeHelperAsync(new[] { t1 }, listId, Guid.NewGuid().ToString(), null, CancellationToken.None);
var sessionDir = TrackSessionDir(spec);
var brief = File.ReadAllText(Path.Combine(sessionDir, "brief.md"));
@@ -1036,7 +1057,7 @@ public sealed class InteractiveLaunchSpecServiceTests : IDisposable
await SeedTaskAsync(t1, listId, TaskStatus.Idle, title: "No description task", description: null);
var svc = BuildService();
- var spec = await svc.BuildForMergeHelperAsync(new[] { t1 }, listId, Guid.NewGuid().ToString(), CancellationToken.None);
+ var spec = await svc.BuildForMergeHelperAsync(new[] { t1 }, listId, Guid.NewGuid().ToString(), null, CancellationToken.None);
var sessionDir = TrackSessionDir(spec);
var brief = File.ReadAllText(Path.Combine(sessionDir, "brief.md"));
@@ -1067,7 +1088,7 @@ public sealed class InteractiveLaunchSpecServiceTests : IDisposable
await SeedTaskAsync(t2, listId, TaskStatus.WaitingForReview, title: "Second task", description: "plain description");
var svc = BuildService();
- var spec = await svc.BuildForMergeHelperAsync(new[] { t1, t2 }, listId, Guid.NewGuid().ToString(), CancellationToken.None);
+ var spec = await svc.BuildForMergeHelperAsync(new[] { t1, t2 }, listId, Guid.NewGuid().ToString(), null, CancellationToken.None);
var sessionDir = TrackSessionDir(spec);
var brief = File.ReadAllText(Path.Combine(sessionDir, "brief.md"));
@@ -1402,7 +1423,7 @@ public sealed class InteractiveLaunchSpecServiceTests : IDisposable
var svc = BuildService();
- var triageSpec = await svc.BuildForMergeHelperAsync(new[] { survivor }, listId, handlerTaskId, CancellationToken.None);
+ var triageSpec = await svc.BuildForMergeHelperAsync(new[] { survivor }, listId, handlerTaskId, null, CancellationToken.None);
var triageDir = TrackSessionDir(triageSpec);
var executeSpec = await svc.BuildForMergeHelperHandoffAsync(handlerTaskId, new[] { survivor }, "merge", CancellationToken.None);
var executeDir = TrackSessionDir(executeSpec);
@@ -1453,7 +1474,7 @@ public sealed class InteractiveLaunchSpecServiceTests : IDisposable
ClaudeSessionId: "sess-42", Token: "tok-2", WorktreePath: _worktreeDir);
var svc = BuildService();
- var mergeHelperSpec = await svc.BuildForMergeHelperAsync(new[] { survivor }, listId, handlerTaskId, CancellationToken.None);
+ var mergeHelperSpec = await svc.BuildForMergeHelperAsync(new[] { survivor }, listId, handlerTaskId, null, CancellationToken.None);
TrackSessionDir(mergeHelperSpec);
var handoffSpec = await svc.BuildForMergeHelperHandoffAsync(handlerTaskId, new[] { survivor }, "wait", CancellationToken.None);
TrackSessionDir(handoffSpec);