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:
MelekiandClaude Opus 4.8 committed 2026-06-20 17:27:46 +02:00
1 parent 5b1802efee
commit b6e546f618
22 files changed
+591 -99

No files matched your search

+1 -1
View File
@@ -56,7 +56,7 @@ public static class ChatEndpoint
var remoteConn = remoteMgr.GetBySessionId(sessionId);
var hasRemote = remoteConn != null;
var ctx = await chatService.PrepareContext(sessionId, userId, maxIterations, planMode: body.PlanMode, hasRemoteConnection: hasRemote, remoteOs: remoteConn?.OsVersion);
var ctx = await chatService.PrepareContext(sessionId, userId, maxIterations, planMode: body.PlanMode, multiModelMode: body.MultiModelMode, hasRemoteConnection: hasRemote, remoteOs: remoteConn?.OsVersion);
if (ctx == null)
{
res.StatusCode = 400;
+49
View File
@@ -28,6 +28,7 @@ public static class SessionEndpoint
app.MapGet("/api/sessions/{sessionId}/system-prompt-blocks", HandleSystemPromptBlocks);
app.MapGet("/api/sessions/{sessionId}/payload-preview", HandlePayloadPreview);
app.MapGet("/api/sessions/{sessionId}/streaming", HandleStreaming);
app.MapGet("/api/sessions/{sessionId}/workflow-status", HandleWorkflowStatus);
app.MapPost("/api/sessions/{sessionId}/exec", HandleExec);
app.MapGet("/api/sessions/{sessionId}/git-status", HandleGitStatus);
}
@@ -430,6 +431,54 @@ public static class SessionEndpoint
await ctx.Response.WriteAsJsonAsync(new { streaming = isStreaming, chainId });
}
/// <summary>
/// Status eines dynamischen Workflows (ultracode) für die Session: LIVE aus dem
/// WorkflowProgressTracker, solange ein Lauf aktiv ist – sonst die zuletzt persistierte
/// Telemetrie (chains.workflow_info) der jüngsten Chain. Liefert Subagent-Anzahl,
/// Tool-Aufrufe (+ Aufschlüsselung) und aggregierte Subagent-Tokens.
/// </summary>
private static async Task HandleWorkflowStatus(HttpContext ctx, DatabaseService db, StreamingTracker streamingTracker, WorkflowProgressTracker workflowTracker)
{
var userId = ctx.GetUserId();
var sessionId = ctx.Request.RouteValues["sessionId"]?.ToString();
if (string.IsNullOrEmpty(sessionId))
{
ctx.Response.StatusCode = 400;
return;
}
var session = await db.GetSession(sessionId);
if (session == null || session.UserId != userId)
{
ctx.Response.StatusCode = 404;
return;
}
var streaming = streamingTracker.IsStreaming(sessionId);
// 1) Live: ein Workflow läuft gerade (Tracker hat einen Eintrag).
var live = workflowTracker.GetSnapshot(sessionId);
if (live != null)
{
await ctx.Response.WriteAsJsonAsync(new { sessionId, streaming, status = "running", chainId = live.ChainId, workflow = live });
return;
}
// 2) Sonst: zuletzt persistierte Workflow-Telemetrie aus den aktiven Chains der Session.
var chains = await db.GetActiveChains(sessionId); // ORDER BY id DESC
var last = chains.FirstOrDefault(c => !string.IsNullOrEmpty(c.WorkflowInfo));
if (last != null)
{
WorkflowProgress wp = null;
try { wp = JsonSerializer.Deserialize<WorkflowProgress>(last.WorkflowInfo); } catch { }
await ctx.Response.WriteAsJsonAsync(new { sessionId, streaming, status = "completed", chainId = last.ChainId, workflow = wp });
return;
}
// 3) Für diese Session lief nie ein Workflow.
await ctx.Response.WriteAsJsonAsync(new { sessionId, streaming, status = "none", workflow = (object)null });
}
private static async Task HandleExec(HttpContext ctx, DatabaseService db)
{
var userId = ctx.GetUserId();