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.
131 lines
6.3 KiB
C#
131 lines
6.3 KiB
C#
using ClaudeDo.Data.Models;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
|
using TaskStatus = ClaudeDo.Data.Models.TaskStatus;
|
|
|
|
namespace ClaudeDo.Data.Configuration;
|
|
|
|
public class TaskEntityConfiguration : IEntityTypeConfiguration<TaskEntity>
|
|
{
|
|
private static string StatusToString(TaskStatus v)
|
|
=> v switch
|
|
{
|
|
TaskStatus.Idle => "idle",
|
|
TaskStatus.Queued => "queued",
|
|
TaskStatus.Running => "running",
|
|
TaskStatus.WaitingForReview => "waiting_for_review",
|
|
TaskStatus.WaitingForChildren => "waiting_for_children",
|
|
TaskStatus.Done => "done",
|
|
TaskStatus.Failed => "failed",
|
|
TaskStatus.Cancelled => "cancelled",
|
|
_ => throw new ArgumentOutOfRangeException(nameof(v)),
|
|
};
|
|
|
|
private static TaskStatus StatusFromString(string v)
|
|
=> v switch
|
|
{
|
|
"idle" => TaskStatus.Idle,
|
|
"queued" => TaskStatus.Queued,
|
|
"running" => TaskStatus.Running,
|
|
"waiting_for_review" => TaskStatus.WaitingForReview,
|
|
"waiting_for_children" => TaskStatus.WaitingForChildren,
|
|
"done" => TaskStatus.Done,
|
|
"failed" => TaskStatus.Failed,
|
|
"cancelled" => TaskStatus.Cancelled,
|
|
_ => throw new ArgumentOutOfRangeException(nameof(v)),
|
|
};
|
|
|
|
private static readonly ValueConverter<TaskStatus, string> StatusConverter =
|
|
new(v => StatusToString(v), v => StatusFromString(v));
|
|
|
|
private static string PhaseToString(PlanningPhase v)
|
|
=> v switch
|
|
{
|
|
PlanningPhase.None => "none",
|
|
PlanningPhase.Active => "active",
|
|
PlanningPhase.Finalized => "finalized",
|
|
_ => throw new ArgumentOutOfRangeException(nameof(v)),
|
|
};
|
|
|
|
private static PlanningPhase PhaseFromString(string v)
|
|
=> v switch
|
|
{
|
|
"none" => PlanningPhase.None,
|
|
"active" => PlanningPhase.Active,
|
|
"finalized" => PlanningPhase.Finalized,
|
|
_ => throw new ArgumentOutOfRangeException(nameof(v)),
|
|
};
|
|
|
|
private static readonly ValueConverter<PlanningPhase, string> PhaseConverter =
|
|
new(v => PhaseToString(v), v => PhaseFromString(v));
|
|
|
|
public void Configure(EntityTypeBuilder<TaskEntity> builder)
|
|
{
|
|
builder.ToTable("tasks");
|
|
|
|
builder.HasKey(t => t.Id);
|
|
builder.Property(t => t.Id).HasColumnName("id");
|
|
builder.Property(t => t.ListId).HasColumnName("list_id").IsRequired();
|
|
builder.Property(t => t.Title).HasColumnName("title").IsRequired();
|
|
builder.Property(t => t.Description).HasColumnName("description");
|
|
builder.Property(t => t.Status).HasColumnName("status").IsRequired()
|
|
.HasConversion(StatusConverter);
|
|
builder.Property(t => t.PlanningPhase).HasColumnName("planning_phase").IsRequired()
|
|
.HasConversion(PhaseConverter).HasDefaultValue(PlanningPhase.None);
|
|
builder.Property(t => t.BlockedByTaskId).HasColumnName("blocked_by_task_id");
|
|
builder.Property(t => t.ScheduledFor).HasColumnName("scheduled_for");
|
|
builder.Property(t => t.Result).HasColumnName("result");
|
|
builder.Property(t => t.ReviewFeedback).HasColumnName("review_feedback");
|
|
builder.Property(t => t.RoadblockCount).HasColumnName("roadblock_count").HasDefaultValue(0);
|
|
builder.Property(t => t.LogPath).HasColumnName("log_path");
|
|
builder.Property(t => t.CreatedAt).HasColumnName("created_at").IsRequired();
|
|
builder.Property(t => t.StartedAt).HasColumnName("started_at");
|
|
builder.Property(t => t.FinishedAt).HasColumnName("finished_at");
|
|
builder.Property(t => t.CommitType).HasColumnName("commit_type").IsRequired().HasDefaultValue("chore");
|
|
builder.Property(t => t.Model).HasColumnName("model");
|
|
builder.Property(t => t.SystemPrompt).HasColumnName("system_prompt");
|
|
builder.Property(t => t.AgentPath).HasColumnName("agent_path");
|
|
builder.Property(t => t.MaxTurns).HasColumnName("max_turns");
|
|
builder.Property(t => t.IsStarred).HasColumnName("is_starred").HasDefaultValue(false);
|
|
builder.Property(t => t.IsMyDay).HasColumnName("is_my_day").HasDefaultValue(false);
|
|
builder.Property(t => t.IsManual).HasColumnName("is_manual").HasDefaultValue(false);
|
|
builder.Property(t => t.Notes).HasColumnName("notes");
|
|
builder.Property(t => t.SortOrder).HasColumnName("sort_order").IsRequired().HasDefaultValue(0);
|
|
builder.Property(t => t.SessionSkills).HasColumnName("session_skills");
|
|
|
|
builder.Property(t => t.ParentTaskId).HasColumnName("parent_task_id");
|
|
builder.Property(t => t.PlanningSessionId).HasColumnName("planning_session_id");
|
|
builder.Property(t => t.PlanningSessionToken).HasColumnName("planning_session_token");
|
|
builder.Property(t => t.PlanningFinalizedAt).HasColumnName("planning_finalized_at");
|
|
|
|
builder.Property(t => t.CreatedBy).HasColumnName("created_by");
|
|
|
|
builder.HasOne(t => t.Parent)
|
|
.WithMany(t => t.Children)
|
|
.HasForeignKey(t => t.ParentTaskId)
|
|
.OnDelete(DeleteBehavior.Restrict);
|
|
|
|
// BlockedBy: predecessor in a sequential chain. SetNull on delete so child becomes pickable.
|
|
builder.HasOne<TaskEntity>()
|
|
.WithMany()
|
|
.HasForeignKey(t => t.BlockedByTaskId)
|
|
.OnDelete(DeleteBehavior.SetNull);
|
|
|
|
builder.HasOne(t => t.List)
|
|
.WithMany(l => l.Tasks)
|
|
.HasForeignKey(t => t.ListId)
|
|
.OnDelete(DeleteBehavior.Cascade);
|
|
|
|
builder.HasOne(t => t.Worktree)
|
|
.WithOne(w => w.Task)
|
|
.HasForeignKey<WorktreeEntity>(w => w.TaskId);
|
|
|
|
builder.HasIndex(t => t.ListId).HasDatabaseName("idx_tasks_list_id");
|
|
builder.HasIndex(t => t.Status).HasDatabaseName("idx_tasks_status");
|
|
builder.HasIndex(t => new { t.ListId, t.SortOrder }).HasDatabaseName("idx_tasks_list_sort");
|
|
builder.HasIndex(t => t.ParentTaskId).HasDatabaseName("idx_tasks_parent_task_id");
|
|
builder.HasIndex(t => t.BlockedByTaskId).HasDatabaseName("idx_tasks_blocked_by");
|
|
}
|
|
}
|