#643: Entscheidungen wecken den Agenten nur noch bei Ablehnung oder gescheiterter Ausfuehrung, gesammelt in einer Aufgabe je Agent (5 min Ruhefenster), frisch per @new:agent statt im fortgefuehrten Chatverlauf. Eine einmalige Aufgabe, deren Lauf wegen Budget oder Herunterfahren gar nicht stattfand, bleibt faellig (Wiedervorlage nach 30 min) statt mit gesetztem Marker liegenzubleiben. #641: Rechte stehen in der Werkzeugkonfiguration des Agenten (erlaubt/verboten). Erlaubtes laeuft, Nicht-Erlaubtes wird einzeln vorgelegt statt verweigert, Verbotenes gar nicht erst. "Gleichartiges kuenftig erlauben" erteilt eine widerrufbare Dauerfreigabe je Agent/Werkzeug/Operation; nach drei gleichartigen Freigaben schlaegt die Oberflaeche sie vor. Aenderungen an Agenten bleiben ausnahmslos freigabepflichtig. Database: Operation ist das SQL-Schluesselwort; die eigene Zugriffspruefung greift nur noch ohne Freigabestelle (AgentToolContext.FreigabeGeprueft). WebUI: Rechte-Tabelle je Werkzeug, Dauerfreigaben mit Widerruf. Version 0.1.11. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -226,6 +226,40 @@ public sealed class TaskScannerTests : IDisposable
|
||||
TaskItemStatus.Blocked, "nicht alle Blocker sind erledigt");
|
||||
}
|
||||
|
||||
// ─── #643: Nicht gelaufene Aufgaben ───
|
||||
|
||||
[Fact]
|
||||
public async Task Eine_einmalige_Aufgabe_ohne_Budget_bleibt_faellig_statt_liegenzubleiben()
|
||||
{
|
||||
await _repo.UpsertAsync(new TaskItem { Id = "t-einmal", Title = "einmal", Assignee = "@new:a", Status = TaskItemStatus.Todo }, default);
|
||||
var dispatcher = new FakeDispatcher { Ergebnis = TaskLauf.NichtGelaufen };
|
||||
var scanner = Scanner(dispatcher);
|
||||
|
||||
(await scanner.ScanOnceAsync(default)).ShouldBe(1);
|
||||
|
||||
var task = (await _repo.GetAsync("t-einmal", default))!;
|
||||
task.Status.ShouldBe(TaskItemStatus.Todo);
|
||||
task.LastOccurrence.ShouldBeNull("der Termin gilt als nicht verbraucht");
|
||||
|
||||
(await scanner.ScanOnceAsync(default)).ShouldBe(0, "Wiedervorlage erst nach der Pause, nicht im Minutentakt");
|
||||
|
||||
_clock.Advance(TaskScanner.Wiedervorlage + TimeSpan.FromMinutes(1));
|
||||
dispatcher.Ergebnis = TaskLauf.Erledigt;
|
||||
(await scanner.ScanOnceAsync(default)).ShouldBe(1);
|
||||
(await _repo.GetAsync("t-einmal", default))!.Status.ShouldBe(TaskItemStatus.Done);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Eine_gescheiterte_einmalige_Aufgabe_bleibt_verbraucht()
|
||||
{
|
||||
await _repo.UpsertAsync(new TaskItem { Id = "t-kaputt", Title = "kaputt", Assignee = "@new:a", Status = TaskItemStatus.Todo }, default);
|
||||
var scanner = Scanner(new FakeDispatcher(succeed: false));
|
||||
|
||||
await scanner.ScanOnceAsync(default);
|
||||
|
||||
(await _repo.GetAsync("t-kaputt", default))!.LastOccurrence.ShouldNotBeNull("kein Retry-Sturm nach echtem Fehlschlag");
|
||||
}
|
||||
|
||||
// ─── Attrappen ───
|
||||
|
||||
private sealed class FakeDispatcher(bool succeed = true) : ITaskDispatcher
|
||||
@@ -233,11 +267,13 @@ public sealed class TaskScannerTests : IDisposable
|
||||
private readonly Lock _lock = new();
|
||||
public List<string> Dispatched { get; } = [];
|
||||
|
||||
public Task<bool> DispatchAsync(TaskItem task, CancellationToken ct)
|
||||
public TaskLauf? Ergebnis { get; set; }
|
||||
|
||||
public Task<TaskLauf> DispatchAsync(TaskItem task, CancellationToken ct)
|
||||
{
|
||||
lock (_lock)
|
||||
Dispatched.Add(task.Id);
|
||||
return Task.FromResult(succeed);
|
||||
return Task.FromResult(Ergebnis ?? (succeed ? TaskLauf.Erledigt : TaskLauf.Gescheitert));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user