fix(conpty): stop pinning --effort on hand-driven interactive sessions
Task, ad-hoc, planning, handoff and resume sessions now inherit the CLI default / the user's /effort setting. List-handler sessions keep their per-role effort.
This commit is contained in:
@@ -170,11 +170,6 @@ public sealed class InteractiveLaunchSpecService
|
||||
var resolvedClaude = WindowsTerminalLauncher.Resolve(_claudePath)
|
||||
?? throw new InvalidOperationException($"claude executable not found: {_claudePath}");
|
||||
|
||||
// Start the hand-driven session at the effort configured for the model this task would run
|
||||
// under, instead of inheriting whatever the user's global Claude Code config happens to be.
|
||||
// The model itself is deliberately NOT forced here — the user can still switch it in the TUI.
|
||||
var effort = EffortFor(globalSettings, task.Model ?? listConfig?.Model);
|
||||
|
||||
// Resume this task's own last interactive conversation when it has one -- it takes
|
||||
// precedence over the latest autonomous run's session, since an interactive session is a
|
||||
// distinct conversation from an autonomous run even against the same worktree. Fall back
|
||||
@@ -193,13 +188,13 @@ public sealed class InteractiveLaunchSpecService
|
||||
if (resumeSessionId is { Length: > 0 })
|
||||
{
|
||||
sessionId = resumeSessionId;
|
||||
args = WithEffort(WindowsTerminalLauncher.BuildResumeArgs(resumeSessionId), effort);
|
||||
args = WindowsTerminalLauncher.BuildResumeArgs(resumeSessionId);
|
||||
}
|
||||
else
|
||||
{
|
||||
sessionId = Guid.NewGuid().ToString();
|
||||
await new TaskRepository(ctx).SetInteractiveSessionIdAsync(taskId, sessionId, ct);
|
||||
args = await BuildFreshTaskArgsAsync(task, effort, sessionId, ct);
|
||||
args = await BuildFreshTaskArgsAsync(task, sessionId, ct);
|
||||
}
|
||||
|
||||
if (paneId is not null)
|
||||
@@ -242,8 +237,7 @@ public sealed class InteractiveLaunchSpecService
|
||||
|
||||
return new LaunchSpec(
|
||||
ctx.WorkingDir, resolvedClaude,
|
||||
WithEffort(WindowsTerminalLauncher.BuildPlanningStartArgs(ctx),
|
||||
EffortFor(ReadSettings(), ModelRegistry.PlanningAlias)),
|
||||
WindowsTerminalLauncher.BuildPlanningStartArgs(ctx),
|
||||
env);
|
||||
}
|
||||
|
||||
@@ -262,8 +256,7 @@ public sealed class InteractiveLaunchSpecService
|
||||
|
||||
return new LaunchSpec(
|
||||
ctx.WorkingDir, resolvedClaude,
|
||||
WithEffort(WindowsTerminalLauncher.BuildPlanningResumeArgs(ctx.ClaudeSessionId),
|
||||
EffortFor(ReadSettings(), ModelRegistry.PlanningAlias)),
|
||||
WindowsTerminalLauncher.BuildPlanningResumeArgs(ctx.ClaudeSessionId),
|
||||
env);
|
||||
}
|
||||
|
||||
@@ -280,15 +273,13 @@ public sealed class InteractiveLaunchSpecService
|
||||
?? throw new InvalidOperationException($"claude executable not found: {_claudePath}");
|
||||
|
||||
await using var ctx = await _dbFactory.CreateDbContextAsync(ct);
|
||||
var settings = await new AppSettingsRepository(ctx).GetAsync(ct);
|
||||
|
||||
var env = new Dictionary<string, string>
|
||||
{
|
||||
["MCP_TOOL_TIMEOUT"] = McpToolTimeoutMs,
|
||||
};
|
||||
|
||||
// No task and no list here — the global default model's preset decides the effort.
|
||||
var args = WithEffort(Array.Empty<string>(), EffortFor(settings, settings.DefaultModel));
|
||||
IReadOnlyList<string> args = [];
|
||||
if (paneId is not null)
|
||||
{
|
||||
// Pre-assigned so the pane entry can carry it -- a handoff records it as the brief's origin.
|
||||
@@ -322,16 +313,13 @@ public sealed class InteractiveLaunchSpecService
|
||||
?? throw new InvalidOperationException($"claude executable not found: {_claudePath}");
|
||||
|
||||
await using var ctx = await _dbFactory.CreateDbContextAsync(ct);
|
||||
var settings = await new AppSettingsRepository(ctx).GetAsync(ct);
|
||||
var sessionId = Guid.NewGuid().ToString();
|
||||
|
||||
var model = settings.DefaultModel;
|
||||
TaskEntity? task = null;
|
||||
if (taskId is not null)
|
||||
{
|
||||
var taskRepo = new TaskRepository(ctx);
|
||||
task = await taskRepo.GetByIdAsync(taskId, ct) ?? throw new KeyNotFoundException($"Task not found: {taskId}");
|
||||
model = task.Model ?? (await new ListRepository(ctx).GetConfigAsync(task.ListId, ct))?.Model;
|
||||
await taskRepo.SetInteractiveSessionIdAsync(taskId, sessionId, ct);
|
||||
}
|
||||
|
||||
@@ -352,7 +340,6 @@ public sealed class InteractiveLaunchSpecService
|
||||
var args = new List<string>(await PaneSessionArgsAsync(paneId, sessionId, ct))
|
||||
{
|
||||
"--add-dir", Path.GetDirectoryName(briefPath)!,
|
||||
"--effort", EffortFor(settings, model),
|
||||
"--session-id", sessionId,
|
||||
$"Read the file {briefPath} first. It is the handoff brief from the session that worked here " +
|
||||
"before you. After reading it, continue the work as it describes.",
|
||||
@@ -379,12 +366,11 @@ public sealed class InteractiveLaunchSpecService
|
||||
|
||||
var resolvedClaude = WindowsTerminalLauncher.Resolve(_claudePath)
|
||||
?? throw new InvalidOperationException($"claude executable not found: {_claudePath}");
|
||||
var settings = await new AppSettingsRepository(ctx).GetAsync(ct);
|
||||
|
||||
IReadOnlyList<string> args =
|
||||
[
|
||||
.. await PaneSessionArgsAsync(paneId, sessionId, ct),
|
||||
.. WithEffort(WindowsTerminalLauncher.BuildResumeArgs(sessionId), EffortFor(settings, settings.DefaultModel)),
|
||||
.. WindowsTerminalLauncher.BuildResumeArgs(sessionId),
|
||||
];
|
||||
var env = new Dictionary<string, string>
|
||||
{
|
||||
@@ -836,21 +822,6 @@ public sealed class InteractiveLaunchSpecService
|
||||
private static string EffortFor(AppSettingsEntity settings, string? model)
|
||||
=> ModelPresets.For(settings.ModelPresets, model ?? settings.DefaultModel).Effort;
|
||||
|
||||
// Prepends `--effort <level>`. It has to lead: a positional kickoff prompt must stay last, and
|
||||
// it may only follow a single-value flag — a variadic flag would swallow it.
|
||||
private static IReadOnlyList<string> WithEffort(IReadOnlyList<string> args, string effort)
|
||||
{
|
||||
var result = new List<string>(args.Count + 2) { "--effort", effort };
|
||||
result.AddRange(args);
|
||||
return result;
|
||||
}
|
||||
|
||||
private AppSettingsEntity ReadSettings()
|
||||
{
|
||||
using var ctx = _dbFactory.CreateDbContext();
|
||||
return new AppSettingsRepository(ctx).GetAsync().GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
// The task's brief (title + description, unmodified/multi-line) must never travel as a CLI
|
||||
// argument: the ConPTY host flattens Args into one command line to spawn the process, and
|
||||
// claude re-splits that line on whitespace, so any token in the description that starts with
|
||||
@@ -858,18 +829,18 @@ public sealed class InteractiveLaunchSpecService
|
||||
// the brief at its first line even if it didn't. So it's written to a file instead --
|
||||
// same pattern as BuildForMergeHelperAsync / WindowsTerminalLauncher.BuildPlanningStartArgs --
|
||||
// and claude is pointed at it with a single-line kickoff. --add-dir exposes the session dir to
|
||||
// Read; --effort (single-value) must sit directly before the positional kickoff so the
|
||||
// Read; --session-id (single-value) must sit directly before the positional kickoff so the
|
||||
// preceding variadic --add-dir doesn't swallow the kickoff as another directory.
|
||||
// No brief (task has neither a title nor a description) -> no positional arg at all.
|
||||
// `--session-id` pre-assigns the claude session id the caller already persisted (see
|
||||
// BuildForTaskAsync) so this fresh conversation is resumable from its very first turn --
|
||||
// it's a single-value flag, so it may sit directly before the positional kickoff.
|
||||
private static async Task<IReadOnlyList<string>> BuildFreshTaskArgsAsync(
|
||||
TaskEntity task, string effort, string sessionId, CancellationToken ct)
|
||||
TaskEntity task, string sessionId, CancellationToken ct)
|
||||
{
|
||||
var brief = BuildTaskBrief(task);
|
||||
if (string.IsNullOrEmpty(brief))
|
||||
return new[] { "--effort", effort, "--session-id", sessionId };
|
||||
return new[] { "--session-id", sessionId };
|
||||
|
||||
var sessionDir = Path.Combine(Paths.AppDataRoot(), "task-sessions", task.Id);
|
||||
Directory.CreateDirectory(sessionDir);
|
||||
@@ -879,7 +850,6 @@ public sealed class InteractiveLaunchSpecService
|
||||
return new[]
|
||||
{
|
||||
"--add-dir", sessionDir,
|
||||
"--effort", effort,
|
||||
"--session-id", sessionId,
|
||||
$"Read the file {briefPath} first. It contains the task you must work on. " +
|
||||
"Then, before changing anything, reply with a one-line restatement of what you intend to do " +
|
||||
|
||||
Reference in New Issue
Block a user