# ClawdDotNet – Prompt-Anhang: WinForms & WebView2 Integration

Dieser Abschnitt ergänzt den Haupt-Entwicklungsprompt und behandelt ausschließlich
die WinForms-UI-Schicht mit WebView2. Er baut auf den bereits definierten Core-Typen
(AgentConfig, InstanceConfig, AgentEngine, IAgentTool etc.) auf.

---

## Übersicht: Zwei WebView2-Kontexte

Es gibt genau zwei WebView2-Kontexte im Host. Sie sind vollständig getrennt
und haben unterschiedliche Sicherheits-Scopes:

| Kontext | Control | Form | Zweck |
|---|---|---|---|
| `webView_chat` | `WebView2` in `frm_main` | Hauptfenster | Agentenübersicht + Auswahl + Chat mit einem Agenten |
| `webView_chat2` | `WebView2` in `frm_chat` | Einzelchat-Fenster | Chat mit genau einem Agenten, mehrfach öffenbar |

`frm_chat` ist bewusst ein eigenständiges, nicht-modales Fenster — es kann mehrfach
instanziiert werden, sodass der Nutzer mehrere Agenten-Chats nebeneinander
auf dem Bildschirm überwachen kann. Jede `frm_chat`-Instanz kennt genau einen `AgentId`.

---

## Sicherheitsarchitektur: Physische Trennung der WebRoots

### Zwei Hostnamen, zwei Quellen — niemals überlappend

```
Assembly (Embedded Resources)          Disk (vom FileRW-Tool beschreibbar)
──────────────────────────────         ──────────────────────────────────
Host/EmbeddedUI/                       data/{instanceId}/
  overview.html      ← frm_main         wwwroot/           ← Kestrel-Root
  overview.css         webView_chat       index.html
  overview.js                            styles/
  chat.html          ← frm_chat          data/
  chat.css             webView_chat2      assets/
  bridge.js
```

**Kernregel:** `EmbeddedUI/` existiert nur als Assembly-Resource.
Sie hat keinen Dateisystempfad, auf den ein Tool zeigen könnte.
Kein `FileRW`-Tool bekommt jemals einen `rootPath`, der auf `EmbeddedUI/` zeigt.

### WebView2 Virtual Host Mapping

```csharp
// Beide Mappings werden in InitWebViewAsync() jeder Form gesetzt:

// Intern – aus Assembly-Stream (temporär extrahiert beim Start)
webView.CoreWebView2.SetVirtualHostNameToFolderMapping(
    "ui.clwd.internal",
    EmbeddedUiManager.GetExtractedPath(),   // einmalig beim App-Start nach temp/
    CoreWebView2HostResourceAccessKind.DenyCors);

// Extern – Agent-generierte Inhalte auf Disk
webView.CoreWebView2.SetVirtualHostNameToFolderMapping(
    "dash.clwd.local",
    _instanceConfig.WwwRootPath,
    CoreWebView2HostResourceAccessKind.Allow);
```

`ui.clwd.internal` → nur lesbar, kein Cross-Origin-Zugriff von außen
`dash.clwd.local` → lesbar für den WebView, schreibbar nur durch FileRW-Tool

### Startvalidierung (Pflicht, einmalig in Program.cs)

```csharp
// Sicherheitscheck beim App-Start – Exception wenn verletzt:
var wwwAbs  = Path.GetFullPath(instanceConfig.WwwRootPath);
var uiAbs   = Path.GetFullPath(EmbeddedUiManager.GetExtractedPath());

if (wwwAbs.StartsWith(uiAbs) || uiAbs.StartsWith(wwwAbs))
    throw new InvalidOperationException(
        "SECURITY: WwwRootPath and EmbeddedUI path must never overlap.");
```

---

## EmbeddedUiManager

**Datei: `Host/UI/EmbeddedUiManager.cs`**

Aufgabe: HTML/CSS/JS-Dateien aus den Assembly Embedded Resources einmalig beim
Programmstart in einen temporären Ordner extrahieren. WebView2 kann nur auf
Dateisystempfade mappen, nicht direkt auf Streams.

```csharp
namespace ClawdDotNet.Host.UI;

public static class EmbeddedUiManager
{
    private static string? _extractedPath;

    // Einmalig beim App-Start aufrufen (vor Application.Run)
    public static string ExtractToTemp()
    {
        if (_extractedPath != null) return _extractedPath;

        var tempDir = Path.Combine(Path.GetTempPath(), "ClawdDotNet_UI",
            Assembly.GetExecutingAssembly()
                    .GetName().Version?.ToString() ?? "dev");

        Directory.CreateDirectory(tempDir);

        var asm = Assembly.GetExecutingAssembly();
        // Alle Embedded Resources im Namespace "ClawdDotNet.Host.EmbeddedUI"
        foreach (var name in asm.GetManifestResourceNames()
                                .Where(n => n.Contains(".EmbeddedUI.")))
        {
            // "ClawdDotNet.Host.EmbeddedUI.chat.css" → "chat.css"
            var fileName = name.Split(".EmbeddedUI.").Last();
            var dest = Path.Combine(tempDir, fileName);

            using var stream = asm.GetManifestResourceStream(name)!;
            using var file   = File.Create(dest);
            stream.CopyTo(file);
        }

        _extractedPath = tempDir;
        return tempDir;
    }

    public static string GetExtractedPath()
        => _extractedPath ?? throw new InvalidOperationException(
               "EmbeddedUiManager.ExtractToTemp() must be called first.");
}
```

Embedded Resources werden in der `.csproj` so eingebunden:
```xml
<ItemGroup>
  <EmbeddedResource Include="EmbeddedUI\**\*" />
</ItemGroup>
```

---

## C#–JavaScript Bridge

**Datei: `Host/UI/WebViewBridge.cs`**

Eine Bridge-Instanz pro WebView2-Control. Kapselt die gesamte
bidirektionale Kommunikation. Keine rohen `ExecuteScriptAsync`-Aufrufe
außerhalb dieser Klasse.

```csharp
namespace ClawdDotNet.Host.UI;

public sealed class WebViewBridge : IDisposable
{
    private readonly Microsoft.Web.WebView2.WinForms.WebView2 _wv;
    private readonly ILogger<WebViewBridge> _logger;

    // Eingehende Nachrichten vom Browser → C#
    public event Action<BridgeMessage>? MessageReceived;

    public WebViewBridge(
        Microsoft.Web.WebView2.WinForms.WebView2 webView,
        ILogger<WebViewBridge> logger)
    {
        _wv = webView;
        _logger = logger;
        _wv.CoreWebView2.WebMessageReceived += OnWebMessageReceived;
    }

    // C# → Browser: typisiert, immer als JSON
    public async Task SendAsync(BridgeMessage message, CancellationToken ct = default)
    {
        var json = JsonSerializer.Serialize(message, BridgeJsonOptions.Default);
        // Muss auf dem UI-Thread ausgeführt werden
        await _wv.InvokeAsync(async () =>
            await _wv.CoreWebView2.ExecuteScriptAsync(
                $"window.__bridge?.receive({json})"));
    }

    private void OnWebMessageReceived(object? sender,
        CoreWebView2WebMessageReceivedEventArgs e)
    {
        try
        {
            var msg = JsonSerializer.Deserialize<BridgeMessage>(
                e.WebMessageAsJson, BridgeJsonOptions.Default);
            if (msg != null) MessageReceived?.Invoke(msg);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Bridge: failed to deserialize incoming message");
        }
    }

    public void Dispose()
        => _wv.CoreWebView2.WebMessageReceived -= OnWebMessageReceived;
}
```

### BridgeMessage – Nachrichtenformat

**Datei: `Host/UI/BridgeMessage.cs`**

Alle Nachrichten in beide Richtungen verwenden diesen Typ.
Das `Type`-Feld bestimmt, was in `Payload` steckt.

```csharp
namespace ClawdDotNet.Host.UI;

public sealed record BridgeMessage(
    string Type,                        // siehe Konstanten unten
    string? AgentId    = null,
    string? Content    = null,          // Chat-Text, HTML-Snippet
    string? Status     = null,          // "running" | "idle" | "error"
    int?    StepCount  = null,
    int?    TokenCount = null,
    string? Error      = null,
    object? Extra      = null           // type-spezifische Zusatzdaten
);

// Typ-Konstanten (C# → Browser)
public static class BridgeTypes
{
    // frm_main: overview.html
    public const string AgentListUpdate  = "agent_list_update";  // Alle Agenten initial laden
    public const string AgentStatusUpdate = "agent_status";      // Statusänderung eines Agenten
    public const string SelectAgent      = "select_agent";       // Agenten im Chat auswählen

    // frm_main + frm_chat: chat.html
    public const string ChatMessage      = "chat_message";       // Neue Nachricht anzeigen
    public const string ChatTyping       = "chat_typing";        // Tipp-Indikator an/aus
    public const string ChatHistory      = "chat_history";       // Verlauf beim Öffnen laden
    public const string RunStarted      = "run_started";         // Agent-Run begann
    public const string RunFinished     = "run_finished";        // Agent-Run beendet

    // Browser → C# (eingehend)
    public const string UserMessage     = "user_message";        // Nutzer hat Enter gedrückt
    public const string OpenAgentChat   = "open_agent_chat";     // "Eigenes Fenster öffnen"
    public const string RunNow          = "run_now";             // Manueller Run-Trigger
    public const string AbortRun        = "abort_run";           // Run abbrechen
}
```

---

## frm_main – Hauptfenster

**Datei: `Host/Forms/frm_main.cs`**

`frm_main` enthält `webView_chat` (bereits angelegt). Dieses WebView zeigt
`overview.html`: eine Seitenleiste mit allen Agenten und einen Chat-Bereich
für den aktuell ausgewählten Agenten.

```csharp
namespace ClawdDotNet.Host.Forms;

public partial class frm_main : Form
{
    private readonly InstanceConfig      _instance;
    private readonly AgentEngine         _engine;
    private readonly AgentScheduler      _scheduler;
    private readonly ILogger<frm_main>   _logger;

    private WebViewBridge? _bridge;
    private string?        _selectedAgentId;

    // Offene Einzelchat-Fenster: AgentId → frm_chat
    private readonly Dictionary<string, frm_chat> _chatWindows = new();

    public frm_main(InstanceConfig instance, AgentEngine engine,
                    AgentScheduler scheduler, ILogger<frm_main> logger)
    {
        InitializeComponent();
        _instance  = instance;
        _engine    = engine;
        _scheduler = scheduler;
        _logger    = logger;
    }

    private async void frm_main_Load(object sender, EventArgs e)
    {
        await InitWebViewAsync();
        _scheduler.RunStatusChanged += OnRunStatusChanged; // Event aus Core
    }

    private async Task InitWebViewAsync()
    {
        await webView_chat.EnsureCoreWebView2Async();

        // Virtual Host Mappings
        webView_chat.CoreWebView2.SetVirtualHostNameToFolderMapping(
            "ui.clwd.internal",
            EmbeddedUiManager.GetExtractedPath(),
            CoreWebView2HostResourceAccessKind.DenyCors);

        webView_chat.CoreWebView2.SetVirtualHostNameToFolderMapping(
            "dash.clwd.local",
            _instance.WwwRootPath,
            CoreWebView2HostResourceAccessKind.Allow);

        _bridge = new WebViewBridge(webView_chat, /* logger */);
        _bridge.MessageReceived += OnBridgeMessage;

        webView_chat.CoreWebView2.Navigate(
            "https://ui.clwd.internal/overview.html");

        // Kurz warten bis DOM bereit, dann Agentenliste senden
        await Task.Delay(300);
        await PushAgentListAsync();
    }

    private async Task PushAgentListAsync()
    {
        // Alle AgentConfigs als Liste → overview.html baut die Sidebar auf
        var agents = _instance.Agents.Select(a => new
        {
            agentId     = a.AgentId,
            displayName = a.DisplayName,
            model       = a.Model,
            status      = _engine.GetStatus(a.AgentId)  // "idle"|"running"|"error"
        });

        await _bridge!.SendAsync(new BridgeMessage(
            Type:  BridgeTypes.AgentListUpdate,
            Extra: agents));
    }

    private async void OnBridgeMessage(BridgeMessage msg)
    {
        // Immer auf UI-Thread
        if (InvokeRequired) { Invoke(() => OnBridgeMessage(msg)); return; }

        switch (msg.Type)
        {
            case BridgeTypes.UserMessage:
                // Nutzer hat im Chat Enter gedrückt
                if (_selectedAgentId is null || msg.Content is null) break;
                await HandleUserMessageAsync(_selectedAgentId, msg.Content);
                break;

            case BridgeTypes.SelectAgent:
                // Agenten in der Sidebar angeklickt → Chat-Verlauf laden
                _selectedAgentId = msg.AgentId;
                await LoadChatHistoryAsync(msg.AgentId!);
                break;

            case BridgeTypes.OpenAgentChat:
                // "Eigenes Fenster" Button → frm_chat öffnen oder fokussieren
                OpenChatWindow(msg.AgentId!);
                break;

            case BridgeTypes.RunNow:
                _ = _engine.RunAsync(msg.AgentId!, CancellationToken.None);
                break;

            case BridgeTypes.AbortRun:
                _engine.Abort(msg.AgentId!);
                break;
        }
    }

    private void OpenChatWindow(string agentId)
    {
        if (_chatWindows.TryGetValue(agentId, out var existing)
            && !existing.IsDisposed)
        {
            existing.BringToFront();
            return;
        }

        var agentConfig = _instance.Agents.First(a => a.AgentId == agentId);
        var frm = new frm_chat(agentConfig, _engine, /* logger */);
        frm.FormClosed += (_, _) => _chatWindows.Remove(agentId);
        _chatWindows[agentId] = frm;
        frm.Show(this);   // nicht-modal, Elternfenster = frm_main
    }

    private void OnRunStatusChanged(string agentId, AgentRunStatus status)
    {
        // Vom Scheduler/Engine gefeuert – auf UI-Thread pushen
        this.InvokeAsync(async () =>
            await _bridge!.SendAsync(new BridgeMessage(
                Type:       BridgeTypes.AgentStatusUpdate,
                AgentId:    agentId,
                Status:     status.ToString().ToLower(),
                StepCount:  status.StepCount,
                TokenCount: status.TokensUsed)));
    }

    private async Task HandleUserMessageAsync(string agentId, string text)
    {
        // Eigene Nachricht sofort anzeigen
        await _bridge!.SendAsync(new BridgeMessage(
            Type:    BridgeTypes.ChatMessage,
            AgentId: agentId,
            Content: text,
            Extra:   new { role = "user", timestamp = DateTime.Now }));

        // Tipp-Indikator an
        await _bridge.SendAsync(new BridgeMessage(
            Type: BridgeTypes.ChatTyping, AgentId: agentId));

        // Chat-Run starten (non-blocking)
        _ = Task.Run(async () =>
        {
            var result = await _engine.ChatAsync(agentId, text, CancellationToken.None);

            await _bridge.SendAsync(new BridgeMessage(
                Type:    BridgeTypes.ChatMessage,
                AgentId: agentId,
                Content: result.FinalMessage,
                Extra:   new { role = "agent", timestamp = DateTime.Now }));
        });
    }

    private async Task LoadChatHistoryAsync(string agentId)
    {
        var history = await _engine.GetChatHistoryAsync(agentId);
        await _bridge!.SendAsync(new BridgeMessage(
            Type:    BridgeTypes.ChatHistory,
            AgentId: agentId,
            Extra:   history));
    }

    protected override void OnFormClosed(FormClosedEventArgs e)
    {
        _bridge?.Dispose();
        _scheduler.RunStatusChanged -= OnRunStatusChanged;
        base.OnFormClosed(e);
    }
}
```

---

## frm_chat – Einzelchat-Fenster

**Datei: `Host/Forms/frm_chat.cs`**

`frm_chat` enthält `webView_chat2` (bereits angelegt). Dieses Fenster zeigt
den Chat mit genau einem Agenten. Es kann beliebig oft gleichzeitig geöffnet
sein — jede Instanz ist vollständig unabhängig.

```csharp
namespace ClawdDotNet.Host.Forms;

public partial class frm_chat : Form
{
    private readonly AgentConfig       _agentConfig;
    private readonly AgentEngine       _engine;
    private readonly ILogger<frm_chat> _logger;

    private WebViewBridge? _bridge;

    public frm_chat(AgentConfig agentConfig, AgentEngine engine,
                    ILogger<frm_chat> logger)
    {
        InitializeComponent();
        _agentConfig = agentConfig;
        _engine      = engine;
        _logger      = logger;

        // Fenstertitel = Agent-Name
        Text = $"Chat – {agentConfig.DisplayName}";
    }

    private async void frm_chat_Load(object sender, EventArgs e)
        => await InitWebViewAsync();

    private async Task InitWebViewAsync()
    {
        await webView_chat2.EnsureCoreWebView2Async();

        // Identische Virtual Host Mappings wie frm_main
        webView_chat2.CoreWebView2.SetVirtualHostNameToFolderMapping(
            "ui.clwd.internal",
            EmbeddedUiManager.GetExtractedPath(),
            CoreWebView2HostResourceAccessKind.DenyCors);

        webView_chat2.CoreWebView2.SetVirtualHostNameToFolderMapping(
            "dash.clwd.local",
            // WwwRootPath kommt vom InstanceConfig über DI/Singleton
            ServiceLocator.Get<InstanceConfig>().WwwRootPath,
            CoreWebView2HostResourceAccessKind.Allow);

        _bridge = new WebViewBridge(webView_chat2, /* logger */);
        _bridge.MessageReceived += OnBridgeMessage;

        // chat.html lädt für einen bestimmten Agenten
        webView_chat2.CoreWebView2.Navigate(
            $"https://ui.clwd.internal/chat.html?agent={_agentConfig.AgentId}");

        await Task.Delay(300);

        // AgentInfo und Verlauf initial senden
        await _bridge.SendAsync(new BridgeMessage(
            Type:    BridgeTypes.AgentListUpdate,
            AgentId: _agentConfig.AgentId,
            Extra:   new { agents = new[] { new {
                agentId     = _agentConfig.AgentId,
                displayName = _agentConfig.DisplayName,
                model       = _agentConfig.Model
            }}}));

        await LoadChatHistoryAsync();
    }

    private async void OnBridgeMessage(BridgeMessage msg)
    {
        if (InvokeRequired) { Invoke(() => OnBridgeMessage(msg)); return; }

        switch (msg.Type)
        {
            case BridgeTypes.UserMessage:
                await HandleUserMessageAsync(msg.Content ?? "");
                break;

            case BridgeTypes.RunNow:
                _ = _engine.RunAsync(_agentConfig.AgentId, CancellationToken.None);
                break;

            case BridgeTypes.AbortRun:
                _engine.Abort(_agentConfig.AgentId);
                break;
        }
    }

    private async Task HandleUserMessageAsync(string text)
    {
        await _bridge!.SendAsync(new BridgeMessage(
            Type:    BridgeTypes.ChatMessage,
            AgentId: _agentConfig.AgentId,
            Content: text,
            Extra:   new { role = "user", timestamp = DateTime.Now }));

        await _bridge.SendAsync(new BridgeMessage(
            Type: BridgeTypes.ChatTyping, AgentId: _agentConfig.AgentId));

        _ = Task.Run(async () =>
        {
            var result = await _engine.ChatAsync(
                _agentConfig.AgentId, text, CancellationToken.None);

            await _bridge!.SendAsync(new BridgeMessage(
                Type:    BridgeTypes.ChatMessage,
                AgentId: _agentConfig.AgentId,
                Content: result.FinalMessage,
                Extra:   new { role = "agent", timestamp = DateTime.Now }));
        });
    }

    private async Task LoadChatHistoryAsync()
    {
        var history = await _engine.GetChatHistoryAsync(_agentConfig.AgentId);
        await _bridge!.SendAsync(new BridgeMessage(
            Type:    BridgeTypes.ChatHistory,
            AgentId: _agentConfig.AgentId,
            Extra:   history));
    }

    protected override void OnFormClosed(FormClosedEventArgs e)
    {
        _bridge?.Dispose();
        base.OnFormClosed(e);
    }
}
```

---

## Embedded HTML/JS/CSS – Dateistruktur

Alle Dateien liegen in `Host/EmbeddedUI/`. Build Action: `Embedded Resource`.

### overview.html (für webView_chat in frm_main)

Dieses HTML baut die komplette Ansicht aus dem Mockup auf:
- Linke Sidebar: Agentenliste (wird via Bridge befüllt)
- Rechter Bereich: Chat mit dem aktuell ausgewählten Agenten
- "Eigenes Fenster"-Button pro Agent → sendet `open_agent_chat`-Nachricht

Kommunikationsprotokoll (JavaScript-Seite):
```javascript
// bridge.js – wird von beiden HTML-Seiten eingebunden

window.__bridge = {
    // Eingehend von C#
    receive(msg) {
        document.dispatchEvent(
            new CustomEvent('bridge:' + msg.type, { detail: msg }));
    },
    // Ausgehend zu C#
    send(msg) {
        window.chrome.webview.postMessage(JSON.stringify(msg));
    }
};

// Beispiel: auf Agentenliste reagieren
document.addEventListener('bridge:agent_list_update', e => {
    renderSidebar(e.detail.extra.agents);
});

// Beispiel: Nachricht senden
function sendUserMessage(agentId, text) {
    window.__bridge.send({
        type:    'user_message',
        agentId: agentId,
        content: text
    });
}
```

### chat.html (für webView_chat2 in frm_chat)

Vereinfachte Version ohne Sidebar — nur der Chat-Bereich.
Liest den `?agent=`-URL-Parameter beim Laden und stellt sich
damit auf den entsprechenden Agenten ein.

```javascript
// chat.html – Init
const agentId = new URLSearchParams(location.search).get('agent');

document.addEventListener('bridge:chat_history', e => {
    if (e.detail.agentId !== agentId) return;
    renderHistory(e.detail.extra);
});

document.addEventListener('bridge:chat_message', e => {
    if (e.detail.agentId !== agentId) return;
    appendBubble(e.detail.extra.role, e.detail.content,
                 e.detail.extra.timestamp);
});

document.addEventListener('bridge:chat_typing', e => {
    if (e.detail.agentId !== agentId) return;
    showTypingIndicator();
});
```

---

## Program.cs – Startup-Reihenfolge

```csharp
// Host/Program.cs

[STAThread]
static async Task Main(string[] args)
{
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);

    // 1. Config laden (--config Argument oder default)
    var configPath  = GetConfigPath(args);
    var instance    = InstanceConfig.LoadFromFile(configPath);

    // 2. Sicherheitscheck: Pfade dürfen sich nicht überlappen
    var uiPath  = EmbeddedUiManager.ExtractToTemp();   // extrahiert EmbeddedUI
    var wwwPath = Path.GetFullPath(instance.WwwRootPath);
    if (wwwPath.StartsWith(uiPath) || uiPath.StartsWith(wwwPath))
        throw new InvalidOperationException(
            "SECURITY: WwwRootPath and EmbeddedUI path must never overlap.");

    // 3. DI-Container aufbauen
    var services = new ServiceCollection();
    services.AddSingleton(instance);
    services.AddSingleton<ToolRegistry>();
    services.AddSingleton<PermissionGate>();
    services.AddSingleton<AgentEngine>();
    services.AddSingleton<AgentScheduler>();
    services.AddSingleton<OpenRouterClient>();
    services.AddLogging(b => b.AddConsole());

    // 4. Tools registrieren (Host ist der einzige Ort, der Tool-Typen kennt)
    var provider = services.BuildServiceProvider();
    var registry = provider.GetRequiredService<ToolRegistry>();
    registry.Register(new DatabaseTool());
    registry.Register(new FileRwTool());
    registry.Register(new MailTool());

    // 5. Scheduler starten
    var scheduler = provider.GetRequiredService<AgentScheduler>();
    await scheduler.StartAsync(CancellationToken.None);

    // 6. WinForms starten
    var mainForm = provider.GetRequiredService<frm_main>();
    Application.Run(mainForm);

    // 7. Cleanup
    await scheduler.StopAsync(CancellationToken.None);
}
```

---

## Core-Erweiterungen für Chat-Support

Der `AgentEngine` im Core benötigt zwei zusätzliche Methoden für
den interaktiven Chat-Modus (ergänze Phase 1.7):

```csharp
// AgentEngine – zusätzliche Methoden

// Interaktiver Chat: eine Nutzer-Nachricht → Agent-Antwort
// Unterschied zum autonomen Run: kein Scheduler-Trigger,
// Verlauf wird an bestehende Konversation angehängt
Task<AgentRunResult> ChatAsync(string agentId, string userMessage, CancellationToken ct);

// Chat-Verlauf aus dem StateManager laden
// Rückgabe: Liste von { role, content, timestamp }
Task<IReadOnlyList<ChatEntry>> GetChatHistoryAsync(string agentId);

// Aktuellen Run-Status abrufen (für Statusanzeige in der Sidebar)
AgentRunStatus GetStatus(string agentId);

// Laufenden Run abbrechen
void Abort(string agentId);
```

```csharp
// Neue Typen in Core/Engine/

public sealed record ChatEntry(
    string Role,        // "user" | "agent" | "tool"
    string Content,
    DateTime Timestamp
);

public sealed record AgentRunStatus(
    string State,       // "idle" | "running" | "error"
    int    StepCount,
    int    TokensUsed,
    string? LastError
);
```

Der StateManager (Phase 1.2) speichert den Chat-Verlauf pro AgentId
persistent in der konfigurierten Datenbank oder als JSON-Datei,
sodass Verläufe auch nach Programm-Neustart verfügbar sind.

---

## Entwicklungsregeln: WinForms-spezifisch

- **Kein UI-Thread-Blocking:** Alle `await`-Aufrufe in Forms immer mit
  `ConfigureAwait(false)` oder explizitem `InvokeAsync`. Bridge-Callbacks
  kommen auf beliebigen Threads — immer per `InvokeRequired` prüfen.

- **WebView2 ist async:** `EnsureCoreWebView2Async()` muss abgewartet sein
  bevor `CoreWebView2`-Eigenschaften gesetzt werden.

- **Keine direkte Form-zu-Form-Kommunikation:** `frm_chat` kommuniziert
  ausschließlich über `AgentEngine` und `WebViewBridge`. Kein direkter
  Methodenaufruf zwischen Form-Instanzen.

- **frm_chat ist nicht-modal:** Immer `frm.Show(owner)` statt
  `frm.ShowDialog()`. Mehrere Instanzen mit demselben AgentId: nur
  eine öffnen, fokussieren wenn vorhanden (Dictionary-Check in frm_main).

- **Bridge-Nachrichten immer typisiert:** Kein rohes JSON-String-Bauen
  außerhalb von `WebViewBridge` und `BridgeMessage`.

- **EmbeddedUI ist readonly:** Keine dynamischen Schreibzugriffe auf
  die extrahierten UI-Dateien zur Laufzeit. UI-Änderungen erfordern
  Neu-Kompilierung.

---

## NuGet-Pakete (Host-Projekt)

```xml
<PackageReference Include="Microsoft.Web.WebView2" Version="*" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="*" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="*" />
```

---

## Implementierungsreihenfolge (für Claude Code)

Bearbeite diesen Abschnitt nach Abschluss der Core-Phase (Phasen 1–2):

1. `EmbeddedUiManager` implementieren und Sicherheitscheck in `Program.cs` einbauen
2. `BridgeMessage` + `BridgeTypes` + `WebViewBridge` implementieren
3. Core: `ChatAsync`, `GetChatHistoryAsync`, `GetStatus`, `Abort` zu `AgentEngine` hinzufügen
4. Core: `ChatEntry` und `AgentRunStatus` Records anlegen
5. `frm_main`: `InitWebViewAsync`, `PushAgentListAsync`, Bridge-Handler implementieren
6. `frm_chat`: vollständig implementieren
7. `EmbeddedUI/bridge.js` erstellen (gemeinsame Bridge-Logik für beide HTML-Seiten)
8. `EmbeddedUI/overview.html` + `overview.css` erstellen (Sidebar + Chat-Bereich)
9. `EmbeddedUI/chat.html` + `chat.css` erstellen (Einzelchat, agentId aus URL-Parameter)
10. `Program.cs` Startup-Reihenfolge implementieren
11. xUnit-Tests: Bridge-Serialisierung, Pfad-Overlap-Check, frm_chat Isolation

**Beginne mit Schritt 1 dieses Abschnitts.**
