diff --git a/Endpoints/ChatEndpoint.cs b/Endpoints/ChatEndpoint.cs index 04e2edb..9be98d7 100644 --- a/Endpoints/ChatEndpoint.cs +++ b/Endpoints/ChatEndpoint.cs @@ -135,6 +135,50 @@ public static class ChatEndpoint res.Headers["Cache-Control"] = "no-cache"; res.Headers["Connection"] = "keep-alive"; + // ── Preparer-Agent Phase (vor dem Worker) ── + if (ctx.Preparer != null && !isChoiceResponse) + { + var preparerResult = await anthropic.RunPreparer( + res, ctx.Preparer, messages, ctx.ChainId, ctx.Workspace, sessionId, cancellationToken); + + if (!preparerResult.ShouldForward) + { + // Preparer hat eine Rückfrage → Antwort als Text streamen, Flow beenden + // Preparer-Antwort wird schon in RunAgentChat in DB gespeichert + await AnthropicService.WriteSseEvent(res, "done", new + { + chainId = ctx.ChainId, + stopReason = "preparer_query", + planMode = ctx.PlanMode + }, cancellationToken); + return; + } + + // Preparer ist zufrieden → aufbereiteten Prompt als zusätzlichen Kontext an die History anhängen + if (!string.IsNullOrWhiteSpace(preparerResult.ForwardedPrompt)) + { + // Den aufbereiteten Prompt als System-Context-Message einfügen + messages.Add(new Dictionary + { + ["role"] = "user", + ["content"] = new List + { + new { type = "text", text = $"[Vorbereitet von {ctx.Preparer.Agent?.Name ?? "Vorbereiter-Agent"}]:\n{preparerResult.ForwardedPrompt}" } + } + }); + + // Auch in DB speichern + await db.SaveMessage(new MessageRecord + { + ChainId = ctx.ChainId, + Iteration = 0, + Role = "user", + Type = "text", + Content = $"[Vorbereitet von {ctx.Preparer.Agent?.Name ?? "Vorbereiter-Agent"}]:\n{preparerResult.ForwardedPrompt}" + }); + } + } + // Bei Choice-Response: toolChoice auf "auto" setzen, damit Claude frei antworten kann var effectiveToolChoice = isChoiceResponse ? "auto" : ctx.ToolChoice; diff --git a/Endpoints/SessionEndpoint.cs b/Endpoints/SessionEndpoint.cs index a6131de..8b2f2d3 100644 --- a/Endpoints/SessionEndpoint.cs +++ b/Endpoints/SessionEndpoint.cs @@ -82,8 +82,9 @@ public static class SessionEndpoint var reviewAgentId = body.TryGetProperty("reviewAgentId", out var ra) ? ra.GetString() : null; var maxReviewIterations = body.TryGetProperty("maxReviewIterations", out var mri) ? mri.GetInt32() : 3; var finalizerAgentId = body.TryGetProperty("finalizerAgentId", out var fa) ? fa.GetString() : null; + var preparerAgentId = body.TryGetProperty("preparerAgentId", out var pra) ? pra.GetString() : null; - var session = await db.CreateSession(userId, name, description, image, agentId, workspace, context, customCommands, avatarScale, avatarOffsetX, avatarOffsetY, reviewAgentId, maxReviewIterations, finalizerAgentId); + var session = await db.CreateSession(userId, name, description, image, agentId, workspace, context, customCommands, avatarScale, avatarOffsetX, avatarOffsetY, reviewAgentId, maxReviewIterations, finalizerAgentId, preparerAgentId); await ctx.Response.WriteAsJsonAsync(session); } diff --git a/Models/ChatContext.cs b/Models/ChatContext.cs index a87c406..1becd3c 100644 --- a/Models/ChatContext.cs +++ b/Models/ChatContext.cs @@ -19,6 +19,9 @@ public class ChatContext public SessionRecord Session { get; set; } public bool PlanMode { get; set; } + // Preparer-Agent (vor dem Worker) + public PreparerContext Preparer { get; set; } + // Review-Agent public ReviewContext Review { get; set; } @@ -26,6 +29,19 @@ public class ChatContext public FinalizerContext Finalizer { get; set; } } +public class PreparerContext +{ + public string ModelId { get; set; } + public List SystemPrompt { get; set; } + public string AgentId { get; set; } + public string Workspace { get; set; } + public bool UseMemory { get; set; } + public string WebSearchVersion { get; set; } + public string ToolChoice { get; set; } + public bool AllowUserChoice { get; set; } + public AgentRecord Agent { get; set; } +} + public class FinalizerContext { public string ModelId { get; set; } diff --git a/Models/SessionRecord.cs b/Models/SessionRecord.cs index 81dc277..15d3f05 100644 --- a/Models/SessionRecord.cs +++ b/Models/SessionRecord.cs @@ -23,6 +23,7 @@ public class SessionRecord public string ReviewAgentId { get; set; } public int MaxReviewIterations { get; set; } = 3; public string FinalizerAgentId { get; set; } + public string PreparerAgentId { get; set; } public string CreatedAt { get; set; } public string UpdatedAt { get; set; } } diff --git a/Services/AnthropicService.cs b/Services/AnthropicService.cs index 1b57550..246aba0 100644 --- a/Services/AnthropicService.cs +++ b/Services/AnthropicService.cs @@ -314,6 +314,82 @@ public class AnthropicService } } + // ── Preparer-Agent Logik ── + + public class PreparerResult + { + public bool ShouldForward { get; set; } + public string ForwardedPrompt { get; set; } + public string ResponseText { get; set; } + public int IterationCount { get; set; } + } + + /// + /// Führt den Preparer-Agent aus. Der Preparer bekommt die User-Nachricht und die bisherige Konversation. + /// Antwortet er mit [FORWARD] → der Text nach [FORWARD] wird an den Session-Agent weitergegeben. + /// Sonst → seine Antwort wird als Rückfrage an den User gestreamt. + /// + public async Task RunPreparer( + HttpResponse response, + PreparerContext preparer, + List historyMessages, + string chainId, + string workspace, + string sessionId, + CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + await TryWriteSse(response, "preparer_start", new + { + agentId = preparer.AgentId, + agentName = preparer.Agent?.Name ?? "Vorbereiter-Agent" + }, cancellationToken); + + // Preparer bekommt die History als Kontext + var preparerMessages = new List(historyMessages); + + var usage = new TokenUsage(); + var (text, iterations) = await RunAgentChat( + response, preparer.ModelId, preparer.SystemPrompt, preparer.AgentId, + preparer.Workspace, preparer.UseMemory, preparer.WebSearchVersion, + preparer.ToolChoice, preparerMessages, chainId, sessionId, usage, + 0, "preparer", preparer.Agent?.Name ?? "Vorbereiter-Agent", + cancellationToken); + + var result = new PreparerResult { IterationCount = iterations, ResponseText = text }; + + // Prüfe ob [FORWARD] enthalten ist + if (!string.IsNullOrEmpty(text) && text.Contains("[FORWARD]")) + { + var idx = text.IndexOf("[FORWARD]"); + var forwardedText = text.Substring(idx + "[FORWARD]".Length).Trim(); + + result.ShouldForward = true; + result.ForwardedPrompt = forwardedText; + + await TryWriteSse(response, "preparer_done", new + { + agentName = preparer.Agent?.Name ?? "Vorbereiter-Agent", + forwarded = true + }, cancellationToken); + } + else + { + // Rückfrage an User – Text streamen + result.ShouldForward = false; + + await TryWriteSse(response, "preparer_done", new + { + agentName = preparer.Agent?.Name ?? "Vorbereiter-Agent", + forwarded = false, + responseText = text + }, cancellationToken); + } + + return result; + } + // ── Review-Agent Logik ── private class ReviewResult @@ -525,7 +601,7 @@ public class AnthropicService int iterations = 0; var finalText = new StringBuilder(); int maxToolIterations = 20; - var tag = agentType == "finalizer" ? "Finalizer" : "Review"; + var tag = agentType == "preparer" ? "Preparer" : agentType == "finalizer" ? "Finalizer" : "Review"; while (iterations < maxToolIterations) { diff --git a/Services/ChatService.cs b/Services/ChatService.cs index 385b04f..35e1340 100644 --- a/Services/ChatService.cs +++ b/Services/ChatService.cs @@ -107,6 +107,81 @@ public class ChatService if (!string.IsNullOrWhiteSpace(systemPromptText)) systemPrompt.Add(new { type = "text", text = systemPromptText }); + // Preparer-Agent vorbereiten (falls konfiguriert) + PreparerContext preparerContext = null; + if (!string.IsNullOrEmpty(session.PreparerAgentId)) + { + var preparerAgent = await _db.GetAgent(session.PreparerAgentId); + if (preparerAgent != null) + { + var preparerModelId = ResolveModelId(preparerAgent.Model); + var preparerMarked = await _db.GetMarkedMessages(sessionId); + var preparerMemDir = _memoryService.GetAgentMemoryDir(preparerAgent.AgentId); + var preparerSkills = await _db.GetAgentSkills(preparerAgent.AgentId); + var preparerWorkspace = workspace; + var preparerSystemText = SystemPromptBuilder.Build( + preparerAgent, session, preparerMarked, _memoryService, preparerMemDir, + preparerSkills, _skillService, userPersona: user?.GetEffectivePersona(), + resolvedWorkspace: preparerWorkspace); + + var preparerPrompt = new List(); + if (!string.IsNullOrWhiteSpace(preparerSystemText)) + preparerPrompt.Add(new { type = "text", text = preparerSystemText }); + + // Preparer-Instruktion: erklärt [FORWARD]-Mechanismus + preparerPrompt.Add(new { type = "text", text = @"## Deine Rolle als Vorbereiter-Agent +Du bist ein Vorbereiter-Agent. Deine Aufgabe ist es, die Anfrage des Users zu analysieren, vorzubereiten und – wenn möglich – Teile der Aufgabe bereits selbst zu erledigen, BEVOR das Ergebnis an den eigentlichen Session-Agent weitergegeben wird. + +### Nutze deine Fähigkeiten aktiv! +Du hast eigene Fähigkeiten und Tools. Nutze sie! Wenn du Teile der Aufgabe selbst erledigen kannst (z.B. Dateien lesen, Daten aufbereiten, Strukturen erstellen, Recherchen durchführen), dann tu das BEVOR du weiterleitest. Der Session-Agent soll von dir ein möglichst gut vorbereitetes Paket bekommen. + +### Dein Ablauf: +1. **Analysiere** die User-Anfrage +2. **Stelle Rückfragen**, wenn die Anfrage unklar, unvollständig oder mehrdeutig ist (antworte einfach mit deiner Frage als normaler Text) +3. **Erledige** was du kannst – nutze deine Fähigkeiten und Tools, um Vorarbeit zu leisten +4. **Leite weiter** mit dem Ergebnis deiner Arbeit + +### Weiterleitung: +Wenn du fertig bist, schreibe `[FORWARD]` gefolgt von dem aufbereiteten Ergebnis/Prompt, der an den Session-Agent weitergegeben werden soll. Alles nach `[FORWARD]` wird als Prompt an den Session-Agent übergeben. + +Beispiel – Anfrage nur verfeinert: +``` +[FORWARD] +Der User möchte eine REST-API mit folgenden Endpunkten erstellen: +- GET /users – Liste aller User +- POST /users – Neuen User anlegen +Technologie: .NET 10, SQLite +``` + +Beispiel – Vorarbeit geleistet: +``` +[FORWARD] +Der User möchte Produktdaten als JSON. Ich habe das JSON-Objekt bereits erstellt: +{""products"": [{""id"": 1, ""name"": ""Widget""}, {""id"": 2, ""name"": ""Gadget""}]} +Bitte speichere es in die Datei products.json im Workspace. +``` + +### Wichtige Regeln: +- Antworte OHNE `[FORWARD]` wenn du Rückfragen hast → der User sieht deine Antwort und kann darauf reagieren +- Antworte MIT `[FORWARD]` wenn du fertig bist → der Text nach [FORWARD] wird an den Session-Agent weitergegeben +- Nutze deine Tools und Fähigkeiten aktiv, um dem Session-Agent möglichst viel Arbeit abzunehmen +- Sei effizient: Wenn die Anfrage offensichtlich klar ist und du nichts vorbereiten kannst, leite sofort weiter" }); + + preparerContext = new PreparerContext + { + ModelId = preparerModelId, + SystemPrompt = preparerPrompt, + AgentId = preparerAgent.AgentId, + Workspace = preparerWorkspace, + UseMemory = preparerAgent.UseMemory, + WebSearchVersion = preparerAgent.WebSearchVersion, + ToolChoice = preparerAgent.ToolChoice ?? "any", + AllowUserChoice = preparerAgent.AllowUserChoice, + Agent = preparerAgent + }; + } + } + // Review-Agent vorbereiten (falls konfiguriert) ReviewContext reviewContext = null; if (!string.IsNullOrEmpty(session.ReviewAgentId)) @@ -193,6 +268,7 @@ public class ChatService MaxIterations = maxIterations, Agent = agent, Session = session, + Preparer = preparerContext, Review = reviewContext, Finalizer = finalizerContext, PlanMode = planMode diff --git a/Services/DatabaseService.cs b/Services/DatabaseService.cs index cff2cec..ba2d74d 100644 --- a/Services/DatabaseService.cs +++ b/Services/DatabaseService.cs @@ -180,6 +180,7 @@ public class DatabaseService TryAddColumn(conn, "sessions", "finalizer_agent_id", "TEXT"); TryAddColumn(conn, "agents", "allow_user_choice", "INTEGER NOT NULL DEFAULT 1"); TryAddColumn(conn, "chains", "plan_mode", "INTEGER NOT NULL DEFAULT 0"); + TryAddColumn(conn, "sessions", "preparer_agent_id", "TEXT"); conn.Execute(@" CREATE TABLE IF NOT EXISTS agent_skills ( @@ -377,7 +378,7 @@ public class DatabaseService // ── Sessions ── - public async Task CreateSession(string userId, string name = null, string description = null, string image = null, string agentId = null, string workspace = null, string context = null, string customCommands = null, double avatarScale = 1.0, double avatarOffsetX = 0, double avatarOffsetY = 0, string reviewAgentId = null, int maxReviewIterations = 3, string finalizerAgentId = null) + public async Task CreateSession(string userId, string name = null, string description = null, string image = null, string agentId = null, string workspace = null, string context = null, string customCommands = null, double avatarScale = 1.0, double avatarOffsetX = 0, double avatarOffsetY = 0, string reviewAgentId = null, int maxReviewIterations = 3, string finalizerAgentId = null, string preparerAgentId = null) { using var conn = CreateConnection(); var now = DateTime.UtcNow.ToString("o"); @@ -398,12 +399,13 @@ public class DatabaseService ReviewAgentId = reviewAgentId, MaxReviewIterations = maxReviewIterations, FinalizerAgentId = finalizerAgentId, + PreparerAgentId = preparerAgentId, CreatedAt = now, UpdatedAt = now }; await conn.ExecuteAsync(@" - INSERT INTO sessions (session_id, user_id, name, description, image, agent_id, workspace, context, custom_commands, avatar_scale, avatar_offset_x, avatar_offset_y, review_agent_id, max_review_iterations, finalizer_agent_id, created_at, updated_at) - VALUES (@SessionId, @UserId, @Name, @Description, @Image, @AgentId, @Workspace, @Context, @CustomCommands, @AvatarScale, @AvatarOffsetX, @AvatarOffsetY, @ReviewAgentId, @MaxReviewIterations, @FinalizerAgentId, @CreatedAt, @UpdatedAt) + INSERT INTO sessions (session_id, user_id, name, description, image, agent_id, workspace, context, custom_commands, avatar_scale, avatar_offset_x, avatar_offset_y, review_agent_id, max_review_iterations, finalizer_agent_id, preparer_agent_id, created_at, updated_at) + VALUES (@SessionId, @UserId, @Name, @Description, @Image, @AgentId, @Workspace, @Context, @CustomCommands, @AvatarScale, @AvatarOffsetX, @AvatarOffsetY, @ReviewAgentId, @MaxReviewIterations, @FinalizerAgentId, @PreparerAgentId, @CreatedAt, @UpdatedAt) ", session); return session; } @@ -452,6 +454,7 @@ public class DatabaseService s.ReviewAgentId, s.MaxReviewIterations, s.FinalizerAgentId, + s.PreparerAgentId, s.IsDeleted, s.CreatedAt, s.UpdatedAt, @@ -507,6 +510,7 @@ public class DatabaseService if (body.TryGetProperty("reviewAgentId", out var ra)) { sets.Add("review_agent_id = @reviewAgentId"); p.Add("reviewAgentId", ra.ValueKind == System.Text.Json.JsonValueKind.Null ? null : ra.GetString()); } if (body.TryGetProperty("maxReviewIterations", out var mri)) { sets.Add("max_review_iterations = @maxReviewIterations"); p.Add("maxReviewIterations", mri.GetInt32()); } if (body.TryGetProperty("finalizerAgentId", out var fa)) { sets.Add("finalizer_agent_id = @finalizerAgentId"); p.Add("finalizerAgentId", fa.ValueKind == System.Text.Json.JsonValueKind.Null ? null : fa.GetString()); } + if (body.TryGetProperty("preparerAgentId", out var pa)) { sets.Add("preparer_agent_id = @preparerAgentId"); p.Add("preparerAgentId", pa.ValueKind == System.Text.Json.JsonValueKind.Null ? null : pa.GetString()); } await conn.ExecuteAsync($"UPDATE sessions SET {string.Join(", ", sets)} WHERE session_id = @sessionId", p); } diff --git a/wwwroot/css/chat.css b/wwwroot/css/chat.css index 9a624e8..2167ecb 100644 --- a/wwwroot/css/chat.css +++ b/wwwroot/css/chat.css @@ -1073,6 +1073,17 @@ color: var(--primary); } +.preparer-inline-header { + color: var(--info); +} + +.preparer-forwarded-header { + color: var(--info); + font-size: .85em; + margin-bottom: .3em; + opacity: .8; +} + .finalizer-bubble .review-header { color: var(--primary); } @@ -1081,6 +1092,14 @@ border-color: var(--primary); } +.preparer-bubble .review-header { + color: var(--info, #17a2b8); +} + +.preparer-bubble { + border-color: var(--info, #17a2b8); +} + .review-inline-feedback { color: var(--warning); margin-bottom: .4em; diff --git a/wwwroot/index.html b/wwwroot/index.html index ef5e69d..35efff3 100644 --- a/wwwroot/index.html +++ b/wwwroot/index.html @@ -121,6 +121,11 @@ + +
+
Kein Vorbereiter-Agent
+
+
Kein Review-Agent
diff --git a/wwwroot/js/chat.js b/wwwroot/js/chat.js index 1f5a01b..6444dca 100644 --- a/wwwroot/js/chat.js +++ b/wwwroot/js/chat.js @@ -77,6 +77,23 @@ function handleStreamEvent(eventType, data) { addChoiceBubble(data.toolCallId, data.question, data.choices); break; + case 'preparer_start': + endStreamBubble(); + addReviewBubble('start', data, 'preparer'); + break; + + case 'preparer_text': + appendToReviewText(data.text); + break; + + case 'preparer_tool_use': + addReviewToolUse(data); + break; + + case 'preparer_done': + finalizeReviewBubble(data.forwarded ? 'forwarded' : 'query', data, 'preparer'); + break; + case 'review_start': endStreamBubble(); addReviewBubble('start', data); diff --git a/wwwroot/js/pages/session-page.js b/wwwroot/js/pages/session-page.js index f6d2495..a7615a4 100644 --- a/wwwroot/js/pages/session-page.js +++ b/wwwroot/js/pages/session-page.js @@ -21,6 +21,8 @@ registerPage('session', { const workspaceEl = document.getElementById('sessionWorkspace'); const agentSelect = document.getElementById('agentSelect'); const agentSelectCard = document.getElementById('agentSelectCard'); + const preparerAgentSelect = document.getElementById('preparerAgentSelect'); + const preparerAgentSelectCard = document.getElementById('preparerAgentSelectCard'); const reviewAgentSelect = document.getElementById('reviewAgentSelect'); const reviewAgentSelectCard = document.getElementById('reviewAgentSelectCard'); const reviewIterationsRow = document.getElementById('reviewIterationsRow'); @@ -115,6 +117,14 @@ registerPage('session', { renderAgentInCard(agentSelectCard, null, 'fa-user-circle-o', 'Kein Agent ausgewählt'); } + // Initiale Anzeige – Preparer-Agent + if (session && session.preparerAgentId) { + preparerAgentSelect.value = session.preparerAgentId; + renderAgentInCard(preparerAgentSelectCard, agentMap[session.preparerAgentId] || null, 'fa-filter', 'Kein Vorbereiter-Agent'); + } else { + renderAgentInCard(preparerAgentSelectCard, null, 'fa-filter', 'Kein Vorbereiter-Agent'); + } + // Initiale Anzeige – Review-Agent if (session && session.reviewAgentId) { reviewAgentSelect.value = session.reviewAgentId; @@ -141,6 +151,14 @@ registerPage('session', { }); }); + // Klick auf Preparer-Agent-Card → Modal öffnen + preparerAgentSelectCard.addEventListener('click', () => { + openAgentPickerModal(agents, agentSkillsMap, preparerAgentSelect.value, (selectedId) => { + preparerAgentSelect.value = selectedId || ''; + renderAgentInCard(preparerAgentSelectCard, selectedId ? agentMap[selectedId] : null, 'fa-filter', 'Kein Vorbereiter-Agent'); + }); + }); + // Klick auf Review-Agent-Card → Modal öffnen reviewAgentSelectCard.addEventListener('click', () => { openAgentPickerModal(agents, agentSkillsMap, reviewAgentSelect.value, (selectedId) => { @@ -208,6 +226,7 @@ registerPage('session', { reviewAgentId: reviewAgentSelect.value || null, maxReviewIterations: parseInt(maxReviewIterationsEl.value) || 3, finalizerAgentId: finalizerAgentSelect.value || null, + preparerAgentId: preparerAgentSelect.value || null, context: _sessionContextMde.value().trim() || null, workspace: workspaceEl.value.trim() || null, customCommands: commands, diff --git a/wwwroot/js/ui.js b/wwwroot/js/ui.js index 19907ee..4f258f9 100644 --- a/wwwroot/js/ui.js +++ b/wwwroot/js/ui.js @@ -185,13 +185,24 @@ function renderMarkdown(text) { return ' ' + esc(agentName.trim()) + ' '; }); + // 4a3) [Preparer: AgentName] prefix → Preparer-Header + html = html.replace(/\[Preparer:\s*([^\]]+)\]\s*/g, function(match, agentName) { + return ' ' + esc(agentName.trim()) + ' '; + }); + + // 4a4) [Vorbereitet von AgentName]: → Preparer-Forwarded-Header + html = html.replace(/\[Vorbereitet von\s+([^\]]+)\]:\s*/g, function(match, agentName) { + return '
Vorbereitet von ' + esc(agentName.trim()) + '
'; + }); + // 4b) [Review-Feedback von AgentName (Runde X/Y)]: → Review-Feedback-Header html = html.replace(/\[Review-Feedback von\s+([^\(]+)\(Runde\s+(\d+)\/(\d+)\)\]:\s*/g, function(match, agentName, round, maxRounds) { return '
Feedback von ' + esc(agentName.trim()) + ' Runde ' + esc(round) + '/' + esc(maxRounds) + '
'; }); - // 4c) [APPROVED] ausblenden + // 4c) [APPROVED] und [FORWARD] ausblenden html = html.replace(/\[APPROVED\]/g, ''); + html = html.replace(/\[FORWARD\]\s*/g, ''); // 4d) [Krea: dateiname.png] → Inline-Thumbnail mit Klick für Vollbild html = html.replace(/\[Krea:\s*([^\]]+)\]/g, function(match, filename) { @@ -767,9 +778,12 @@ function addReviewBubble(type, data, agentType) { _reviewBubble = document.createElement('div'); _reviewBubble.className = 'msg review-bubble streamed'; if (agentType === 'finalizer') _reviewBubble.classList.add('finalizer-bubble'); + if (agentType === 'preparer') _reviewBubble.classList.add('preparer-bubble'); const header = document.createElement('div'); header.className = 'review-header'; - if (agentType === 'finalizer') { + if (agentType === 'preparer') { + header.innerHTML = ' ' + esc(data.agentName || 'Vorbereiter-Agent') + ''; + } else if (agentType === 'finalizer') { header.innerHTML = ' ' + esc(data.agentName || 'Finalizer-Agent') + ''; } else { header.innerHTML = ' ' + esc(data.agentName || 'Review-Agent') + ' Runde ' + data.round + '/' + data.maxRounds + ''; @@ -803,7 +817,13 @@ function finalizeReviewBubble(type, data, agentType) { if (!_reviewBubble) return; const status = document.createElement('div'); status.className = 'review-status'; - if (agentType === 'finalizer' || type === 'done') { + if (agentType === 'preparer' && type === 'forwarded') { + status.className += ' review-approved'; + status.innerHTML = ' Weitergeleitet an Session-Agent'; + } else if (agentType === 'preparer' && type === 'query') { + status.className += ' review-rejected'; + status.innerHTML = ' Rückfrage an User'; + } else if (agentType === 'finalizer' || type === 'done') { status.className += ' review-approved'; status.innerHTML = ' Abgeschlossen'; } else if (type === 'approved') {