diff --git a/src/PolyTrader.Modules.Supervisor/Agent/SupervisorAgent.cs b/src/PolyTrader.Modules.Supervisor/Agent/SupervisorAgent.cs
index 6d9c12b..44d984a 100644
--- a/src/PolyTrader.Modules.Supervisor/Agent/SupervisorAgent.cs
+++ b/src/PolyTrader.Modules.Supervisor/Agent/SupervisorAgent.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -34,22 +35,36 @@ namespace PolyTrader.Modules.Supervisor.Agent
_tools = tools;
}
- private static string SystemPrompt() =>
- "Du bist der Supervisor von PolyTrader: ein Analyse-Agent für automatisierten Polymarket-Handel. " +
- "Du bist strikt read-only – du kannst und darfst nicht handeln. Nutze die Tools, um Entscheidungsjournal, " +
- "Order-Events, Trades und Logs abzufragen, BEVOR du Schlüsse ziehst. Zitiere konkrete Daten " +
- "(SignalIds, Zeiten, Preise, ReasonCodes). Antworte auf Deutsch, präzise und mit klarer Schlussfolgerung.\n\n" +
- "=== ARCHITEKTUR-KONTEXT ===\n" + ArchitectureContext.Load();
+ private static string SystemPrompt(SupervisorProfile profile)
+ {
+ string basePrompt =
+ "Du bist der Supervisor von PolyTrader: ein Analyse-Agent für automatisierten Polymarket-Handel. " +
+ "Du bist strikt read-only – du kannst und darfst nicht handeln. Nutze die Tools, um Entscheidungsjournal, " +
+ "Order-Events, Trades und Logs abzufragen, BEVOR du Schlüsse ziehst. Zitiere konkrete Daten " +
+ "(SignalIds, Zeiten, Preise, ReasonCodes). Antworte auf Deutsch, präzise und mit klarer Schlussfolgerung.";
+ if (!string.IsNullOrEmpty(profile.PromptAddendum))
+ basePrompt += "\n\n" + profile.PromptAddendum;
+ return basePrompt + "\n\n=== ARCHITEKTUR-KONTEXT ===\n" + ArchitectureContext.Load();
+ }
+
+ /// Tools des Profils (Subset oder alle).
+ private IReadOnlyList ToolsFor(SupervisorProfile profile) =>
+ profile.ToolFilter == null
+ ? _tools.Tools
+ : _tools.Tools.Where(t => Array.Exists(profile.ToolFilter, n =>
+ string.Equals(n, t.Name, StringComparison.OrdinalIgnoreCase))).ToList();
///
/// Beantwortet eine Analyse-Frage. meldet Tool-Aufrufe live an die UI.
///
public async Task AskAsync(string question, string? model = null,
- IProgress? progress = null, CancellationToken ct = default)
+ IProgress? progress = null, SupervisorProfile? profile = null, CancellationToken ct = default)
{
+ var activeProfile = profile ?? SupervisorProfiles.Allgemein;
+ var activeTools = ToolsFor(activeProfile);
var messages = new List
{
- ChatMessage.System(SystemPrompt()),
+ ChatMessage.System(SystemPrompt(activeProfile)),
ChatMessage.User(question)
};
var invocations = new List<(string, string, string)>();
@@ -59,7 +74,7 @@ namespace PolyTrader.Modules.Supervisor.Agent
for (int i = 0; i < MaxIterations; i++)
{
ct.ThrowIfCancellationRequested();
- var response = await _chat.CompleteAsync(usedModel, messages, _tools.Tools, ct);
+ var response = await _chat.CompleteAsync(usedModel, messages, activeTools, ct);
promptTokens += response.PromptTokens;
completionTokens += response.CompletionTokens;
diff --git a/src/PolyTrader.Modules.Supervisor/Agent/SupervisorProfiles.cs b/src/PolyTrader.Modules.Supervisor/Agent/SupervisorProfiles.cs
new file mode 100644
index 0000000..caae4e7
--- /dev/null
+++ b/src/PolyTrader.Modules.Supervisor/Agent/SupervisorProfiles.cs
@@ -0,0 +1,53 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace PolyTrader.Modules.Supervisor.Agent
+{
+ ///
+ /// Ein Supervisor-Profil (S-3): Fokus-Anweisung + optionales Tool-Subset über EINER gemeinsamen
+ /// Agent-Infrastruktur (Konzept §4a) — bewusst KEINE Agent-zu-Agent-Orchestrierung.
+ ///
+ public sealed record SupervisorProfile(string Name, string PromptAddendum, string[]? ToolFilter)
+ {
+ public override string ToString() => Name;
+ }
+
+ /// Die eingebauten Profile. Modul-Wissen liegt im Architektur-Kontext; hier nur der Fokus.
+ public static class SupervisorProfiles
+ {
+ public static readonly SupervisorProfile Allgemein = new(
+ "Allgemein",
+ "",
+ null);
+
+ public static readonly SupervisorProfile Technik = new(
+ "Technik",
+ "FOKUS TECHNIK-SUPERVISOR: Du prüfst ausschließlich die technische Gesundheit — Fehler-/Warning-" +
+ "Muster in den Logs, fehlgeschlagene/stornierte Orders, CLOB-Fehlerantworten, auffällige " +
+ "Latenzen und Lücken in den Datenketten. KEINE Strategie-Bewertung (ob ein Trade klug war, " +
+ "ist nicht dein Thema — nur ob die Software korrekt funktioniert hat).",
+ new[] { "read_logs", "query_order_events", "query_decisions", "get_dossier", "get_architecture_context" });
+
+ public static readonly SupervisorProfile CopyTrading = new(
+ "CopyTrading",
+ "FOKUS COPYTRADING-SUPERVISOR: Du bewertest die CopyTrading-Strategie — Master-Qualität vs. " +
+ "Ausführungsqualität (Latenz/Preisdifferenz Signal→Fill), Verhalten der SELL-Eskalationsleiter, " +
+ "Reject-Muster (waren die Risk-Limits klug oder haben sie profitable Trades verhindert?). " +
+ "Filtere Daten auf ModuleName='CopyTrading'.",
+ null);
+
+ public static readonly SupervisorProfile ResolutionFarming = new(
+ "ResolutionFarming",
+ "FOKUS RF-SUPERVISOR: Du bewertest die ResolutionFarming-Strategie — Kalibrierung (realisierte " +
+ "Winrate je Einstiegspreisband vs. implizite Wahrscheinlichkeit), Cluster-Risiken, Scanner-" +
+ "Qualität. Filtere Daten auf ModuleName='ResolutionFarming'.",
+ null);
+
+ public static IReadOnlyList All { get; } =
+ new[] { Allgemein, Technik, CopyTrading, ResolutionFarming };
+
+ public static SupervisorProfile ByName(string? name) =>
+ All.FirstOrDefault(p => string.Equals(p.Name, name, StringComparison.OrdinalIgnoreCase)) ?? Allgemein;
+ }
+}
diff --git a/src/PolyTrader.Modules.Supervisor/Mcp/McpJsonRpc.cs b/src/PolyTrader.Modules.Supervisor/Mcp/McpJsonRpc.cs
new file mode 100644
index 0000000..8f12b5f
--- /dev/null
+++ b/src/PolyTrader.Modules.Supervisor/Mcp/McpJsonRpc.cs
@@ -0,0 +1,149 @@
+using System;
+using System.IO;
+using System.Text;
+using System.Text.Json;
+using PolyTrader.Modules.Supervisor.Agent;
+
+namespace PolyTrader.Modules.Supervisor.Mcp
+{
+ ///
+ /// MCP-Light (S-4): purer JSON-RPC-2.0-Handler für das Model Context Protocol über die
+ /// read-only . Externe KI-Clients (z. B. Claude Code)
+ /// erhalten damit dieselben Analyse-Tools wie der In-App-Agent — **kein** Modell-Zugang,
+ /// nur die Daten-Tür. Unterstützt: initialize, ping, tools/list, tools/call.
+ /// Pur und seiteneffektfrei → unit-getestet; der HTTP-Host ist nur eine dünne Hülle.
+ ///
+ public static class McpJsonRpc
+ {
+ public const string ProtocolVersion = "2025-03-26";
+ public const string ServerName = "polytrader-supervisor";
+ public const string ServerVersion = "1.0";
+
+ ///
+ /// Verarbeitet eine JSON-RPC-Nachricht. Liefert die Antwort als JSON-String —
+ /// oder null für Notifications (kein id) und unparsbare Eingaben ohne id.
+ ///
+ public static string? Handle(string requestJson, SupervisorToolRegistry registry)
+ {
+ JsonDocument doc;
+ try { doc = JsonDocument.Parse(requestJson); }
+ catch (JsonException) { return Error(null, -32700, "Parse error"); }
+
+ using (doc)
+ {
+ var root = doc.RootElement;
+ JsonElement? id = root.TryGetProperty("id", out var idProp) ? idProp.Clone() : (JsonElement?)null;
+ string method = root.TryGetProperty("method", out var m) ? m.GetString() ?? "" : "";
+
+ // Notifications (kein id) werden nicht beantwortet.
+ if (id == null) return null;
+
+ try
+ {
+ return method switch
+ {
+ "initialize" => Result(id.Value, w =>
+ {
+ w.WriteString("protocolVersion", ProtocolVersion);
+ w.WriteStartObject("capabilities");
+ w.WriteStartObject("tools");
+ w.WriteEndObject();
+ w.WriteEndObject();
+ w.WriteStartObject("serverInfo");
+ w.WriteString("name", ServerName);
+ w.WriteString("version", ServerVersion);
+ w.WriteEndObject();
+ }),
+
+ "ping" => Result(id.Value, _ => { }),
+
+ "tools/list" => Result(id.Value, w =>
+ {
+ w.WriteStartArray("tools");
+ foreach (var tool in registry.Tools)
+ {
+ w.WriteStartObject();
+ w.WriteString("name", tool.Name);
+ w.WriteString("description", tool.Description);
+ w.WritePropertyName("inputSchema");
+ using (var schema = JsonDocument.Parse(tool.ParametersJsonSchema))
+ schema.RootElement.WriteTo(w);
+ w.WriteEndObject();
+ }
+ w.WriteEndArray();
+ }),
+
+ "tools/call" => HandleToolCall(id.Value, root, registry),
+
+ _ => Error(id, -32601, $"Method not found: {method}")
+ };
+ }
+ catch (Exception ex)
+ {
+ return Error(id, -32603, $"Internal error: {ex.Message}");
+ }
+ }
+ }
+
+ private static string HandleToolCall(JsonElement id, JsonElement root, SupervisorToolRegistry registry)
+ {
+ if (!root.TryGetProperty("params", out var p) || p.ValueKind != JsonValueKind.Object)
+ return Error(id, -32602, "Invalid params");
+
+ string name = p.TryGetProperty("name", out var n) ? n.GetString() ?? "" : "";
+ string argsJson = p.TryGetProperty("arguments", out var a) && a.ValueKind == JsonValueKind.Object
+ ? a.GetRawText() : "{}";
+
+ string toolResult = registry.Execute(name, argsJson);
+ bool isError = toolResult.StartsWith("FEHLER", StringComparison.OrdinalIgnoreCase);
+
+ return Result(id, w =>
+ {
+ w.WriteStartArray("content");
+ w.WriteStartObject();
+ w.WriteString("type", "text");
+ w.WriteString("text", toolResult);
+ w.WriteEndObject();
+ w.WriteEndArray();
+ w.WriteBoolean("isError", isError);
+ });
+ }
+
+ // ----- JSON-RPC-Hüllen -----
+
+ private static string Result(JsonElement id, Action writeResult)
+ {
+ using var ms = new MemoryStream();
+ using (var w = new Utf8JsonWriter(ms))
+ {
+ w.WriteStartObject();
+ w.WriteString("jsonrpc", "2.0");
+ w.WritePropertyName("id");
+ id.WriteTo(w);
+ w.WriteStartObject("result");
+ writeResult(w);
+ w.WriteEndObject();
+ w.WriteEndObject();
+ }
+ return Encoding.UTF8.GetString(ms.ToArray());
+ }
+
+ private static string Error(JsonElement? id, int code, string message)
+ {
+ using var ms = new MemoryStream();
+ using (var w = new Utf8JsonWriter(ms))
+ {
+ w.WriteStartObject();
+ w.WriteString("jsonrpc", "2.0");
+ w.WritePropertyName("id");
+ if (id.HasValue) id.Value.WriteTo(w); else w.WriteNullValue();
+ w.WriteStartObject("error");
+ w.WriteNumber("code", code);
+ w.WriteString("message", message);
+ w.WriteEndObject();
+ w.WriteEndObject();
+ }
+ return Encoding.UTF8.GetString(ms.ToArray());
+ }
+ }
+}
diff --git a/src/PolyTrader.Modules.Supervisor/Mcp/McpLightServer.cs b/src/PolyTrader.Modules.Supervisor/Mcp/McpLightServer.cs
new file mode 100644
index 0000000..e4d6a73
--- /dev/null
+++ b/src/PolyTrader.Modules.Supervisor/Mcp/McpLightServer.cs
@@ -0,0 +1,109 @@
+using System;
+using System.IO;
+using System.Net;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Hosting;
+using PolyTrader.Modules.Supervisor.Agent;
+using PolyTraderSharp.Services;
+
+namespace PolyTrader.Modules.Supervisor.Mcp
+{
+ ///
+ /// MCP-Light-Host (S-4): lokaler HTTP-Endpoint (Streamable-HTTP-Transport, nur POST-JSON), der die
+ /// read-only Tool-Registry per Model Context Protocol exponiert. Externe Clients wie Claude Code
+ /// verbinden sich mit: claude mcp add --transport http polytrader http://127.0.0.1:PORT/mcp.
+ ///
+ /// SICHERHEIT: bewusst OPT-IN (startet nur, wenn die Umgebungsvariable POLYTRADER_MCP_PORT gesetzt
+ /// ist) und bindet ausschließlich an 127.0.0.1 (kein Netzwerkzugriff). Die Tools sind read-only —
+ /// es existiert kein Mechanismus zum Handeln/Schreiben. Kein Modell-Zugang: MCP ist nur die
+ /// Daten-Tür; Modelle laufen weiterhin über OpenRouter (In-App-Agent) bzw. den externen Client.
+ ///
+ public sealed class McpLightServer : BackgroundService
+ {
+ private readonly SupervisorToolRegistry _registry;
+ private readonly TerminalLogger _logger;
+
+ public McpLightServer(SupervisorToolRegistry registry, TerminalLogger logger)
+ {
+ _registry = registry;
+ _logger = logger;
+ }
+
+ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
+ {
+ string? portRaw = Environment.GetEnvironmentVariable("POLYTRADER_MCP_PORT");
+ if (string.IsNullOrWhiteSpace(portRaw))
+ {
+ _logger.Info("MCP-Light: deaktiviert (POLYTRADER_MCP_PORT nicht gesetzt).");
+ return;
+ }
+ if (!int.TryParse(portRaw, out int port) || port is < 1024 or > 65535)
+ {
+ _logger.Warning($"MCP-Light: ungültiger Port '{portRaw}' – Server startet nicht.");
+ return;
+ }
+
+ using var listener = new HttpListener();
+ listener.Prefixes.Add($"http://127.0.0.1:{port}/mcp/");
+ try { listener.Start(); }
+ catch (Exception ex)
+ {
+ _logger.Error($"MCP-Light: Start auf Port {port} fehlgeschlagen: {ex.Message}");
+ return;
+ }
+
+ _logger.Info($"🔌 MCP-Light aktiv: http://127.0.0.1:{port}/mcp (read-only, {_registry.Tools.Count} Tools). " +
+ $"Claude Code: claude mcp add --transport http polytrader http://127.0.0.1:{port}/mcp");
+
+ using var reg = stoppingToken.Register(() => { try { listener.Stop(); } catch { } });
+ while (!stoppingToken.IsCancellationRequested)
+ {
+ HttpListenerContext ctx;
+ try { ctx = await listener.GetContextAsync(); }
+ catch when (stoppingToken.IsCancellationRequested) { break; }
+ catch (Exception ex) { _logger.Warning($"MCP-Light: Listener-Fehler: {ex.Message}"); continue; }
+
+ _ = Task.Run(() => HandleRequestAsync(ctx), stoppingToken);
+ }
+ }
+
+ private async Task HandleRequestAsync(HttpListenerContext ctx)
+ {
+ try
+ {
+ if (ctx.Request.HttpMethod != "POST")
+ {
+ ctx.Response.StatusCode = 405; // GET/SSE-Stream bieten wir bewusst nicht an
+ ctx.Response.Close();
+ return;
+ }
+
+ string body;
+ using (var reader = new StreamReader(ctx.Request.InputStream, ctx.Request.ContentEncoding))
+ body = await reader.ReadToEndAsync();
+
+ string? response = McpJsonRpc.Handle(body, _registry);
+ if (response == null)
+ {
+ ctx.Response.StatusCode = 202; // Notification: angenommen, keine Antwort
+ ctx.Response.Close();
+ return;
+ }
+
+ byte[] bytes = Encoding.UTF8.GetBytes(response);
+ ctx.Response.StatusCode = 200;
+ ctx.Response.ContentType = "application/json";
+ ctx.Response.ContentLength64 = bytes.Length;
+ await ctx.Response.OutputStream.WriteAsync(bytes);
+ ctx.Response.Close();
+ }
+ catch (Exception ex)
+ {
+ _logger.Warning($"MCP-Light: Request-Fehler: {ex.Message}");
+ try { ctx.Response.StatusCode = 500; ctx.Response.Close(); } catch { }
+ }
+ }
+ }
+}
diff --git a/src/PolyTrader.Modules.Supervisor/Persistence/Migrations/20260718085811_InitialSupervisor.Designer.cs b/src/PolyTrader.Modules.Supervisor/Persistence/Migrations/20260718085811_InitialSupervisor.Designer.cs
new file mode 100644
index 0000000..498d8e6
--- /dev/null
+++ b/src/PolyTrader.Modules.Supervisor/Persistence/Migrations/20260718085811_InitialSupervisor.Designer.cs
@@ -0,0 +1,80 @@
+//
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using PolyTrader.Modules.Supervisor.Persistence;
+
+#nullable disable
+
+namespace PolyTrader.Modules.Supervisor.Persistence.Migrations
+{
+ [DbContext(typeof(SupervisorDbContext))]
+ [Migration("20260718085811_InitialSupervisor")]
+ partial class InitialSupervisor
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "8.0.13")
+ .HasAnnotation("Relational:MaxIdentifierLength", 64);
+
+ MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder);
+
+ modelBuilder.Entity("PolyTrader.Modules.Supervisor.Persistence.SupervisorReport", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id"));
+
+ b.Property("Answer")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("CompletionTokens")
+ .HasColumnType("int");
+
+ b.Property("CreatedAt")
+ .HasColumnType("datetime(6)");
+
+ b.Property("Model")
+ .IsRequired()
+ .HasMaxLength(120)
+ .HasColumnType("varchar(120)");
+
+ b.Property("Profile")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("varchar(50)");
+
+ b.Property("PromptTokens")
+ .HasColumnType("int");
+
+ b.Property("Question")
+ .IsRequired()
+ .HasMaxLength(4000)
+ .HasColumnType("varchar(4000)");
+
+ b.Property("ToolCallCount")
+ .HasColumnType("int");
+
+ b.Property("ToolCallsJson")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CreatedAt");
+
+ b.ToTable("sup_reports", (string)null);
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/src/PolyTrader.Modules.Supervisor/Persistence/Migrations/20260718085811_InitialSupervisor.cs b/src/PolyTrader.Modules.Supervisor/Persistence/Migrations/20260718085811_InitialSupervisor.cs
new file mode 100644
index 0000000..70d0754
--- /dev/null
+++ b/src/PolyTrader.Modules.Supervisor/Persistence/Migrations/20260718085811_InitialSupervisor.cs
@@ -0,0 +1,58 @@
+using System;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace PolyTrader.Modules.Supervisor.Persistence.Migrations
+{
+ ///
+ public partial class InitialSupervisor : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.AlterDatabase()
+ .Annotation("MySql:CharSet", "utf8mb4");
+
+ migrationBuilder.CreateTable(
+ name: "sup_reports",
+ columns: table => new
+ {
+ Id = table.Column(type: "bigint", nullable: false)
+ .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
+ CreatedAt = table.Column(type: "datetime(6)", nullable: false),
+ Profile = table.Column(type: "varchar(50)", maxLength: 50, nullable: false)
+ .Annotation("MySql:CharSet", "utf8mb4"),
+ Model = table.Column(type: "varchar(120)", maxLength: 120, nullable: false)
+ .Annotation("MySql:CharSet", "utf8mb4"),
+ Question = table.Column(type: "varchar(4000)", maxLength: 4000, nullable: false)
+ .Annotation("MySql:CharSet", "utf8mb4"),
+ Answer = table.Column(type: "text", nullable: false)
+ .Annotation("MySql:CharSet", "utf8mb4"),
+ ToolCallsJson = table.Column(type: "text", nullable: false)
+ .Annotation("MySql:CharSet", "utf8mb4"),
+ ToolCallCount = table.Column(type: "int", nullable: false),
+ PromptTokens = table.Column(type: "int", nullable: false),
+ CompletionTokens = table.Column(type: "int", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_sup_reports", x => x.Id);
+ })
+ .Annotation("MySql:CharSet", "utf8mb4");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_sup_reports_CreatedAt",
+ table: "sup_reports",
+ column: "CreatedAt");
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropTable(
+ name: "sup_reports");
+ }
+ }
+}
diff --git a/src/PolyTrader.Modules.Supervisor/Persistence/Migrations/SupervisorDbContextModelSnapshot.cs b/src/PolyTrader.Modules.Supervisor/Persistence/Migrations/SupervisorDbContextModelSnapshot.cs
new file mode 100644
index 0000000..47bbb57
--- /dev/null
+++ b/src/PolyTrader.Modules.Supervisor/Persistence/Migrations/SupervisorDbContextModelSnapshot.cs
@@ -0,0 +1,77 @@
+//
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using PolyTrader.Modules.Supervisor.Persistence;
+
+#nullable disable
+
+namespace PolyTrader.Modules.Supervisor.Persistence.Migrations
+{
+ [DbContext(typeof(SupervisorDbContext))]
+ partial class SupervisorDbContextModelSnapshot : ModelSnapshot
+ {
+ protected override void BuildModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "8.0.13")
+ .HasAnnotation("Relational:MaxIdentifierLength", 64);
+
+ MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder);
+
+ modelBuilder.Entity("PolyTrader.Modules.Supervisor.Persistence.SupervisorReport", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id"));
+
+ b.Property("Answer")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("CompletionTokens")
+ .HasColumnType("int");
+
+ b.Property("CreatedAt")
+ .HasColumnType("datetime(6)");
+
+ b.Property("Model")
+ .IsRequired()
+ .HasMaxLength(120)
+ .HasColumnType("varchar(120)");
+
+ b.Property("Profile")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("varchar(50)");
+
+ b.Property("PromptTokens")
+ .HasColumnType("int");
+
+ b.Property("Question")
+ .IsRequired()
+ .HasMaxLength(4000)
+ .HasColumnType("varchar(4000)");
+
+ b.Property("ToolCallCount")
+ .HasColumnType("int");
+
+ b.Property("ToolCallsJson")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CreatedAt");
+
+ b.ToTable("sup_reports", (string)null);
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/src/PolyTrader.Modules.Supervisor/Persistence/SupervisorDbContext.cs b/src/PolyTrader.Modules.Supervisor/Persistence/SupervisorDbContext.cs
new file mode 100644
index 0000000..e002f9f
--- /dev/null
+++ b/src/PolyTrader.Modules.Supervisor/Persistence/SupervisorDbContext.cs
@@ -0,0 +1,82 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Design;
+using PolyTrader.Core.Configuration;
+
+namespace PolyTrader.Modules.Supervisor.Persistence
+{
+ /// EF-Kontext des Supervisor-Moduls (gleiche MySQL-DB, Tabellen mit Präfix sup_).
+ public class SupervisorDbContext : DbContext
+ {
+ public SupervisorDbContext(DbContextOptions options) : base(options) { }
+
+ public DbSet Reports => Set();
+
+ protected override void OnModelCreating(ModelBuilder b)
+ {
+ b.Entity(e =>
+ {
+ e.ToTable("sup_reports");
+ e.HasKey(x => x.Id);
+ e.Property(x => x.Id).ValueGeneratedOnAdd();
+ e.Property(x => x.Profile).HasMaxLength(50);
+ e.Property(x => x.Model).HasMaxLength(120);
+ e.Property(x => x.Question).HasMaxLength(4000);
+ e.Property(x => x.Answer).HasColumnType("text");
+ e.Property(x => x.ToolCallsJson).HasColumnType("text");
+ e.HasIndex(x => x.CreatedAt);
+ });
+ }
+ }
+
+ ///
+ /// Design-Time-Factory (fixe Server-Version → Migrations-Scaffolding OHNE DB-Verbindung).
+ ///
+ public class SupervisorDbContextFactory : IDesignTimeDbContextFactory
+ {
+ public SupervisorDbContext CreateDbContext(string[] args)
+ {
+ var conn = Environment.GetEnvironmentVariable("POLYTRADER_MYSQL")
+ ?? "Server=localhost;Port=3306;Database=polytrader;User ID=root;Password=;";
+ var options = new DbContextOptionsBuilder()
+ .UseMySql(conn, DatabaseServerVersion.Value)
+ .Options;
+ return new SupervisorDbContext(options);
+ }
+ }
+
+ /// Bericht-Ablage. Write fehlertolerant (Analyse darf nie an der Persistenz scheitern).
+ public interface ISupervisorReportRepository
+ {
+ void Insert(SupervisorReport report);
+ List GetRecent(int limit);
+ }
+
+ public class EfSupervisorReportRepository : ISupervisorReportRepository
+ {
+ private readonly IDbContextFactory _factory;
+ public EfSupervisorReportRepository(IDbContextFactory factory) => _factory = factory;
+
+ public void Insert(SupervisorReport report)
+ {
+ try
+ {
+ using var ctx = _factory.CreateDbContext();
+ ctx.Reports.Add(report);
+ ctx.SaveChanges();
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"[SupervisorReports] Write fehlgeschlagen (ignoriert): {ex.Message}");
+ }
+ }
+
+ public List GetRecent(int limit)
+ {
+ using var ctx = _factory.CreateDbContext();
+ return ctx.Reports.AsNoTracking().OrderByDescending(r => r.CreatedAt).Take(limit).ToList();
+ }
+ }
+}
diff --git a/src/PolyTrader.Modules.Supervisor/Persistence/SupervisorReport.cs b/src/PolyTrader.Modules.Supervisor/Persistence/SupervisorReport.cs
new file mode 100644
index 0000000..f91357f
--- /dev/null
+++ b/src/PolyTrader.Modules.Supervisor/Persistence/SupervisorReport.cs
@@ -0,0 +1,27 @@
+using System;
+
+namespace PolyTrader.Modules.Supervisor.Persistence
+{
+ ///
+ /// Gespeicherte Analyse (Tabelle sup_reports): Frage, Antwort, Profil/Modell und die
+ /// Tool-Aufruf-Historie — macht den Supervisor selbst auditierbar (Konzept §6) und
+ /// füttert später den „Chef"-Supervisor sowie Tagesberichte.
+ ///
+ public class SupervisorReport
+ {
+ public long Id { get; set; } // DB-Autoincrement
+ public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
+
+ public string Profile { get; set; } = string.Empty;
+ public string Model { get; set; } = string.Empty;
+ public string Question { get; set; } = string.Empty;
+ public string Answer { get; set; } = string.Empty;
+
+ /// Tool-Aufrufe als JSON [{tool,args}] (Ergebnisse sind reproduzierbar, daher nicht gespeichert).
+ public string ToolCallsJson { get; set; } = string.Empty;
+
+ public int ToolCallCount { get; set; }
+ public int PromptTokens { get; set; }
+ public int CompletionTokens { get; set; }
+ }
+}
diff --git a/src/PolyTrader.Modules.Supervisor/PolyTrader.Modules.Supervisor.csproj b/src/PolyTrader.Modules.Supervisor/PolyTrader.Modules.Supervisor.csproj
index 790239d..c08ebcf 100644
--- a/src/PolyTrader.Modules.Supervisor/PolyTrader.Modules.Supervisor.csproj
+++ b/src/PolyTrader.Modules.Supervisor/PolyTrader.Modules.Supervisor.csproj
@@ -4,6 +4,14 @@
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
diff --git a/src/PolyTrader.Modules.Supervisor/SupervisorModule.cs b/src/PolyTrader.Modules.Supervisor/SupervisorModule.cs
index 13688a6..50ed606 100644
--- a/src/PolyTrader.Modules.Supervisor/SupervisorModule.cs
+++ b/src/PolyTrader.Modules.Supervisor/SupervisorModule.cs
@@ -1,5 +1,6 @@
using System.Threading;
using System.Threading.Tasks;
+using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using PolyTrader.Core.Modularity;
@@ -34,7 +35,15 @@ namespace PolyTrader.Modules.Supervisor
new Agent.OpenRouterClient(new System.Net.Http.HttpClient { Timeout = TimeSpan.FromMinutes(3) }));
services.AddSingleton();
- // sup_-Persistenz (Berichte/Konversationen) + Profile folgen mit S-3.
+ // S-3: sup_-Persistenz (gespeicherte Analysen/Berichte).
+ var conn = configuration["Database:MySqlConnectionString"] ?? string.Empty;
+ services.AddDbContextFactory(o =>
+ o.UseMySql(conn, PolyTrader.Core.Configuration.DatabaseServerVersion.Value));
+ services.AddSingleton();
+
+ // S-4: MCP-Light – exponiert die read-only Tool-Registry für externe KI-Clients
+ // (z. B. Claude Code). OPT-IN via POLYTRADER_MCP_PORT, bindet nur 127.0.0.1.
+ services.AddHostedService();
}
public void RegisterUi(IModuleUiHost host, System.IServiceProvider services)
diff --git a/src/PolyTrader.Modules.Supervisor/Ui/SupervisorMainForm.Designer.cs b/src/PolyTrader.Modules.Supervisor/Ui/SupervisorMainForm.Designer.cs
new file mode 100644
index 0000000..7d85ca8
--- /dev/null
+++ b/src/PolyTrader.Modules.Supervisor/Ui/SupervisorMainForm.Designer.cs
@@ -0,0 +1,412 @@
+namespace PolyTrader.Modules.Supervisor.Ui
+{
+ partial class SupervisorMainForm
+ {
+ private System.ComponentModel.IContainer components = null;
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region Vom Komponenten-Designer generierter Code
+
+ private void InitializeComponent()
+ {
+ this.tabControlSup = new System.Windows.Forms.TabControl();
+ this.tabAnalyse = new System.Windows.Forms.TabPage();
+ this.rtbChat = new System.Windows.Forms.RichTextBox();
+ this.pnlChatInput = new System.Windows.Forms.Panel();
+ this.tbChatInput = new System.Windows.Forms.TextBox();
+ this.btnSend = new System.Windows.Forms.Button();
+ this.toolStripChat = new System.Windows.Forms.ToolStrip();
+ this.lblProfil = new System.Windows.Forms.ToolStripLabel();
+ this.cbProfile = new System.Windows.Forms.ToolStripComboBox();
+ this.lblModel = new System.Windows.Forms.ToolStripLabel();
+ this.tbModel = new System.Windows.Forms.ToolStripTextBox();
+ this.sepChat = new System.Windows.Forms.ToolStripSeparator();
+ this.btnClearChat = new System.Windows.Forms.ToolStripButton();
+ this.tabDossiers = new System.Windows.Forms.TabPage();
+ this.splitDossiers = new System.Windows.Forms.SplitContainer();
+ this.dgvSignals = new System.Windows.Forms.DataGridView();
+ this.tbDossier = new System.Windows.Forms.TextBox();
+ this.toolStripDossier = new System.Windows.Forms.ToolStrip();
+ this.btnDossierRefresh = new System.Windows.Forms.ToolStripButton();
+ this.sepDossier = new System.Windows.Forms.ToolStripSeparator();
+ this.lblSignal = new System.Windows.Forms.ToolStripLabel();
+ this.tbSignalId = new System.Windows.Forms.ToolStripTextBox();
+ this.btnDossierOpen = new System.Windows.Forms.ToolStripButton();
+ this.tabBerichte = new System.Windows.Forms.TabPage();
+ this.splitReports = new System.Windows.Forms.SplitContainer();
+ this.dgvReports = new System.Windows.Forms.DataGridView();
+ this.tbReport = new System.Windows.Forms.TextBox();
+ this.toolStripReports = new System.Windows.Forms.ToolStrip();
+ this.btnReportsRefresh = new System.Windows.Forms.ToolStripButton();
+ this.lblStatus = new System.Windows.Forms.Label();
+ this.tabControlSup.SuspendLayout();
+ this.tabAnalyse.SuspendLayout();
+ this.pnlChatInput.SuspendLayout();
+ this.toolStripChat.SuspendLayout();
+ this.tabDossiers.SuspendLayout();
+ ((System.ComponentModel.ISupportInitialize)(this.splitDossiers)).BeginInit();
+ this.splitDossiers.Panel1.SuspendLayout();
+ this.splitDossiers.Panel2.SuspendLayout();
+ this.splitDossiers.SuspendLayout();
+ ((System.ComponentModel.ISupportInitialize)(this.dgvSignals)).BeginInit();
+ this.toolStripDossier.SuspendLayout();
+ this.tabBerichte.SuspendLayout();
+ ((System.ComponentModel.ISupportInitialize)(this.splitReports)).BeginInit();
+ this.splitReports.Panel1.SuspendLayout();
+ this.splitReports.Panel2.SuspendLayout();
+ this.splitReports.SuspendLayout();
+ ((System.ComponentModel.ISupportInitialize)(this.dgvReports)).BeginInit();
+ this.toolStripReports.SuspendLayout();
+ this.SuspendLayout();
+ //
+ // tabControlSup
+ //
+ this.tabControlSup.Controls.Add(this.tabAnalyse);
+ this.tabControlSup.Controls.Add(this.tabDossiers);
+ this.tabControlSup.Controls.Add(this.tabBerichte);
+ this.tabControlSup.Dock = System.Windows.Forms.DockStyle.Fill;
+ this.tabControlSup.Location = new System.Drawing.Point(0, 0);
+ this.tabControlSup.Name = "tabControlSup";
+ this.tabControlSup.SelectedIndex = 0;
+ this.tabControlSup.Size = new System.Drawing.Size(1250, 676);
+ this.tabControlSup.TabIndex = 0;
+ //
+ // tabAnalyse
+ //
+ this.tabAnalyse.Controls.Add(this.rtbChat);
+ this.tabAnalyse.Controls.Add(this.pnlChatInput);
+ this.tabAnalyse.Controls.Add(this.toolStripChat);
+ this.tabAnalyse.Location = new System.Drawing.Point(4, 24);
+ this.tabAnalyse.Name = "tabAnalyse";
+ this.tabAnalyse.Padding = new System.Windows.Forms.Padding(3);
+ this.tabAnalyse.Size = new System.Drawing.Size(1242, 648);
+ this.tabAnalyse.TabIndex = 0;
+ this.tabAnalyse.Text = "Analyse";
+ this.tabAnalyse.UseVisualStyleBackColor = true;
+ //
+ // rtbChat
+ //
+ this.rtbChat.BackColor = System.Drawing.Color.White;
+ this.rtbChat.Dock = System.Windows.Forms.DockStyle.Fill;
+ this.rtbChat.Font = new System.Drawing.Font("Segoe UI", 9.5F);
+ this.rtbChat.Location = new System.Drawing.Point(3, 28);
+ this.rtbChat.Name = "rtbChat";
+ this.rtbChat.ReadOnly = true;
+ this.rtbChat.Size = new System.Drawing.Size(1236, 557);
+ this.rtbChat.TabIndex = 1;
+ this.rtbChat.Text = "";
+ //
+ // pnlChatInput
+ //
+ this.pnlChatInput.Controls.Add(this.tbChatInput);
+ this.pnlChatInput.Controls.Add(this.btnSend);
+ this.pnlChatInput.Dock = System.Windows.Forms.DockStyle.Bottom;
+ this.pnlChatInput.Location = new System.Drawing.Point(3, 585);
+ this.pnlChatInput.Name = "pnlChatInput";
+ this.pnlChatInput.Padding = new System.Windows.Forms.Padding(4);
+ this.pnlChatInput.Size = new System.Drawing.Size(1236, 60);
+ this.pnlChatInput.TabIndex = 2;
+ //
+ // tbChatInput
+ //
+ this.tbChatInput.Dock = System.Windows.Forms.DockStyle.Fill;
+ this.tbChatInput.Location = new System.Drawing.Point(4, 4);
+ this.tbChatInput.Multiline = true;
+ this.tbChatInput.Name = "tbChatInput";
+ this.tbChatInput.PlaceholderText = "Analyse-Frage stellen … (Strg+Enter zum Senden)";
+ this.tbChatInput.Size = new System.Drawing.Size(1118, 52);
+ this.tbChatInput.TabIndex = 0;
+ //
+ // btnSend
+ //
+ this.btnSend.Dock = System.Windows.Forms.DockStyle.Right;
+ this.btnSend.Location = new System.Drawing.Point(1122, 4);
+ this.btnSend.Name = "btnSend";
+ this.btnSend.Size = new System.Drawing.Size(110, 52);
+ this.btnSend.TabIndex = 1;
+ this.btnSend.Text = "Senden";
+ this.btnSend.UseVisualStyleBackColor = true;
+ //
+ // toolStripChat
+ //
+ this.toolStripChat.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
+ this.lblProfil, this.cbProfile, this.lblModel, this.tbModel, this.sepChat, this.btnClearChat});
+ this.toolStripChat.Location = new System.Drawing.Point(3, 3);
+ this.toolStripChat.Name = "toolStripChat";
+ this.toolStripChat.Size = new System.Drawing.Size(1236, 25);
+ this.toolStripChat.TabIndex = 0;
+ //
+ // lblProfil
+ //
+ this.lblProfil.Name = "lblProfil";
+ this.lblProfil.Text = "Profil:";
+ //
+ // cbProfile
+ //
+ this.cbProfile.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+ this.cbProfile.Name = "cbProfile";
+ this.cbProfile.Size = new System.Drawing.Size(160, 25);
+ //
+ // lblModel
+ //
+ this.lblModel.Name = "lblModel";
+ this.lblModel.Text = "Modell:";
+ //
+ // tbModel
+ //
+ this.tbModel.AutoSize = false;
+ this.tbModel.Name = "tbModel";
+ this.tbModel.Size = new System.Drawing.Size(220, 25);
+ this.tbModel.Text = "openrouter/auto";
+ //
+ // sepChat
+ //
+ this.sepChat.Name = "sepChat";
+ //
+ // btnClearChat
+ //
+ this.btnClearChat.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
+ this.btnClearChat.Name = "btnClearChat";
+ this.btnClearChat.Text = "Verlauf leeren";
+ //
+ // tabDossiers
+ //
+ this.tabDossiers.Controls.Add(this.splitDossiers);
+ this.tabDossiers.Controls.Add(this.toolStripDossier);
+ this.tabDossiers.Location = new System.Drawing.Point(4, 24);
+ this.tabDossiers.Name = "tabDossiers";
+ this.tabDossiers.Padding = new System.Windows.Forms.Padding(3);
+ this.tabDossiers.Size = new System.Drawing.Size(1242, 648);
+ this.tabDossiers.TabIndex = 1;
+ this.tabDossiers.Text = "Dossiers";
+ this.tabDossiers.UseVisualStyleBackColor = true;
+ //
+ // splitDossiers
+ //
+ this.splitDossiers.Dock = System.Windows.Forms.DockStyle.Fill;
+ this.splitDossiers.Location = new System.Drawing.Point(3, 28);
+ this.splitDossiers.Name = "splitDossiers";
+ this.splitDossiers.Panel1.Controls.Add(this.dgvSignals);
+ this.splitDossiers.Panel2.Controls.Add(this.tbDossier);
+ this.splitDossiers.Size = new System.Drawing.Size(1236, 617);
+ this.splitDossiers.SplitterDistance = 420;
+ this.splitDossiers.TabIndex = 1;
+ //
+ // dgvSignals
+ //
+ this.dgvSignals.AllowUserToAddRows = false;
+ this.dgvSignals.AllowUserToDeleteRows = false;
+ this.dgvSignals.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
+ this.dgvSignals.Dock = System.Windows.Forms.DockStyle.Fill;
+ this.dgvSignals.Location = new System.Drawing.Point(0, 0);
+ this.dgvSignals.MultiSelect = false;
+ this.dgvSignals.Name = "dgvSignals";
+ this.dgvSignals.ReadOnly = true;
+ this.dgvSignals.RowHeadersVisible = false;
+ this.dgvSignals.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect;
+ this.dgvSignals.Size = new System.Drawing.Size(420, 617);
+ this.dgvSignals.TabIndex = 0;
+ //
+ // tbDossier
+ //
+ this.tbDossier.Dock = System.Windows.Forms.DockStyle.Fill;
+ this.tbDossier.Font = new System.Drawing.Font("Consolas", 9.5F);
+ this.tbDossier.Location = new System.Drawing.Point(0, 0);
+ this.tbDossier.Multiline = true;
+ this.tbDossier.Name = "tbDossier";
+ this.tbDossier.ReadOnly = true;
+ this.tbDossier.ScrollBars = System.Windows.Forms.ScrollBars.Both;
+ this.tbDossier.Size = new System.Drawing.Size(812, 617);
+ this.tbDossier.TabIndex = 0;
+ this.tbDossier.WordWrap = false;
+ //
+ // toolStripDossier
+ //
+ this.toolStripDossier.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
+ this.btnDossierRefresh, this.sepDossier, this.lblSignal, this.tbSignalId, this.btnDossierOpen});
+ this.toolStripDossier.Location = new System.Drawing.Point(3, 3);
+ this.toolStripDossier.Name = "toolStripDossier";
+ this.toolStripDossier.Size = new System.Drawing.Size(1236, 25);
+ this.toolStripDossier.TabIndex = 0;
+ //
+ // btnDossierRefresh
+ //
+ this.btnDossierRefresh.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
+ this.btnDossierRefresh.Name = "btnDossierRefresh";
+ this.btnDossierRefresh.Text = "Aktualisieren";
+ //
+ // sepDossier
+ //
+ this.sepDossier.Name = "sepDossier";
+ //
+ // lblSignal
+ //
+ this.lblSignal.Name = "lblSignal";
+ this.lblSignal.Text = "SignalId:";
+ //
+ // tbSignalId
+ //
+ this.tbSignalId.AutoSize = false;
+ this.tbSignalId.Name = "tbSignalId";
+ this.tbSignalId.Size = new System.Drawing.Size(220, 25);
+ //
+ // btnDossierOpen
+ //
+ this.btnDossierOpen.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
+ this.btnDossierOpen.Name = "btnDossierOpen";
+ this.btnDossierOpen.Text = "Dossier öffnen";
+ //
+ // tabBerichte
+ //
+ this.tabBerichte.Controls.Add(this.splitReports);
+ this.tabBerichte.Controls.Add(this.toolStripReports);
+ this.tabBerichte.Location = new System.Drawing.Point(4, 24);
+ this.tabBerichte.Name = "tabBerichte";
+ this.tabBerichte.Padding = new System.Windows.Forms.Padding(3);
+ this.tabBerichte.Size = new System.Drawing.Size(1242, 648);
+ this.tabBerichte.TabIndex = 2;
+ this.tabBerichte.Text = "Berichte";
+ this.tabBerichte.UseVisualStyleBackColor = true;
+ //
+ // splitReports
+ //
+ this.splitReports.Dock = System.Windows.Forms.DockStyle.Fill;
+ this.splitReports.Location = new System.Drawing.Point(3, 28);
+ this.splitReports.Name = "splitReports";
+ this.splitReports.Panel1.Controls.Add(this.dgvReports);
+ this.splitReports.Panel2.Controls.Add(this.tbReport);
+ this.splitReports.Size = new System.Drawing.Size(1236, 617);
+ this.splitReports.SplitterDistance = 520;
+ this.splitReports.TabIndex = 1;
+ //
+ // dgvReports
+ //
+ this.dgvReports.AllowUserToAddRows = false;
+ this.dgvReports.AllowUserToDeleteRows = false;
+ this.dgvReports.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
+ this.dgvReports.Dock = System.Windows.Forms.DockStyle.Fill;
+ this.dgvReports.Location = new System.Drawing.Point(0, 0);
+ this.dgvReports.MultiSelect = false;
+ this.dgvReports.Name = "dgvReports";
+ this.dgvReports.ReadOnly = true;
+ this.dgvReports.RowHeadersVisible = false;
+ this.dgvReports.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect;
+ this.dgvReports.Size = new System.Drawing.Size(520, 617);
+ this.dgvReports.TabIndex = 0;
+ //
+ // tbReport
+ //
+ this.tbReport.Dock = System.Windows.Forms.DockStyle.Fill;
+ this.tbReport.Font = new System.Drawing.Font("Segoe UI", 9.5F);
+ this.tbReport.Location = new System.Drawing.Point(0, 0);
+ this.tbReport.Multiline = true;
+ this.tbReport.Name = "tbReport";
+ this.tbReport.ReadOnly = true;
+ this.tbReport.ScrollBars = System.Windows.Forms.ScrollBars.Both;
+ this.tbReport.Size = new System.Drawing.Size(712, 617);
+ this.tbReport.TabIndex = 0;
+ //
+ // toolStripReports
+ //
+ this.toolStripReports.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
+ this.btnReportsRefresh});
+ this.toolStripReports.Location = new System.Drawing.Point(3, 3);
+ this.toolStripReports.Name = "toolStripReports";
+ this.toolStripReports.Size = new System.Drawing.Size(1236, 25);
+ this.toolStripReports.TabIndex = 0;
+ //
+ // btnReportsRefresh
+ //
+ this.btnReportsRefresh.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
+ this.btnReportsRefresh.Name = "btnReportsRefresh";
+ this.btnReportsRefresh.Text = "Aktualisieren";
+ //
+ // lblStatus
+ //
+ this.lblStatus.Dock = System.Windows.Forms.DockStyle.Bottom;
+ this.lblStatus.Location = new System.Drawing.Point(0, 676);
+ this.lblStatus.Name = "lblStatus";
+ this.lblStatus.Padding = new System.Windows.Forms.Padding(6, 2, 6, 2);
+ this.lblStatus.Size = new System.Drawing.Size(1250, 22);
+ this.lblStatus.TabIndex = 1;
+ this.lblStatus.Text = "";
+ //
+ // SupervisorMainForm
+ //
+ this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
+ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+ this.ClientSize = new System.Drawing.Size(1250, 698);
+ this.Controls.Add(this.tabControlSup);
+ this.Controls.Add(this.lblStatus);
+ this.Name = "SupervisorMainForm";
+ this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
+ this.Text = "Supervisor";
+ this.tabControlSup.ResumeLayout(false);
+ this.tabAnalyse.ResumeLayout(false);
+ this.pnlChatInput.ResumeLayout(false);
+ this.pnlChatInput.PerformLayout();
+ this.toolStripChat.ResumeLayout(false);
+ this.toolStripChat.PerformLayout();
+ this.tabDossiers.ResumeLayout(false);
+ this.splitDossiers.Panel1.ResumeLayout(false);
+ this.splitDossiers.Panel2.ResumeLayout(false);
+ this.splitDossiers.Panel2.PerformLayout();
+ ((System.ComponentModel.ISupportInitialize)(this.splitDossiers)).EndInit();
+ this.splitDossiers.ResumeLayout(false);
+ ((System.ComponentModel.ISupportInitialize)(this.dgvSignals)).EndInit();
+ this.toolStripDossier.ResumeLayout(false);
+ this.toolStripDossier.PerformLayout();
+ this.tabBerichte.ResumeLayout(false);
+ this.splitReports.Panel1.ResumeLayout(false);
+ this.splitReports.Panel2.ResumeLayout(false);
+ this.splitReports.Panel2.PerformLayout();
+ ((System.ComponentModel.ISupportInitialize)(this.splitReports)).EndInit();
+ this.splitReports.ResumeLayout(false);
+ ((System.ComponentModel.ISupportInitialize)(this.dgvReports)).EndInit();
+ this.toolStripReports.ResumeLayout(false);
+ this.toolStripReports.PerformLayout();
+ this.ResumeLayout(false);
+ }
+
+ #endregion
+
+ private System.Windows.Forms.TabControl tabControlSup;
+ private System.Windows.Forms.TabPage tabAnalyse;
+ private System.Windows.Forms.ToolStrip toolStripChat;
+ private System.Windows.Forms.ToolStripLabel lblProfil;
+ private System.Windows.Forms.ToolStripComboBox cbProfile;
+ private System.Windows.Forms.ToolStripLabel lblModel;
+ private System.Windows.Forms.ToolStripTextBox tbModel;
+ private System.Windows.Forms.ToolStripSeparator sepChat;
+ private System.Windows.Forms.ToolStripButton btnClearChat;
+ private System.Windows.Forms.RichTextBox rtbChat;
+ private System.Windows.Forms.Panel pnlChatInput;
+ private System.Windows.Forms.TextBox tbChatInput;
+ private System.Windows.Forms.Button btnSend;
+ private System.Windows.Forms.TabPage tabDossiers;
+ private System.Windows.Forms.ToolStrip toolStripDossier;
+ private System.Windows.Forms.ToolStripButton btnDossierRefresh;
+ private System.Windows.Forms.ToolStripSeparator sepDossier;
+ private System.Windows.Forms.ToolStripLabel lblSignal;
+ private System.Windows.Forms.ToolStripTextBox tbSignalId;
+ private System.Windows.Forms.ToolStripButton btnDossierOpen;
+ private System.Windows.Forms.SplitContainer splitDossiers;
+ private System.Windows.Forms.DataGridView dgvSignals;
+ private System.Windows.Forms.TextBox tbDossier;
+ private System.Windows.Forms.TabPage tabBerichte;
+ private System.Windows.Forms.ToolStrip toolStripReports;
+ private System.Windows.Forms.ToolStripButton btnReportsRefresh;
+ private System.Windows.Forms.SplitContainer splitReports;
+ private System.Windows.Forms.DataGridView dgvReports;
+ private System.Windows.Forms.TextBox tbReport;
+ private System.Windows.Forms.Label lblStatus;
+ }
+}
diff --git a/src/PolyTrader.Modules.Supervisor/Ui/SupervisorMainForm.cs b/src/PolyTrader.Modules.Supervisor/Ui/SupervisorMainForm.cs
index c2611d8..b8bf0d9 100644
--- a/src/PolyTrader.Modules.Supervisor/Ui/SupervisorMainForm.cs
+++ b/src/PolyTrader.Modules.Supervisor/Ui/SupervisorMainForm.cs
@@ -4,110 +4,52 @@ using System.Windows.Forms;
using Microsoft.Extensions.DependencyInjection;
using PolyTrader.Core.Analytics;
using PolyTrader.Modules.Supervisor.Agent;
+using PolyTrader.Modules.Supervisor.Persistence;
using PolyTrader.Modules.Supervisor.Services;
namespace PolyTrader.Modules.Supervisor.Ui
{
///
- /// Hauptfenster des Supervisor-Moduls: Tab „Analyse" (Chat mit dem read-only-Agenten,
- /// Tool-Aufrufe transparent im Verlauf) und Tab „Dossiers" (Signal-Browser mit Markdown-Dossier).
- /// Code-only konstruiert (Muster RF-Modul); DB-/API-Zugriffe defensiv.
+ /// Hauptfenster des Supervisor-Moduls: Tab „Analyse" (Chat mit Profil-/Modellwahl, Tool-Aufrufe
+ /// transparent), Tab „Dossiers" (Signal-Browser), Tab „Berichte" (gespeicherte Analysen).
+ /// Layout im Designer (SupervisorMainForm.Designer.cs), Verhalten/Daten hier.
///
- public sealed class SupervisorMainForm : Form
+ public partial class SupervisorMainForm : Form
{
private DossierService? _dossiers;
private SupervisorAgent? _agent;
-
- // ----- Tab Analyse (Chat) -----
- private readonly ToolStrip _chatStrip = new();
- private readonly ToolStripLabel _lblModel = new() { Text = "Modell:" };
- private readonly ToolStripTextBox _tbModel = new() { AutoSize = false, Width = 220, Text = SupervisorAgent.DefaultModel };
- private readonly ToolStripButton _btnClearChat = new() { Text = "Verlauf leeren", DisplayStyle = ToolStripItemDisplayStyle.Text };
- private readonly RichTextBox _chatLog = new()
- {
- Dock = DockStyle.Fill, ReadOnly = true, BackColor = System.Drawing.Color.White,
- Font = new System.Drawing.Font("Segoe UI", 9.5f)
- };
- private readonly TextBox _chatInput = new()
- {
- Dock = DockStyle.Fill, Multiline = true, Height = 54,
- PlaceholderText = "Analyse-Frage stellen … (Strg+Enter zum Senden)"
- };
- private readonly Button _btnSend = new() { Text = "Senden", Dock = DockStyle.Right, Width = 110 };
-
- // ----- Tab Dossiers (Browser) -----
- private readonly ToolStrip _dossierStrip = new();
- private readonly ToolStripButton _btnRefresh = new() { Text = "Aktualisieren", DisplayStyle = ToolStripItemDisplayStyle.Text };
- private readonly ToolStripLabel _lblSearch = new() { Text = "SignalId:" };
- private readonly ToolStripTextBox _tbSignalId = new() { AutoSize = false, Width = 220 };
- private readonly ToolStripButton _btnOpen = new() { Text = "Dossier öffnen", DisplayStyle = ToolStripItemDisplayStyle.Text };
- private readonly SplitContainer _split = new() { Dock = DockStyle.Fill, SplitterDistance = 420 };
- private readonly DataGridView _grid = new()
- {
- Dock = DockStyle.Fill, ReadOnly = true, AllowUserToAddRows = false, AllowUserToDeleteRows = false,
- AutoGenerateColumns = true, SelectionMode = DataGridViewSelectionMode.FullRowSelect,
- RowHeadersVisible = false, MultiSelect = false
- };
- private readonly TextBox _dossierText = new()
- {
- Dock = DockStyle.Fill, Multiline = true, ReadOnly = true, ScrollBars = ScrollBars.Both,
- Font = new System.Drawing.Font("Consolas", 9.5f), WordWrap = false
- };
-
- private readonly Label _status = new() { Dock = DockStyle.Bottom, Height = 22, Padding = new Padding(6, 2, 6, 2), Text = "" };
+ private ISupervisorReportRepository? _reports;
public SupervisorMainForm()
{
- Text = "Supervisor";
- Width = 1250;
- Height = 720;
- StartPosition = FormStartPosition.CenterScreen;
+ InitializeComponent();
- var tabs = new TabControl { Dock = DockStyle.Fill };
+ foreach (var profile in SupervisorProfiles.All)
+ cbProfile.Items.Add(profile);
+ cbProfile.SelectedIndex = 0;
- // --- Tab Analyse ---
- var tabAnalyse = new TabPage("Analyse");
- _chatStrip.Items.AddRange(new ToolStripItem[] { _lblModel, _tbModel, new ToolStripSeparator(), _btnClearChat });
- var inputPanel = new Panel { Dock = DockStyle.Bottom, Height = 60, Padding = new Padding(4) };
- inputPanel.Controls.Add(_chatInput);
- inputPanel.Controls.Add(_btnSend);
- tabAnalyse.Controls.Add(_chatLog);
- tabAnalyse.Controls.Add(inputPanel);
- tabAnalyse.Controls.Add(_chatStrip);
- _chatStrip.Dock = DockStyle.Top;
-
- // --- Tab Dossiers ---
- var tabDossiers = new TabPage("Dossiers");
- _dossierStrip.Items.AddRange(new ToolStripItem[] { _btnRefresh, new ToolStripSeparator(), _lblSearch, _tbSignalId, _btnOpen });
- _split.Panel1.Controls.Add(_grid);
- _split.Panel2.Controls.Add(_dossierText);
- tabDossiers.Controls.Add(_split);
- tabDossiers.Controls.Add(_dossierStrip);
- _dossierStrip.Dock = DockStyle.Top;
-
- tabs.TabPages.AddRange(new[] { tabAnalyse, tabDossiers });
- Controls.Add(tabs);
- Controls.Add(_status);
-
- // Verhalten
- _btnSend.Click += async (_, _) => await SendQuestionAsync();
- _chatInput.KeyDown += async (_, e) =>
+ btnSend.Click += async (_, _) => await SendQuestionAsync();
+ tbChatInput.KeyDown += async (_, e) =>
{
if (e.Control && e.KeyCode == Keys.Enter) { e.SuppressKeyPress = true; await SendQuestionAsync(); }
};
- _btnClearChat.Click += (_, _) => _chatLog.Clear();
- _btnRefresh.Click += (_, _) => LoadSignals();
- _btnOpen.Click += (_, _) => OpenDossier(_tbSignalId.Text.Trim());
- _grid.SelectionChanged += (_, _) => OpenSelected();
+ btnClearChat.Click += (_, _) => rtbChat.Clear();
+ btnDossierRefresh.Click += (_, _) => LoadSignals();
+ btnDossierOpen.Click += (_, _) => OpenDossier(tbSignalId.Text.Trim());
+ dgvSignals.SelectionChanged += (_, _) => OpenSelectedSignal();
+ btnReportsRefresh.Click += (_, _) => LoadReports();
+ dgvReports.SelectionChanged += (_, _) => ShowSelectedReport();
}
public void Initialize(IServiceProvider services)
{
_dossiers = services.GetRequiredService();
_agent = services.GetRequiredService();
+ _reports = services.GetRequiredService();
LoadSignals();
- AppendChat("System", "Supervisor bereit. Read-only-Analyse über Entscheidungsjournal, Order-Events, Trades und Logs. " +
- "API-Key: POLYTRADER_OPENROUTER_KEY oder Datei openrouter.key.", System.Drawing.Color.Gray);
+ LoadReports();
+ AppendChat("System", "Supervisor bereit (read-only). API-Key: POLYTRADER_OPENROUTER_KEY oder Datei openrouter.key. " +
+ "MCP-Light für externe Clients: POLYTRADER_MCP_PORT setzen.", System.Drawing.Color.Gray);
}
// ===== Analyse-Chat =====
@@ -115,40 +57,66 @@ namespace PolyTrader.Modules.Supervisor.Ui
private async System.Threading.Tasks.Task SendQuestionAsync()
{
if (_agent == null) return;
- string question = _chatInput.Text.Trim();
+ string question = tbChatInput.Text.Trim();
if (question.Length == 0) return;
+ var profile = cbProfile.SelectedItem as SupervisorProfile ?? SupervisorProfiles.Allgemein;
- _chatInput.Text = "";
- _btnSend.Enabled = false;
- AppendChat("Du", question, System.Drawing.Color.DarkBlue);
- _status.Text = "Analyse läuft …";
+ tbChatInput.Text = "";
+ btnSend.Enabled = false;
+ AppendChat($"Du ({profile.Name})", question, System.Drawing.Color.DarkBlue);
+ lblStatus.Text = "Analyse läuft …";
var progress = new Progress(msg => AppendChat("Tool", msg, System.Drawing.Color.DarkGoldenrod));
try
{
+ string model = tbModel.Text;
var result = await System.Threading.Tasks.Task.Run(() =>
- _agent.AskAsync(question, _tbModel.Text, progress));
+ _agent.AskAsync(question, model, progress, profile));
AppendChat("Supervisor", result.Answer, System.Drawing.Color.Black);
- _status.Text = $"Fertig. {result.ToolInvocations.Count} Tool-Aufruf(e), ~{result.PromptTokens + result.CompletionTokens} Tokens.";
+ lblStatus.Text = $"Fertig. {result.ToolInvocations.Count} Tool-Aufruf(e), ~{result.PromptTokens + result.CompletionTokens} Tokens.";
+ SaveReport(profile, model, question, result);
}
catch (Exception ex)
{
AppendChat("Fehler", ex.Message, System.Drawing.Color.Firebrick);
- _status.Text = "Fehler bei der Analyse.";
+ lblStatus.Text = "Fehler bei der Analyse.";
}
finally
{
- _btnSend.Enabled = true;
+ btnSend.Enabled = true;
}
}
+ private void SaveReport(SupervisorProfile profile, string model, string question, AgentResult result)
+ {
+ try
+ {
+ var calls = new List