feat(ui): complete Avalonia UI port with 7 main pages, tool settings & top MenuBar
This commit is contained in:
@@ -0,0 +1,233 @@
|
||||
namespace ClawdDotNet.Core.Tasks;
|
||||
|
||||
/// <summary>
|
||||
/// Lebenszyklus einer Aufgabe. Als Zeichenkette in der DB und im Frontmatter abgelegt,
|
||||
/// damit beide Seiten dieselbe, menschenlesbare Form teilen.
|
||||
/// </summary>
|
||||
public enum TaskItemStatus
|
||||
{
|
||||
/// <summary>Angelegt, aber noch nicht zur Ausführung freigegeben.</summary>
|
||||
Backlog,
|
||||
|
||||
/// <summary>Bereit; der Scanner darf sie beim nächsten fälligen Termin nehmen.</summary>
|
||||
Todo,
|
||||
|
||||
/// <summary>Ein Lauf hat sie beansprucht und arbeitet daran.</summary>
|
||||
InProgress,
|
||||
|
||||
/// <summary>Erledigt, wartet aber auf Review (<c>require_approval</c>).</summary>
|
||||
InReview,
|
||||
|
||||
/// <summary>Abgeschlossen.</summary>
|
||||
Done,
|
||||
|
||||
/// <summary>Verworfen, ohne erledigt zu sein.</summary>
|
||||
Canceled,
|
||||
|
||||
/// <summary>Wartet auf einen offenen Blocker (<c>blocked_by</c>) oder auf eine Meldung.</summary>
|
||||
Blocked,
|
||||
|
||||
/// <summary>Aus dem Board genommen (Datei gelöscht, aufgeräumt) — bleibt für die Historie.</summary>
|
||||
Archived
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Art der Aufgabe. Ein Mensch ist einfach ein Assignee, kein Sonderpfad — <c>approval</c>
|
||||
/// und <c>human_input</c> unterscheiden sich nur darin, dass ihr Ergebnis von einem
|
||||
/// Menschen statt von einem Modell kommt.
|
||||
/// </summary>
|
||||
public enum TaskItemType
|
||||
{
|
||||
Work,
|
||||
Approval,
|
||||
HumanInput,
|
||||
|
||||
/// <summary>
|
||||
/// Ein wiederkehrender Poll: Der Scanner tickt beim fälligen Termin einen
|
||||
/// <c>IToolJobProvider</c> (siehe <see cref="TaskItem.ToolName"/>/<see cref="TaskItem.JobTypeId"/>)
|
||||
/// und weckt den Agenten nur, wenn der Tick etwas meldet. Ersetzt den früheren
|
||||
/// ToolJobScheduler — alles Periodische ist jetzt ein Task.
|
||||
/// </summary>
|
||||
ToolJob
|
||||
}
|
||||
|
||||
/// <summary>Wie ein Termin zu verstehen ist.</summary>
|
||||
public enum TaskWhenKind
|
||||
{
|
||||
/// <summary>Einmaliger Zeitpunkt (ISO 8601).</summary>
|
||||
At,
|
||||
|
||||
/// <summary>Wiederkehrendes Intervall (z. B. <c>30m</c>, <c>2h</c>).</summary>
|
||||
Every,
|
||||
|
||||
/// <summary>5-Felder-Cron-Ausdruck.</summary>
|
||||
Cron
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ein Termin. Die Zeitzone ist Pflicht — das ist die Antwort auf B7 (Cron lief bisher
|
||||
/// zonen-blind in Lokalzeit). Ohne Zone lässt sich <c>0 7 * * *</c> nicht eindeutig
|
||||
/// deuten.
|
||||
/// </summary>
|
||||
public sealed record TaskWhen
|
||||
{
|
||||
public TaskWhenKind Kind { get; init; }
|
||||
public string Value { get; init; } = "";
|
||||
|
||||
/// <summary>IANA-Zeitzone, etwa <c>Europe/Berlin</c>.</summary>
|
||||
public string TimeZone { get; init; } = "";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wer eine Aufgabe ausführt. Löst das implizite <c>UseChatContext</c>-Flag (T7) durch
|
||||
/// eine explizite Angabe am Auftrag ab.
|
||||
/// </summary>
|
||||
public enum TaskAssigneeKind
|
||||
{
|
||||
/// <summary><c>@new</c> — frischer Lauf ohne Historie (<c>RunAsync</c>).</summary>
|
||||
New,
|
||||
|
||||
/// <summary><c>@<agentId></c> — bestehender Agent mit seinem Kontext (<c>ChatAsync</c>).</summary>
|
||||
Agent,
|
||||
|
||||
/// <summary><c>@human</c> — wartet auf einen Menschen; kein Modell-Lauf.</summary>
|
||||
Human
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deutet den <c>assignee</c>-String, ohne ihn selbst zu speichern. Formen:
|
||||
/// <c>@human</c>, <c>@<agentId></c> (bestehender Kontext), <c>@new</c> bzw.
|
||||
/// <c>@new:<agentId></c> (frischer Lauf ohne Historie). Bei bloßem <c>@new</c> ohne
|
||||
/// Agent bleibt die Ziel-Id offen — dann muss die Instanz genau einen Agenten haben.
|
||||
/// </summary>
|
||||
public static class TaskAssignee
|
||||
{
|
||||
public const string New = "@new";
|
||||
public const string Human = "@human";
|
||||
|
||||
public static TaskAssigneeKind KindOf(string? assignee)
|
||||
{
|
||||
var value = assignee?.Trim();
|
||||
if (string.Equals(value, Human, StringComparison.OrdinalIgnoreCase))
|
||||
return TaskAssigneeKind.Human;
|
||||
if (string.Equals(value, New, StringComparison.OrdinalIgnoreCase)
|
||||
|| value?.StartsWith("@new:", StringComparison.OrdinalIgnoreCase) == true)
|
||||
return TaskAssigneeKind.New;
|
||||
return TaskAssigneeKind.Agent;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Die reine Agent-Id. Für <c>@<agent></c> der Teil hinter dem <c>@</c>, für
|
||||
/// <c>@new:<agent></c> der Teil hinter dem Doppelpunkt. Leer bei <c>@human</c>
|
||||
/// und bloßem <c>@new</c>.
|
||||
/// </summary>
|
||||
public static string AgentId(string? assignee)
|
||||
{
|
||||
var value = assignee?.Trim() ?? "";
|
||||
switch (KindOf(value))
|
||||
{
|
||||
case TaskAssigneeKind.Agent:
|
||||
return value.StartsWith('@') ? value[1..] : value;
|
||||
case TaskAssigneeKind.New:
|
||||
var colon = value.IndexOf(':');
|
||||
return colon >= 0 ? value[(colon + 1)..].Trim() : "";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Eine Aufgabe — die Spiegelung der Frontmatter-Definition zusammen mit dem
|
||||
/// Ausführungszustand. Die Definition ist in der Datei die Wahrheit, der
|
||||
/// Ausführungszustand in der DB (siehe Taskboard-Konzept). Diese Zeile hält beides,
|
||||
/// damit der Scanner ohne Dateizugriff entscheiden kann.
|
||||
/// </summary>
|
||||
public sealed record TaskItem
|
||||
{
|
||||
// ─── Definition (aus dem Frontmatter gespiegelt) ───
|
||||
|
||||
/// <summary>Stabile Identität. Überlebt das Umbenennen der Datei.</summary>
|
||||
public string Id { get; init; } = "";
|
||||
|
||||
public string Title { get; init; } = "";
|
||||
public TaskItemStatus Status { get; init; } = TaskItemStatus.Todo;
|
||||
public TaskItemType Type { get; init; } = TaskItemType.Work;
|
||||
|
||||
/// <summary>1 (niedrig) .. 5 (hoch).</summary>
|
||||
public int Priority { get; init; } = 3;
|
||||
|
||||
public string Assignee { get; init; } = TaskAssignee.Human;
|
||||
|
||||
/// <summary>Termin; <c>null</c> = einmalige, sofort fällige Aufgabe.</summary>
|
||||
public TaskWhen? When { get; init; }
|
||||
|
||||
/// <summary>Gilt erst nach Review als <see cref="TaskItemStatus.Done"/>.</summary>
|
||||
public bool RequireApproval { get; init; }
|
||||
|
||||
/// <summary>Abnahmekriterien, gegen die das Ergebnis geprüft wird.</summary>
|
||||
public string Acceptance { get; init; } = "";
|
||||
|
||||
/// <summary>Ids der Aufgaben, die erst erledigt sein müssen.</summary>
|
||||
public IReadOnlyList<string> BlockedBy { get; init; } = [];
|
||||
|
||||
/// <summary>C1: Termin nur auslösen, wenn der Markt offen ist.</summary>
|
||||
public bool OnlyWhenMarketOpen { get; init; }
|
||||
|
||||
/// <summary>Auftragsbeschreibung — geht als Aufgabenstellung an den Agenten.</summary>
|
||||
public string Body { get; init; } = "";
|
||||
|
||||
/// <summary>Dateiname relativ zu <c>tasks/</c>. Dient dem Rückschreiben.</summary>
|
||||
public string FileName { get; init; } = "";
|
||||
|
||||
// ─── Nur für Typ tool_job ───
|
||||
|
||||
/// <summary>Das zu tickende Tool (z. B. <c>Telegram</c>) — nur bei <c>tool_job</c>.</summary>
|
||||
public string ToolName { get; init; } = "";
|
||||
|
||||
/// <summary>Die Job-Art des Tools (z. B. <c>telegram_poll</c>) — nur bei <c>tool_job</c>.</summary>
|
||||
public string JobTypeId { get; init; } = "";
|
||||
|
||||
// ─── Ausführungszustand (nur das Board schreibt) ───
|
||||
|
||||
/// <summary>
|
||||
/// Occurrence-Key des zuletzt behandelten Termins (sortierbares ISO-UTC). Ein neuer
|
||||
/// Termin gilt nur als fällig, wenn er hierüber liegt — so wird ein Termin höchstens
|
||||
/// einmal ausgelöst.
|
||||
/// </summary>
|
||||
public string? LastOccurrence { get; init; }
|
||||
|
||||
/// <summary>Gesetzt, solange ein Lauf die Aufgabe beansprucht.</summary>
|
||||
public string? ClaimToken { get; init; }
|
||||
|
||||
public DateTime? ClaimedAt { get; init; }
|
||||
|
||||
public DateTime CreatedAt { get; init; }
|
||||
public DateTime UpdatedAt { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Wiederkehrend? Solche Tasks kehren nach dem Feuern auf <c>todo</c> zurück statt auf
|
||||
/// <c>done</c> — sonst würde ein Cron-Task nur ein einziges Mal laufen. Ein
|
||||
/// <c>tool_job</c> ist immer ein Poll und damit wiederkehrend.
|
||||
/// </summary>
|
||||
public bool IsRecurring
|
||||
=> Type == TaskItemType.ToolJob
|
||||
|| When is { Kind: TaskWhenKind.Cron or TaskWhenKind.Every };
|
||||
}
|
||||
|
||||
/// <summary>Suchkriterien für <see cref="ITaskRepository.ListAsync"/>.</summary>
|
||||
public sealed record TaskQuery
|
||||
{
|
||||
public TaskItemStatus? Status { get; init; }
|
||||
|
||||
/// <summary>Filtert auf einen Assignee (roher String, z. B. <c>@crawler</c>).</summary>
|
||||
public string? Assignee { get; init; }
|
||||
|
||||
/// <summary>Freitext über Titel und Beschreibung.</summary>
|
||||
public string? Search { get; init; }
|
||||
|
||||
/// <summary>Archivierte werden standardmäßig ausgeblendet.</summary>
|
||||
public bool IncludeArchived { get; init; }
|
||||
|
||||
public int Limit { get; init; } = 50;
|
||||
}
|
||||
Reference in New Issue
Block a user