Weboberflaeche hinzugefuegt und Fachlogik nach App herausgeloest (#493, #494)

- ClawdDotNet.Web: Kestrel im Prozess der Anwendung, Token-Absicherung (TokenGate mit FixedTimeEquals), Endpunkte unter /api, Ereignisstrom unter /events (Server-Sent Events) und fortlaufendes Log-Streaming.

- Weboberflaeche (wwwroot/): Vollstaendiges Single-Page-Frontend mit nativem HTML5, CSS3 und ES-Modulen ohne Buildschritt (strikt textContent, #-Routing, sofortiger Login-Sprung bei fehlendem Token, Light/Dark-Theme, lokale Barlow-Schriften).

- ClawdDotNet.App: Fachlogik aus den Avalonia-ViewModels in Dienste herausgeloest (AgentAdministration, JobAdministration, ServiceAdministration, RunCoordinator) und ueber InstanceChange synchronisiert.

- Tests: ClawdDotNet.Web.Tests und ClawdDotNet.App.Tests hinzugefuegt, alle 684 Tests erfolgreich.
This commit is contained in:
Richard
2026-09-07 21:56:02 +02:00
parent cffd6f4b77
commit 1e9cb789c8
64 changed files with 10543 additions and 554 deletions
@@ -360,6 +360,45 @@ public sealed class InstanceDirectoryManager
AtomicFile.WriteAllText(Path.Combine(agentDir, "Soul.md"), soul);
}
/// <summary>
/// Nimmt einen Agenten aus dem Betrieb, ohne seine Arbeit wegzuwerfen.
///
/// <para><b>Warum umbenennen und nicht löschen.</b> Geladen werden Agenten aus den
/// Ordnern <c>Agents/Agent-*</c> — was nicht so heißt, wird nicht geladen. Ein
/// Ordner <c>Entfernt-…</c> ist damit fachlich weg und liegt trotzdem noch da. Im
/// Arbeitsordner eines Agenten steht seine Arbeit, und ein Klick in einer Liste ist
/// keine Entscheidung, sie wegzuwerfen. Wer wirklich löschen will, nimmt
/// <see cref="RemoveAgent"/> — oder den Dateimanager.</para>
///
/// <para>Der Zeitstempel im neuen Namen ist kein Schmuck: Ohne ihn scheitert das
/// zweite Entfernen eines gleichnamigen Agenten daran, dass es das Ziel schon gibt.</para>
/// </summary>
/// <returns>Der neue Pfad, oder <c>null</c>, wenn es den Ordner gar nicht gab.</returns>
public string? ArchiveAgent(string instanceDir, string agentFolderName)
{
var agentDir = Path.Combine(instanceDir, "Agents", agentFolderName);
if (!Directory.Exists(agentDir)) return null;
var bare = agentFolderName.StartsWith("Agent-", StringComparison.Ordinal)
? agentFolderName["Agent-".Length..]
: agentFolderName;
var stamp = DateTime.Now.ToString("yyyyMMdd-HHmmss");
var archived = Path.Combine(instanceDir, "Agents", $"Entfernt-{bare}-{stamp}");
Directory.Move(agentDir, archived);
var agentListPath = Path.Combine(instanceDir, "Agents", "AgentList.json");
if (File.Exists(agentListPath))
{
var list = LoadJson<AgentListFile>(agentListPath) ?? new AgentListFile();
list.Agents.RemoveAll(a => a.FolderName == agentFolderName);
SaveJson(agentListPath, list);
}
return archived;
}
public void RemoveAgent(string instanceDir, string agentFolderName)
{
var agentDir = Path.Combine(instanceDir, "Agents", agentFolderName);