diff --git a/src/ClaudeDo.Worker/Runner/TaskRunner.cs b/src/ClaudeDo.Worker/Runner/TaskRunner.cs index 0c05959..ce1246d 100644 --- a/src/ClaudeDo.Worker/Runner/TaskRunner.cs +++ b/src/ClaudeDo.Worker/Runner/TaskRunner.cs @@ -82,13 +82,7 @@ public sealed class TaskRunner Directory.CreateDirectory(runDir); } - // Resolve config: task overrides > list config > null. - var resolvedConfig = new ClaudeRunConfig( - Model: task.Model ?? listConfig?.Model ?? "claude-sonnet-4-6", - SystemPrompt: task.SystemPrompt ?? listConfig?.SystemPrompt, - AgentPath: task.AgentPath ?? listConfig?.AgentPath, - ResumeSessionId: null - ); + var resolvedConfig = await ResolveConfigAsync(task, listConfig, null, ct); var now = DateTime.UtcNow; using (var context = _dbFactory.CreateDbContext()) @@ -186,12 +180,7 @@ public sealed class TaskRunner worktree = await wtRepo.GetByTaskIdAsync(taskId, ct); } - var resolvedConfig = new ClaudeRunConfig( - Model: task.Model ?? listConfig?.Model, - SystemPrompt: task.SystemPrompt ?? listConfig?.SystemPrompt, - AgentPath: task.AgentPath ?? listConfig?.AgentPath, - ResumeSessionId: lastRun.SessionId - ); + var resolvedConfig = await ResolveConfigAsync(task, listConfig, lastRun.SessionId, ct); // Determine run directory from existing worktree or sandbox. string runDir; @@ -268,7 +257,7 @@ public sealed class TaskRunner async line => { await logWriter.WriteLineAsync(line, ct); - await _broadcaster.TaskMessage(taskId, line); + await _broadcaster.TaskMessage(taskId, "[stdout] " + line); }, ct); @@ -372,4 +361,35 @@ public sealed class TaskRunner _logger.LogError(ex, "Failed to mark task {TaskId} as failed", taskId); } } + + private async Task ResolveConfigAsync( + TaskEntity task, ListConfigEntity? listConfig, string? resumeSessionId, CancellationToken ct) + { + AppSettingsEntity global; + using (var ctx = _dbFactory.CreateDbContext()) + { + var settingsRepo = new AppSettingsRepository(ctx); + global = await settingsRepo.GetAsync(ct); + } + + var instructions = MergeInstructions( + global.DefaultClaudeInstructions, listConfig?.SystemPrompt, task.SystemPrompt); + + return new ClaudeRunConfig( + Model: task.Model ?? listConfig?.Model ?? global.DefaultModel, + SystemPrompt: string.IsNullOrWhiteSpace(instructions) ? null : instructions, + AgentPath: task.AgentPath ?? listConfig?.AgentPath, + ResumeSessionId: resumeSessionId, + MaxTurns: global.DefaultMaxTurns, + PermissionMode: global.DefaultPermissionMode); + } + + public static string MergeInstructions(string? global, string? list, string? task) + { + var parts = new List(3); + if (!string.IsNullOrWhiteSpace(global)) parts.Add(global.Trim()); + if (!string.IsNullOrWhiteSpace(list)) parts.Add(list.Trim()); + if (!string.IsNullOrWhiteSpace(task)) parts.Add(task.Trim()); + return string.Join("\n\n", parts); + } }