Add multi-model panel with Fusion mode
Panel agents answer the user prompt in parallel (read-only, non-streaming); their responses are merged into the worker's last user message as a <panel_responses> block, and the session agent synthesizes one final streamed answer. The RunPanel engine is built layer-ready for future MoA. - New session fields: multi_model_mode (off/fusion), panel_agent_ids (CSV) with idempotent column migration - New partial Services/AnthropicService.Panel.cs holding the RunPanel engine - Provider-agnostic members: Anthropic RunNonStreamingToolLoop / OpenAI CompleteText / CLI plan-mode; all forced read-only, persisted with Tag=panel - SSE events: panel_start, panel_member_start, panel_member_done, panel_done - Frontend: multi-agent picker, live panel bubbles, history rendering - Graceful degradation: panel errors fall back to worker-only Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
b23eba32b4
commit
268f11fa6d
20 files changed
+647
-35
No files matched your search
@@ -29,6 +29,10 @@ registerPage('session', {
|
||||
const maxReviewIterationsEl = document.getElementById('maxReviewIterations');
|
||||
const finalizerAgentSelect = document.getElementById('finalizerAgentSelect');
|
||||
const finalizerAgentSelectCard = document.getElementById('finalizerAgentSelectCard');
|
||||
const panelModeEl = document.getElementById('multiModelMode');
|
||||
const panelAgentsRow = document.getElementById('panelAgentsRow');
|
||||
const panelAgentsCard = document.getElementById('panelAgentsCard');
|
||||
const panelAgentIdsEl = document.getElementById('panelAgentIds');
|
||||
const imagePreview = document.getElementById('sessionImagePreview');
|
||||
const saveBtn = document.getElementById('sessionSaveBtn');
|
||||
const cancelBtn = document.getElementById('sessionCancelBtn');
|
||||
@@ -126,6 +130,38 @@ registerPage('session', {
|
||||
updateReviewVisibility();
|
||||
setupAgentPicker(finalizerAgentSelectCard, finalizerAgentSelect, session && session.finalizerAgentId, 'fa-flag-checkered', 'Kein Finalizer-Agent');
|
||||
|
||||
// Multi-Modell-Panel (Fusion): Modus + Mehrfach-Agenten-Picker
|
||||
function renderPanelInCard() {
|
||||
var ids = (panelAgentIdsEl.value || '').split(',').map(function(s){ return s.trim(); }).filter(Boolean);
|
||||
if (ids.length === 0) {
|
||||
panelAgentsCard.innerHTML = '<div class="agent-select-empty"><i class="fa fa-users"></i> Keine Panel-Agenten</div>';
|
||||
return;
|
||||
}
|
||||
var chips = ids.map(function(id) {
|
||||
var a = agentMap[id];
|
||||
var label = a ? a.name : id.slice(0, 8);
|
||||
var model = a ? ((typeof prettyModelName === 'function') ? prettyModelName(a.model) : a.model) : '';
|
||||
return '<span class="badge">' + esc(label) + (model ? ' · ' + esc(model) : '') + '</span>';
|
||||
}).join(' ');
|
||||
panelAgentsCard.innerHTML = '<div style="display:flex;flex-wrap:wrap;gap:4px;align-items:center">' + chips + '</div>'
|
||||
+ '<span class="agent-select-change"><i class="fa fa-pencil"></i></span>';
|
||||
}
|
||||
function updatePanelVisibility() {
|
||||
panelAgentsRow.hidden = (panelModeEl.value === 'off');
|
||||
}
|
||||
panelModeEl.value = (session && session.multiModelMode) || 'off';
|
||||
panelAgentIdsEl.value = (session && session.panelAgentIds) || '';
|
||||
renderPanelInCard();
|
||||
updatePanelVisibility();
|
||||
panelModeEl.addEventListener('change', updatePanelVisibility);
|
||||
panelAgentsCard.addEventListener('click', function() {
|
||||
var currentIds = (panelAgentIdsEl.value || '').split(',').map(function(s){ return s.trim(); }).filter(Boolean);
|
||||
openMultiAgentPickerModal(agents, agentSkillsMap, currentIds, function(newIds) {
|
||||
panelAgentIdsEl.value = newIds.join(',');
|
||||
renderPanelInCard();
|
||||
});
|
||||
});
|
||||
|
||||
// User-Zuweisung (jeder Besitzer, nur für bestehende Sessions)
|
||||
if (!isNew) {
|
||||
initSessionUserPicker(ownerRow, userSelectCard, userSelect, session && session.userId);
|
||||
@@ -177,6 +213,8 @@ registerPage('session', {
|
||||
maxReviewIterations: parseInt(maxReviewIterationsEl.value) || 3,
|
||||
finalizerAgentId: finalizerAgentSelect.value || null,
|
||||
preparerAgentId: preparerAgentSelect.value || null,
|
||||
multiModelMode: panelModeEl.value || 'off',
|
||||
panelAgentIds: panelAgentIdsEl.value || null,
|
||||
context: _sessionContextMde.value().trim() || null,
|
||||
workspace: workspaceEl.value.trim() || null,
|
||||
customCommands: commands,
|
||||
@@ -247,6 +285,16 @@ function openAgentPickerModal(agents, agentSkillsMap, currentId, onSelect) {
|
||||
});
|
||||
}
|
||||
|
||||
function openMultiAgentPickerModal(agents, agentSkillsMap, selectedIds, onConfirm) {
|
||||
const items = agents.map(a => {
|
||||
a._pickerId = a.agentId;
|
||||
return a;
|
||||
});
|
||||
_openMultiPickerModal('Panel-Agenten auswählen', items, selectedIds, onConfirm, {
|
||||
buildCard: (a) => buildAgentCard(a, agentSkillsMap[a.agentId] || [])
|
||||
});
|
||||
}
|
||||
|
||||
function initSessionTelegram(sessionId, session) {
|
||||
var botSelectCard = document.getElementById('telegramBotSelectCard');
|
||||
var botSelect = document.getElementById('telegramBotSelect');
|
||||
|
||||
Reference in new issue
Block a user