Fuellarbeit: zeitlich flexible Arbeit fuellt ungenutztes Budget (#717, Stufe 1)
Eine einmalige Aufgabe mit fill_by (spaetestens bis) statt Termin laeuft, wenn der Tag Budget uebrig laesst: nach regulaerer und nachgeholter Arbeit, nur wenn nichts vom Agenten wartet, bevorzugt ab 18 Uhr, und nur wenn ihre eigene Schaetzung in den Spielraum aus #716 passt. Kurz vor der Frist wird einmal ein Mensch gefragt; nach der Frist laeuft sie als regulaere Arbeit. Ehrliche Schaetzung (TaskCostEstimator): eigene Laeufe, dann Geschwister unter derselben Elternaufgabe, dann die Angabe des Autors (als Schaetzung gekennzeichnet), zuletzt das Mittel des Agenten. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
25f74d9ad6
commit
183e922278
@@ -0,0 +1,86 @@
|
||||
using System.Text.Json;
|
||||
using ClawdDotNet.Core.Storage;
|
||||
using ClawdDotNet.Core.Tasks;
|
||||
using ClawdDotNet.Core.Tools;
|
||||
using ClawdDotNet.Tools.Taskboard;
|
||||
using ClawdDotNet.Tools.Tests.FileRW;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Shouldly;
|
||||
|
||||
namespace ClawdDotNet.Tools.Tests.Taskboard;
|
||||
|
||||
/// <summary>#717: An agent hands out fill work — a deadline instead of an appointment, and its estimate.</summary>
|
||||
public sealed class FillWorkToolTests : IDisposable
|
||||
{
|
||||
private readonly string _directory = Path.Combine(Path.GetTempPath(), "clawd-tests", Guid.NewGuid().ToString("N"));
|
||||
private readonly SqliteTaskRepository _repo;
|
||||
private readonly TaskboardTool _tool = new();
|
||||
|
||||
public FillWorkToolTests()
|
||||
{
|
||||
_repo = new SqliteTaskRepository(new SqliteStorage(Path.Combine(_directory, "state.db")));
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
SqliteStorage.ReleasePooledConnections(_directory);
|
||||
try { Directory.Delete(_directory, recursive: true); }
|
||||
catch { /* cleanup is secondary */ }
|
||||
}
|
||||
|
||||
private Task<ToolResult> Run(object input)
|
||||
=> _tool.ExecuteAsync(JsonSerializer.SerializeToElement(input), new AgentToolContext(
|
||||
"lead", "inst", new Dictionary<string, object?>(), new NullStateStore(), NullLogger.Instance,
|
||||
CancellationToken.None, SharedWorkspacePath: Path.Combine(_directory, "SharedWorkspace"), Tasks: _repo),
|
||||
CancellationToken.None);
|
||||
|
||||
private static string IdOf(ToolResult result) => result.Content.Split(' ', 4)[2];
|
||||
|
||||
private static string InAWeek => DateTime.UtcNow.AddDays(7).ToString("yyyy-MM-dd");
|
||||
|
||||
[Fact]
|
||||
public async Task Fill_work_is_created_with_its_deadline_and_estimate()
|
||||
{
|
||||
var created = await Run(new { action = "task_create", title = "Rewrite all 400 listings", assignee = "@ads", fill_by = InAWeek, estimate_usd = 1.5 });
|
||||
|
||||
created.Success.ShouldBeTrue(created.ErrorMessage);
|
||||
var task = (await _repo.GetAsync(IdOf(created), default))!;
|
||||
task.IsFillWork.ShouldBeTrue();
|
||||
task.EstimateUsd.ShouldBe(1.5m);
|
||||
|
||||
var list = (await Run(new { action = "task_list" })).Content;
|
||||
list.ShouldContain("Fill work: runs when the budget has room");
|
||||
list.ShouldContain("Author's estimate: 1.50 USD per run (not measured)");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task A_recurring_task_cannot_be_fill_work()
|
||||
{
|
||||
var result = await Run(new
|
||||
{
|
||||
action = "task_create", title = "Hourly check", fill_by = InAWeek,
|
||||
when = new { kind = "every", value = "1h", tz = "UTC" }
|
||||
});
|
||||
|
||||
result.Success.ShouldBeFalse();
|
||||
result.ErrorMessage!.ShouldContain("one-time");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task A_deadline_in_the_past_is_refused()
|
||||
{
|
||||
var result = await Run(new { action = "task_create", title = "Too late", fill_by = "2020-01-01" });
|
||||
|
||||
result.ErrorMessage!.ShouldContain("lies in the past");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task An_empty_deadline_turns_fill_work_back_into_regular_work()
|
||||
{
|
||||
var id = IdOf(await Run(new { action = "task_create", title = "Rewrite all 400 listings", assignee = "@lead", fill_by = InAWeek }));
|
||||
|
||||
(await Run(new { action = "task_update", id, fill_by = "" })).Success.ShouldBeTrue();
|
||||
|
||||
(await _repo.GetAsync(id, default))!.IsFillWork.ShouldBeFalse();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user