feat(settings): per-model effort and turn presets
ClaudeDo never passed --effort, so every session inherited whatever effortLevel the user's Claude Code config happened to carry. Settings -> General now holds one row per model alias (haiku medium/20, sonnet high/30, opus high/40, fable high/25) supplying the global effort and turn defaults; list- and task-level max-turns overrides still win, and the agent editor's inherited badge follows the model. --effort is applied to autonomous runs and to every ConPTY spec (task session, planning start/resume, ad-hoc, list handler). The model itself is deliberately not forced on interactive sessions. The single global 'Max turns' field is replaced by the table, and 'fable' joins ModelRegistry.Aliases. The migration also adds the is_manual columns used by the next commit.
This commit is contained in:
@@ -184,7 +184,7 @@ public sealed class InteractiveLaunchSpecServiceTests : IDisposable
|
||||
Assert.True(Directory.Exists(wtRow.Path));
|
||||
|
||||
Assert.Equal(wtRow.Path, spec.Cwd);
|
||||
Assert.Equal(new[] { "T" }, spec.Args); // fresh session seeds the task title as the prompt
|
||||
Assert.Equal(new[] { "T" }, ArgsAfterEffort(spec)); // fresh session seeds the task title as the prompt
|
||||
Assert.Equal(_claudeStubPath, spec.Exe);
|
||||
}
|
||||
|
||||
@@ -230,7 +230,7 @@ public sealed class InteractiveLaunchSpecServiceTests : IDisposable
|
||||
|
||||
Assert.Equal(_worktreeDir, spec.Cwd);
|
||||
Assert.Equal(_claudeStubPath, spec.Exe);
|
||||
Assert.Equal(new[] { "--resume", "sess-123" }, spec.Args);
|
||||
Assert.Equal(new[] { "--resume", "sess-123" }, ArgsAfterEffort(spec));
|
||||
Assert.Equal("200000", spec.Env["MCP_TOOL_TIMEOUT"]);
|
||||
}
|
||||
|
||||
@@ -246,7 +246,7 @@ public sealed class InteractiveLaunchSpecServiceTests : IDisposable
|
||||
var svc = BuildService();
|
||||
var spec = await svc.BuildForTaskAsync(taskId, CancellationToken.None);
|
||||
|
||||
Assert.Equal(new[] { "--resume", "sess-kept" }, spec.Args);
|
||||
Assert.Equal(new[] { "--resume", "sess-kept" }, ArgsAfterEffort(spec));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -261,7 +261,7 @@ public sealed class InteractiveLaunchSpecServiceTests : IDisposable
|
||||
var svc = BuildService();
|
||||
var spec = await svc.BuildForTaskAsync(taskId, CancellationToken.None);
|
||||
|
||||
Assert.Equal(new[] { "T" }, spec.Args); // fresh: seeds the task title as the prompt
|
||||
Assert.Equal(new[] { "T" }, ArgsAfterEffort(spec)); // fresh: seeds the task title as the prompt
|
||||
Assert.Equal(_worktreeDir, spec.Cwd);
|
||||
}
|
||||
|
||||
@@ -277,7 +277,7 @@ public sealed class InteractiveLaunchSpecServiceTests : IDisposable
|
||||
var svc = BuildService();
|
||||
var spec = await svc.BuildForTaskAsync(taskId, CancellationToken.None);
|
||||
|
||||
Assert.Equal(new[] { "T" }, spec.Args); // fresh: seeds the task title as the prompt
|
||||
Assert.Equal(new[] { "T" }, ArgsAfterEffort(spec)); // fresh: seeds the task title as the prompt
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -297,7 +297,7 @@ public sealed class InteractiveLaunchSpecServiceTests : IDisposable
|
||||
var svc = BuildService();
|
||||
var spec = await svc.BuildForTaskAsync(taskId, CancellationToken.None);
|
||||
|
||||
Assert.Equal(new[] { "T\n\nDo the thing" }, spec.Args);
|
||||
Assert.Equal(new[] { "T\n\nDo the thing" }, ArgsAfterEffort(spec));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -333,7 +333,7 @@ public sealed class InteractiveLaunchSpecServiceTests : IDisposable
|
||||
|
||||
Assert.Equal(_tempDir, spec.Cwd);
|
||||
Assert.Equal(_claudeStubPath, spec.Exe);
|
||||
Assert.Empty(spec.Args);
|
||||
Assert.Empty(ArgsAfterEffort(spec));
|
||||
Assert.Equal("200000", spec.Env["MCP_TOOL_TIMEOUT"]);
|
||||
Assert.Empty(_seeder.Calls);
|
||||
}
|
||||
@@ -348,6 +348,17 @@ public sealed class InteractiveLaunchSpecServiceTests : IDisposable
|
||||
|
||||
// ── Merge helper ──
|
||||
|
||||
/// Every ConPTY spec now leads with `--effort <level>` from the per-model preset (the seeded
|
||||
/// settings row has no overrides, so the shipped default for the default model applies).
|
||||
/// Asserts that pair and returns the rest of the args for the test's own comparison.
|
||||
private static string[] ArgsAfterEffort(LaunchSpec spec)
|
||||
{
|
||||
var args = spec.Args.ToList();
|
||||
Assert.Equal("--effort", args[0]);
|
||||
Assert.Equal(ModelPresets.For(ModelPresets.Defaults, ModelRegistry.DefaultAlias).Effort, args[1]);
|
||||
return args.Skip(2).ToArray();
|
||||
}
|
||||
|
||||
private readonly List<string> _mergeHelperSessionDirs = new();
|
||||
|
||||
/// The session dir is the value right after --add-dir; register it for cleanup
|
||||
@@ -498,11 +509,12 @@ public sealed class InteractiveLaunchSpecServiceTests : IDisposable
|
||||
|
||||
var spec = BuildService().BuildPlanningResume(ctx);
|
||||
|
||||
Assert.Equal("--permission-mode", spec.Args[0]);
|
||||
Assert.Equal("default", spec.Args[1]);
|
||||
Assert.Equal("--allowedTools", spec.Args[2]);
|
||||
Assert.Contains("mcp__claudedo__", spec.Args[3]);
|
||||
Assert.Equal(new[] { "--resume", "sess-42" }, spec.Args.Skip(4).ToArray());
|
||||
var args = ArgsAfterEffort(spec);
|
||||
Assert.Equal("--permission-mode", args[0]);
|
||||
Assert.Equal("default", args[1]);
|
||||
Assert.Equal("--allowedTools", args[2]);
|
||||
Assert.Contains("mcp__claudedo__", args[3]);
|
||||
Assert.Equal(new[] { "--resume", "sess-42" }, args.Skip(4).ToArray());
|
||||
Assert.Equal("tok-2", spec.Env["CLAUDEDO_PLANNING_TOKEN"]);
|
||||
Assert.Equal(_worktreeDir, spec.Cwd);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user