Add workflow-status telemetry view and make panel modes on-demand
Workflow-status: a new WorkflowProgressTracker singleton taps the
suppressed dynamic-workflow subagent events (those carrying
parent_tool_use_id) into per-session telemetry without altering the
existing guard; the snapshot is serialized into the new chains.workflow_info
column on FinalizeChain (ChainId-matched so a panel member of the same
session cannot clear it) and exposed via GET /api/sessions/{id}/workflow-status
(live -> persisted -> none). The header menu gains a 'Workflow-Status' entry
with a modal that live-polls every 2s while running and self-stops on close.
Panel on-demand: Fusion/MoA is now a transient per-request multiModelMode
flag (like plan/ultracode) instead of a persisted session column. Session
settings keep only the panel members + moa_layers; the mode <select> was
removed and the composer dropdown drives the mode per request.
Docs: ARCHITECTURE.md, ARCHITECTURE_QUICK.md and README.md updated for both
features plus the already-shipped z.ai/GLM provider, GLM 5.2+ effort
passthrough, web-search-prime MCP and the ultracode toggle.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
5b1802efee
commit
b6e546f618
22 files changed
+591
-99
No files matched your search
@@ -96,6 +96,109 @@ async function showPayloadPreviewModal(sessionId) {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Workflow-Status Modal (Ultracode) ──
|
||||
|
||||
function _wfNum(n) { return Number(n || 0).toLocaleString('de-DE'); }
|
||||
function _wfTime(t) { try { return new Date(t).toLocaleTimeString('de-DE'); } catch (e) { return ''; } }
|
||||
|
||||
function _renderWorkflowStatus(body, data) {
|
||||
var status = (data && data.status) || 'none';
|
||||
var wf = data && data.workflow;
|
||||
|
||||
var meta = {
|
||||
running: { label: 'Workflow läuft', cls: 'wf-running', icon: 'fa-spinner fa-spin' },
|
||||
completed: { label: 'Workflow abgeschlossen', cls: 'wf-done', icon: 'fa-check-circle' },
|
||||
none: { label: 'Kein Workflow für diese Session', cls: 'wf-none', icon: 'fa-ban' }
|
||||
};
|
||||
var sm = meta[status] || meta.none;
|
||||
|
||||
body.innerHTML = '';
|
||||
|
||||
var head = document.createElement('div');
|
||||
head.className = 'wf-status-head ' + sm.cls;
|
||||
head.innerHTML = '<i class="fa ' + sm.icon + '"></i> ' + esc(sm.label);
|
||||
body.appendChild(head);
|
||||
|
||||
if (!wf) {
|
||||
var empty = document.createElement('div');
|
||||
empty.className = 'wf-empty';
|
||||
empty.textContent = 'Für diese Session wurde noch kein dynamischer Workflow (Ultracode) ausgeführt.';
|
||||
body.appendChild(empty);
|
||||
return;
|
||||
}
|
||||
|
||||
var stats = [
|
||||
{ label: 'Subagenten', value: _wfNum(wf.subagentCount) },
|
||||
{ label: 'Tool-Aufrufe', value: _wfNum(wf.subagentToolCalls) },
|
||||
{ label: 'Input-Tokens', value: _wfNum(wf.subagentInputTokens) },
|
||||
{ label: 'Output-Tokens', value: _wfNum(wf.subagentOutputTokens) }
|
||||
];
|
||||
var grid = document.createElement('div');
|
||||
grid.className = 'wf-stat-grid';
|
||||
stats.forEach(function (s) {
|
||||
var cell = document.createElement('div');
|
||||
cell.className = 'wf-stat';
|
||||
cell.innerHTML = '<div class="wf-stat-val">' + esc(String(s.value)) +
|
||||
'</div><div class="wf-stat-lbl">' + esc(s.label) + '</div>';
|
||||
grid.appendChild(cell);
|
||||
});
|
||||
body.appendChild(grid);
|
||||
|
||||
var tb = wf.toolBreakdown || {};
|
||||
var keys = Object.keys(tb).sort(function (a, b) { return tb[b] - tb[a]; });
|
||||
if (keys.length) {
|
||||
var sec = document.createElement('div');
|
||||
sec.className = 'wf-section';
|
||||
sec.innerHTML = '<div class="wf-section-lbl">Tool-Aufschlüsselung</div>';
|
||||
var list = document.createElement('div');
|
||||
list.className = 'wf-tool-list';
|
||||
keys.forEach(function (k) {
|
||||
var row = document.createElement('div');
|
||||
row.className = 'wf-tool-row';
|
||||
row.innerHTML = '<span class="wf-tool-name">' + esc(k) +
|
||||
'</span><span class="wf-tool-count">' + esc(String(tb[k])) + '</span>';
|
||||
list.appendChild(row);
|
||||
});
|
||||
sec.appendChild(list);
|
||||
body.appendChild(sec);
|
||||
}
|
||||
|
||||
if (wf.startedAt) {
|
||||
var ts = document.createElement('div');
|
||||
ts.className = 'wf-meta';
|
||||
ts.textContent = 'Start: ' + _wfTime(wf.startedAt) +
|
||||
(wf.lastActivityAt ? ' · letzte Aktivität: ' + _wfTime(wf.lastActivityAt) : '');
|
||||
body.appendChild(ts);
|
||||
}
|
||||
}
|
||||
|
||||
async function showWorkflowStatusModal(sessionId) {
|
||||
const { body, overlay } = _createModal('Workflow-Status', { modal: 'workflow-status-modal' });
|
||||
body.innerHTML = '<div class="sp-loading">Laden...</div>';
|
||||
|
||||
var poll = async function () {
|
||||
try {
|
||||
var data = await apiGetWorkflowStatus(sessionId);
|
||||
_renderWorkflowStatus(body, data);
|
||||
return data && data.status;
|
||||
} catch (e) {
|
||||
body.innerHTML = '<div class="sp-loading text-danger">Fehler beim Laden des Workflow-Status</div>';
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
var status = await poll();
|
||||
|
||||
// Live-Refresh, solange ein Workflow läuft – endet automatisch beim Schließen des Modals.
|
||||
if (status === 'running') {
|
||||
var timer = setInterval(async function () {
|
||||
if (!document.body.contains(overlay)) { clearInterval(timer); return; }
|
||||
var s = await poll();
|
||||
if (s !== 'running') clearInterval(timer);
|
||||
}, 2000);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Command Output Modal ──
|
||||
|
||||
function showCommandOutputModal(name, command, result) {
|
||||
|
||||
Reference in new issue
Block a user