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

+39 -12
View File
@@ -21,6 +21,10 @@ registerPage('session', {
const workspaceEl = document.getElementById('sessionWorkspace');
const agentSelect = document.getElementById('agentSelect');
const agentSelectCard = document.getElementById('agentSelectCard');
const reviewAgentSelect = document.getElementById('reviewAgentSelect');
const reviewAgentSelectCard = document.getElementById('reviewAgentSelectCard');
const reviewIterationsRow = document.getElementById('reviewIterationsRow');
const maxReviewIterationsEl = document.getElementById('maxReviewIterations');
const imagePreview = document.getElementById('sessionImagePreview');
const saveBtn = document.getElementById('sessionSaveBtn');
const cancelBtn = document.getElementById('sessionCancelBtn');
@@ -83,33 +87,54 @@ registerPage('session', {
agentSkillsMap[a.agentId] = await apiGetAgentSkills(a.agentId);
}));
function renderAgentSelectCard(agent) {
agentSelectCard.innerHTML = '';
function renderAgentInCard(cardEl, agent, emptyIcon, emptyText) {
cardEl.innerHTML = '';
if (!agent) {
agentSelectCard.innerHTML = '<div class="agent-select-empty"><i class="fa fa-user-circle-o"></i> Kein Agent ausgewählt</div>';
cardEl.innerHTML = '<div class="agent-select-empty"><i class="fa ' + emptyIcon + '"></i> ' + emptyText + '</div>';
return;
}
const skills = agentSkillsMap[agent.agentId] || [];
const inner = buildAgentCard(agent, skills);
// Die inneren Elemente (card-image + card-body) in die Select-Card übernehmen
while (inner.firstChild) agentSelectCard.appendChild(inner.firstChild);
// Pencil-Icon hinzufügen
agentSelectCard.insertAdjacentHTML('beforeend', '<span class="agent-select-change"><i class="fa fa-pencil"></i></span>');
while (inner.firstChild) cardEl.appendChild(inner.firstChild);
cardEl.insertAdjacentHTML('beforeend', '<span class="agent-select-change"><i class="fa fa-pencil"></i></span>');
}
// Initiale Anzeige
function updateReviewVisibility() {
reviewIterationsRow.hidden = !reviewAgentSelect.value;
}
// Initiale Anzeige – Agent
if (session && session.agentId) {
agentSelect.value = session.agentId;
renderAgentSelectCard(agentMap[session.agentId] || null);
renderAgentInCard(agentSelectCard, agentMap[session.agentId] || null, 'fa-user-circle-o', 'Kein Agent ausgewählt');
} else {
renderAgentSelectCard(null);
renderAgentInCard(agentSelectCard, null, 'fa-user-circle-o', 'Kein Agent ausgewählt');
}
// Klick auf Card → Modal öffnen
// Initiale Anzeige – Review-Agent
if (session && session.reviewAgentId) {
reviewAgentSelect.value = session.reviewAgentId;
renderAgentInCard(reviewAgentSelectCard, agentMap[session.reviewAgentId] || null, 'fa-check-circle-o', 'Kein Review-Agent');
maxReviewIterationsEl.value = session.maxReviewIterations || 3;
} else {
renderAgentInCard(reviewAgentSelectCard, null, 'fa-check-circle-o', 'Kein Review-Agent');
}
updateReviewVisibility();
// Klick auf Agent-Card → Modal öffnen
agentSelectCard.addEventListener('click', () => {
openAgentPickerModal(agents, agentSkillsMap, agentSelect.value, (selectedId) => {
agentSelect.value = selectedId || '';
renderAgentSelectCard(selectedId ? agentMap[selectedId] : null);
renderAgentInCard(agentSelectCard, selectedId ? agentMap[selectedId] : null, 'fa-user-circle-o', 'Kein Agent ausgewählt');
});
});
// Klick auf Review-Agent-Card → Modal öffnen
reviewAgentSelectCard.addEventListener('click', () => {
openAgentPickerModal(agents, agentSkillsMap, reviewAgentSelect.value, (selectedId) => {
reviewAgentSelect.value = selectedId || '';
renderAgentInCard(reviewAgentSelectCard, selectedId ? agentMap[selectedId] : null, 'fa-check-circle-o', 'Kein Review-Agent');
updateReviewVisibility();
});
});
@@ -160,6 +185,8 @@ registerPage('session', {
description: descEl.value.trim(),
image: _sessionAvatarEditor.getImage(),
agentId: agentSelect.value || null,
reviewAgentId: reviewAgentSelect.value || null,
maxReviewIterations: parseInt(maxReviewIterationsEl.value) || 3,
context: _sessionContextMde.value().trim() || null,
workspace: workspaceEl.value.trim() || null,
customCommands: commands,