- 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:
@@ -63,6 +63,12 @@ public sealed class AppHost : IAsyncDisposable
|
||||
|
||||
public TaskScanner? Scanner { get; private init; }
|
||||
public StagingService? Staging { get; private init; }
|
||||
|
||||
/// <summary>
|
||||
/// Das Taskboard. Null, wenn den Agenten dieser Instanz ein gemeinsamer
|
||||
/// Arbeitsbereich fehlt — dann gibt es keinen Ort, an dem Aufgaben lägen.
|
||||
/// </summary>
|
||||
public TaskboardService? Board { get; private init; }
|
||||
public SqliteUsageRepository? Usage { get; private init; }
|
||||
public TelegramClientManager? TelegramClient { get; private init; }
|
||||
public OpenRouterStatusService? Status { get; private init; }
|
||||
@@ -98,6 +104,68 @@ public sealed class AppHost : IAsyncDisposable
|
||||
/// </summary>
|
||||
public LicenseWatch? LicenseWatch { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Meldet, dass die Instanzkonfiguration geschrieben wurde.
|
||||
///
|
||||
/// <para>Es gibt dieses Ereignis erst, seit es zwei Oberflächen gibt.
|
||||
/// <see cref="Instance"/> ist ein veränderliches Objekt, das beide teilen — die
|
||||
/// Ansichtsmodelle lesen es aber einmal beim Aufbau in ihre Listen. Ohne eine
|
||||
/// Meldung erschiene ein in der Weboberfläche angelegter Agent im laufenden Fenster
|
||||
/// nicht, und umgekehrt genauso.</para>
|
||||
///
|
||||
/// <para><b>Kommt auf dem Faden des Verursachers</b>, nicht auf dem der Oberfläche.
|
||||
/// Wer eine <c>ObservableCollection</c> daran hängt, geht über den Dispatcher.</para>
|
||||
/// </summary>
|
||||
public event Action<InstanceChange>? InstanceChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Schreibt die Instanzkonfiguration und sagt Bescheid.
|
||||
///
|
||||
/// <para>Der einzige Weg, auf dem <see cref="Instance"/> gespeichert werden soll.
|
||||
/// <c>Directories.SaveInstanceConfig</c> von Hand zu rufen schreibt zwar dieselbe
|
||||
/// Datei, lässt aber die andere Oberfläche in Unkenntnis.</para>
|
||||
/// </summary>
|
||||
public void SaveInstance(InstanceChange change)
|
||||
{
|
||||
Directories.SaveInstanceConfig(InstancePath, Instance);
|
||||
InstanceChanged?.Invoke(change);
|
||||
}
|
||||
|
||||
// ─── Verwaltung ───
|
||||
//
|
||||
// Die Regeln zum Anlegen, Ändern und Ausführen — bis zum 2026-09-06 in den
|
||||
// Avalonia-Ansichtsmodellen, jetzt hier. Der Grund ist die zweite Oberfläche: Was
|
||||
// dort stand, hätte sie ein zweites Mal schreiben müssen.
|
||||
//
|
||||
// Je Aufbau genau eine Ausprägung, und deshalb hier und nicht beim Aufrufer: Der
|
||||
// RunCoordinator führt den Verlauf und merkt sich die letzten Handläufe. Zwei
|
||||
// Ausprägungen hiessen zwei Verläufe, von denen jede Oberfläche einen anderen zeigt.
|
||||
|
||||
private readonly Lazy<AgentAdministration> _agentAdmin;
|
||||
private readonly Lazy<ServiceAdministration> _serviceAdmin;
|
||||
private readonly Lazy<JobAdministration> _jobAdmin;
|
||||
private readonly Lazy<RunCoordinator> _runs;
|
||||
|
||||
public AppHost()
|
||||
{
|
||||
_agentAdmin = new Lazy<AgentAdministration>(() => new AgentAdministration(this));
|
||||
_serviceAdmin = new Lazy<ServiceAdministration>(() => new ServiceAdministration(this));
|
||||
_jobAdmin = new Lazy<JobAdministration>(() => new JobAdministration(this));
|
||||
_runs = new Lazy<RunCoordinator>(() => new RunCoordinator(this));
|
||||
}
|
||||
|
||||
/// <summary>Agenten anlegen, entfernen, Werkzeuge zulassen und einstellen.</summary>
|
||||
public AgentAdministration AgentAdmin => _agentAdmin.Value;
|
||||
|
||||
/// <summary>Dienste der Instanz anlegen, entfernen, umschalten.</summary>
|
||||
public ServiceAdministration ServiceAdmin => _serviceAdmin.Value;
|
||||
|
||||
/// <summary>Geplante Läufe anlegen, ändern, entfernen, abschalten.</summary>
|
||||
public JobAdministration JobAdmin => _jobAdmin.Value;
|
||||
|
||||
/// <summary>„Jetzt ausführen" samt Verlauf.</summary>
|
||||
public RunCoordinator Runs => _runs.Value;
|
||||
|
||||
/// <summary>Was der Aufrufer beim Start erfragen muss.</summary>
|
||||
public sealed class Callbacks
|
||||
{
|
||||
@@ -362,7 +430,7 @@ public sealed class AppHost : IAsyncDisposable
|
||||
|
||||
engine.LoadPersistedChats();
|
||||
|
||||
var (scanner, staging) = BuildTaskboard(
|
||||
var (scanner, staging, board) = BuildTaskboard(
|
||||
instance, taskRepository, engine, tools, stateStore, audit,
|
||||
stagingRepository, loggerFactory, logger);
|
||||
|
||||
@@ -378,6 +446,7 @@ public sealed class AppHost : IAsyncDisposable
|
||||
Engine = engine,
|
||||
Scanner = scanner,
|
||||
Staging = staging,
|
||||
Board = board,
|
||||
Usage = usage,
|
||||
TelegramClient = telegram,
|
||||
Status = new OpenRouterStatusService(instance.OpenRouterApiKey)
|
||||
@@ -394,7 +463,7 @@ public sealed class AppHost : IAsyncDisposable
|
||||
/// Taskboard und Scanner. Der Scanner ist der einzige periodische Treiber (A1) —
|
||||
/// geplante Agentenläufe wie Tool-Job-Polls sind Tasks.
|
||||
/// </summary>
|
||||
private static (TaskScanner?, StagingService?) BuildTaskboard(
|
||||
private static (TaskScanner?, StagingService?, TaskboardService?) BuildTaskboard(
|
||||
InstanceConfig instance,
|
||||
SqliteTaskRepository taskRepository,
|
||||
AgentEngine engine,
|
||||
@@ -410,7 +479,7 @@ public sealed class AppHost : IAsyncDisposable
|
||||
.FirstOrDefault(p => !string.IsNullOrWhiteSpace(p));
|
||||
|
||||
if (string.IsNullOrWhiteSpace(sharedWorkspace))
|
||||
return (null, null);
|
||||
return (null, null, null);
|
||||
|
||||
var board = new TaskboardService(taskRepository, Path.Combine(sharedWorkspace, "tasks"));
|
||||
|
||||
@@ -451,7 +520,7 @@ public sealed class AppHost : IAsyncDisposable
|
||||
}
|
||||
});
|
||||
|
||||
return (scanner, staging);
|
||||
return (scanner, staging, board);
|
||||
}
|
||||
|
||||
private async Task ConnectTelegramAsync(Callbacks callbacks, ILogger logger)
|
||||
|
||||
Reference in New Issue
Block a user