feat(ui): triage model picker (opus/fable) in the merge helper start modal
This commit is contained in:
@@ -128,7 +128,7 @@ public abstract class StubWorkerClient : IWorkerClient
|
||||
=> Task.FromResult(new LaunchSpec(".", "claude", Array.Empty<string>(), new Dictionary<string, string>()));
|
||||
public virtual Task<LaunchSpec> GetAdHocLaunchSpecAsync(string directory, CancellationToken ct = default)
|
||||
=> Task.FromResult(new LaunchSpec(directory, "claude", Array.Empty<string>(), new Dictionary<string, string>()));
|
||||
public virtual Task<LaunchSpec> GetMergeHelperLaunchSpecAsync(IReadOnlyList<string> taskIds, string listId, string handlerTaskId, CancellationToken ct = default)
|
||||
public virtual Task<LaunchSpec> GetMergeHelperLaunchSpecAsync(IReadOnlyList<string> taskIds, string listId, string handlerTaskId, string? model = null, CancellationToken ct = default)
|
||||
=> Task.FromResult(new LaunchSpec(".", "claude", Array.Empty<string>(), new Dictionary<string, string>()));
|
||||
public virtual Task<string> CreateMergeHelperTaskAsync(
|
||||
IReadOnlyList<string> taskIds, string listId, string title, string descriptionHeader, CancellationToken ct = default)
|
||||
|
||||
@@ -556,7 +556,7 @@ public class MissionControlViewModelTests : IDisposable
|
||||
|
||||
private sealed class ThrowingMergeHelperLaunchSpecWorker : StubWorkerClient
|
||||
{
|
||||
public override Task<LaunchSpec> GetMergeHelperLaunchSpecAsync(IReadOnlyList<string> taskIds, string listId, string handlerTaskId, CancellationToken ct = default)
|
||||
public override Task<LaunchSpec> GetMergeHelperLaunchSpecAsync(IReadOnlyList<string> taskIds, string listId, string handlerTaskId, string? model = null, CancellationToken ct = default)
|
||||
=> throw new InvalidOperationException("spec failed");
|
||||
}
|
||||
|
||||
|
||||
@@ -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<InvalidOperationException>(
|
||||
() => svc.BuildForMergeHelperAsync(Array.Empty<string>(), listId, Guid.NewGuid().ToString(), CancellationToken.None));
|
||||
() => svc.BuildForMergeHelperAsync(Array.Empty<string>(), 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<InvalidOperationException>(
|
||||
() => 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<KeyNotFoundException>(
|
||||
() => 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);
|
||||
|
||||
Reference in New Issue
Block a user