feat(planning): let CreateChildTask set maxTurns on child tasks

Planning sessions could already steer a subtask's model but not its turn
budget, so a visibly large subtask would still die at the global default
turn limit. maxTurns is optional (default null = inherit list/global
default, matching model); 0/negative values are rejected as invalid input,
consistent with the existing model-alias validation.
This commit is contained in:
mika kuns
2026-08-05 11:33:16 +02:00
parent 1ee21b560d
commit 65db1cdefa
7 changed files with 103 additions and 13 deletions
@@ -119,6 +119,47 @@ public sealed class TaskRepositoryPlanningTests : IDisposable
_tasks.CreateChildAsync("nonexistent-parent-id", "t", null, null));
}
[Fact]
public async Task CreateChildAsync_NoMaxTurns_ChildInherits()
{
var listId = await CreateListAsync();
var parent = MakeTask(listId, phase: PlanningPhase.Active);
await _tasks.AddAsync(parent);
var child = await _tasks.CreateChildAsync(parent.Id, "child", null, null);
Assert.Null(child.MaxTurns);
var loaded = await _tasks.GetByIdAsync(child.Id);
Assert.Null(loaded!.MaxTurns);
}
[Fact]
public async Task CreateChildAsync_WithMaxTurns_Persists()
{
var listId = await CreateListAsync();
var parent = MakeTask(listId, phase: PlanningPhase.Active);
await _tasks.AddAsync(parent);
var child = await _tasks.CreateChildAsync(parent.Id, "child", null, null, maxTurns: 200);
Assert.Equal(200, child.MaxTurns);
var loaded = await _tasks.GetByIdAsync(child.Id);
Assert.Equal(200, loaded!.MaxTurns);
}
[Theory]
[InlineData(0)]
[InlineData(-5)]
public async Task CreateChildAsync_InvalidMaxTurns_Throws(int maxTurns)
{
var listId = await CreateListAsync();
var parent = MakeTask(listId, phase: PlanningPhase.Active);
await _tasks.AddAsync(parent);
await Assert.ThrowsAsync<ArgumentException>(() =>
_tasks.CreateChildAsync(parent.Id, "child", null, null, maxTurns: maxTurns));
}
[Fact]
public async Task SetPlanningStartedAsync_IdleTask_TransitionsToActivePhase()
{