feat: Review-Agent (Multi-Agent-Kommunikation)

- Session hat optionalen ReviewAgentId + MaxReviewIterations
- Nach end_turn des Worker-Agents wird der Review-Agent aufgerufen
- Review-Agent bekommt Worker-Konversation als Kontext + kann Tools nutzen
- [APPROVED] in Review-Antwort → fertig, sonst Feedback an Worker
- Worker korrigiert und wird erneut reviewed (max N Runden)
- SSE-Events: review_start, review_text, review_tool_use, review_feedback, review_approved
- Chat-UI: Review-Phase als eigene Bubble mit Header, Text, Status
- Session-Page: Review-Agent-Picker (gleicher Agent-Card-Helper)
- Dashboard: Review-Agent-Badge auf Session-Cards
- DB-Migration: review_agent_id, max_review_iterations auf sessions
This commit is contained in:
Meleki committed 2026-03-06 16:04:00 +01:00
1 parent 2598656f1c
commit 5fe3f8ac87
12 files changed
+619 -18

No files matched your search

+56
View File
@@ -13,6 +13,7 @@ function buildSessionCard(session, agent) {
// Badges
const badges = [];
if (agent) badges.push(`<span class="badge">${esc(agent.name)}</span>`);
if (session.reviewAgentId) badges.push(`<span class="badge badge-feature" title="Review-Agent"><i class="fa fa-check-circle-o"></i></span>`);
if (session.context) badges.push(`<span class="badge badge-feature" title="${esc(session.context)}"><i class="fa fa-file-text-o"></i></span>`);
if (session.telegramConfigured) badges.push(`<span class="badge ${session.telegramLinked ? 'badge-feature' : 'badge-off'}" title="Telegram ${session.telegramLinked ? 'verbunden' : 'konfiguriert'}"><i class="fa fa-paper-plane"></i></span>`);
@@ -637,6 +638,61 @@ function addWarningBubble(text, streamed = false) {
scrollDown();
}
// ── Review Bubbles ──
let _reviewBubble = null;
let _reviewTextEl = null;
let _reviewText = '';
function addReviewBubble(type, data) {
_reviewBubble = document.createElement('div');
_reviewBubble.className = 'msg review-bubble streamed';
const header = document.createElement('div');
header.className = 'review-header';
header.innerHTML = '<i class="fa fa-check-circle-o"></i> <strong>' + esc(data.agentName || 'Review-Agent') + '</strong> <span class="review-round">Runde ' + data.round + '/' + data.maxRounds + '</span>';
_reviewBubble.appendChild(header);
_reviewTextEl = document.createElement('div');
_reviewTextEl.className = 'review-content markdown';
_reviewBubble.appendChild(_reviewTextEl);
_reviewText = '';
chatEl().appendChild(_reviewBubble);
scrollDown();
}
function appendToReviewText(text) {
if (!_reviewBubble) return;
_reviewText += text;
_reviewTextEl.innerHTML = renderMarkdown(_reviewText);
scrollDown();
}
function addReviewToolUse(data) {
if (!_reviewBubble) return;
const toolDiv = document.createElement('div');
toolDiv.className = 'review-tool-use';
toolDiv.innerHTML = '<i class="fa fa-wrench"></i> <span class="tool-name">' + esc(data.toolName) + '</span>';
_reviewBubble.appendChild(toolDiv);
scrollDown();
}
function finalizeReviewBubble(type, data) {
if (!_reviewBubble) return;
const status = document.createElement('div');
status.className = 'review-status';
if (type === 'approved') {
status.className += ' review-approved';
status.innerHTML = '<i class="fa fa-check"></i> Approved';
} else {
status.className += ' review-rejected';
status.innerHTML = '<i class="fa fa-repeat"></i> Feedback – Worker korrigiert...';
}
_reviewBubble.appendChild(status);
_reviewBubble = null;
_reviewTextEl = null;
_reviewText = '';
scrollDown();
}
// ── Streaming Bubble ──
let _streamDiv = null;