diff --git a/src/ClaudeDo.Worker/External/ConfigMcpTools.cs b/src/ClaudeDo.Worker/External/ConfigMcpTools.cs index ab4d872..0b497bb 100644 --- a/src/ClaudeDo.Worker/External/ConfigMcpTools.cs +++ b/src/ClaudeDo.Worker/External/ConfigMcpTools.cs @@ -6,7 +6,7 @@ using ModelContextProtocol.Server; namespace ClaudeDo.Worker.External; -public sealed record TaskConfigDto(string? Model, string? SystemPrompt, string? AgentPath); +public sealed record TaskConfigDto(string? Model, string? SystemPrompt, string? AgentPath, int? MaxTurns); [McpServerToolType] public sealed class ConfigMcpTools @@ -26,12 +26,12 @@ public sealed class ConfigMcpTools public async Task GetListConfig(string listId, CancellationToken cancellationToken) { var cfg = await _lists.GetConfigAsync(listId, cancellationToken); - return cfg is null ? null : new TaskConfigDto(cfg.Model, cfg.SystemPrompt, cfg.AgentPath); + return cfg is null ? null : new TaskConfigDto(cfg.Model, cfg.SystemPrompt, cfg.AgentPath, cfg.MaxTurns); } - [McpServerTool, Description("Set a list's default model/system prompt/agent path. Passing all three as null clears the list config.")] + [McpServerTool, Description("Set a list's default model/system prompt/agent path/max turns. Passing all four as null clears the list config.")] public async Task SetListConfig( - string listId, string? model, string? systemPrompt, string? agentPath, CancellationToken cancellationToken) + string listId, string? model, string? systemPrompt, string? agentPath, int? maxTurns, CancellationToken cancellationToken) { _ = await _lists.GetByIdAsync(listId, cancellationToken) ?? throw new InvalidOperationException($"List {listId} not found."); @@ -40,25 +40,25 @@ public sealed class ConfigMcpTools var sp = systemPrompt.NullIfBlank(); var ap = agentPath.NullIfBlank(); - if (m is null && sp is null && ap is null) + if (m is null && sp is null && ap is null && maxTurns is null) await _lists.DeleteConfigAsync(listId, cancellationToken); else await _lists.SetConfigAsync(new ListConfigEntity { - ListId = listId, Model = m, SystemPrompt = sp, AgentPath = ap, + ListId = listId, Model = m, SystemPrompt = sp, AgentPath = ap, MaxTurns = maxTurns, }, cancellationToken); await _broadcaster.ListUpdated(listId); } - [McpServerTool, Description("Set per-task config overrides (model/system prompt/agent path). Pass null for any field to clear that override.")] + [McpServerTool, Description("Set per-task config overrides (model/system prompt/agent path/max turns). Pass null for any field to clear that override.")] public async Task SetTaskConfig( - string taskId, string? model, string? systemPrompt, string? agentPath, CancellationToken cancellationToken) + string taskId, string? model, string? systemPrompt, string? agentPath, int? maxTurns, CancellationToken cancellationToken) { _ = await _tasks.GetByIdAsync(taskId, cancellationToken) ?? throw new InvalidOperationException($"Task {taskId} not found."); - await _tasks.UpdateAgentSettingsAsync(taskId, model.NullIfBlank(), systemPrompt.NullIfBlank(), agentPath.NullIfBlank(), ct: cancellationToken); + await _tasks.UpdateAgentSettingsAsync(taskId, model.NullIfBlank(), systemPrompt.NullIfBlank(), agentPath.NullIfBlank(), maxTurns, cancellationToken); await _broadcaster.TaskUpdated(taskId); } } diff --git a/src/ClaudeDo.Worker/Hub/WorkerHub.cs b/src/ClaudeDo.Worker/Hub/WorkerHub.cs index af1fad9..72ac154 100644 --- a/src/ClaudeDo.Worker/Hub/WorkerHub.cs +++ b/src/ClaudeDo.Worker/Hub/WorkerHub.cs @@ -55,9 +55,9 @@ public record ForceRemoveResultDto(bool Removed, string? Reason); public record MergeResultDto(string Status, IReadOnlyList ConflictFiles, string? ErrorMessage); public record MergeTargetsDto(string DefaultBranch, IReadOnlyList LocalBranches); public record UpdateListDto(string Id, string Name, string? WorkingDir, string DefaultCommitType); -public record UpdateListConfigDto(string ListId, string? Model, string? SystemPrompt, string? AgentPath); -public record UpdateTaskAgentSettingsDto(string TaskId, string? Model, string? SystemPrompt, string? AgentPath); -public record ListConfigDto(string? Model, string? SystemPrompt, string? AgentPath); +public record UpdateListConfigDto(string ListId, string? Model, string? SystemPrompt, string? AgentPath, int? MaxTurns = null); +public record UpdateTaskAgentSettingsDto(string TaskId, string? Model, string? SystemPrompt, string? AgentPath, int? MaxTurns = null); +public record ListConfigDto(string? Model, string? SystemPrompt, string? AgentPath, int? MaxTurns = null); public record SeedResultDto(int Copied, int Skipped); public sealed class WorkerHub : Microsoft.AspNetCore.SignalR.Hub @@ -340,7 +340,7 @@ public sealed class WorkerHub : Microsoft.AspNetCore.SignalR.Hub var systemPrompt = dto.SystemPrompt.NullIfBlank(); var agentPath = dto.AgentPath.NullIfBlank(); - if (model is null && systemPrompt is null && agentPath is null) + if (model is null && systemPrompt is null && agentPath is null && dto.MaxTurns is null) { await repo.DeleteConfigAsync(dto.ListId); } @@ -352,6 +352,7 @@ public sealed class WorkerHub : Microsoft.AspNetCore.SignalR.Hub Model = model, SystemPrompt = systemPrompt, AgentPath = agentPath, + MaxTurns = dto.MaxTurns, }); } @@ -364,7 +365,7 @@ public sealed class WorkerHub : Microsoft.AspNetCore.SignalR.Hub var repo = new ListRepository(ctx); var config = await repo.GetConfigAsync(listId); if (config is null) return null; - return new ListConfigDto(config.Model, config.SystemPrompt, config.AgentPath); + return new ListConfigDto(config.Model, config.SystemPrompt, config.AgentPath, config.MaxTurns); } public async Task SetTaskStatus(string taskId, string status) @@ -411,7 +412,8 @@ public sealed class WorkerHub : Microsoft.AspNetCore.SignalR.Hub dto.TaskId, dto.Model.NullIfBlank(), dto.SystemPrompt.NullIfBlank(), - dto.AgentPath.NullIfBlank()); + dto.AgentPath.NullIfBlank(), + dto.MaxTurns); await _broadcaster.TaskUpdated(dto.TaskId); }