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
+18
-5
@@ -89,7 +89,7 @@ User 1──* TelegramBot 1──* Session (via telegram_bot_id)
|
||||
| `tokens` | Auth-Tokens → User |
|
||||
| `agents` | AI-Agenten (Model – z.B. `claude-sonnet-4-6` oder `nvidia:model-id`, CustomCommands, AutoCommit, UseSoul/Identity/UserInfo/Memory/AutoContext, UseGitHubSearch, UseSerperSearch, WebSearch, AllowUserChoice, TaskInstruction, Effort, AvatarScale/Offset, AvatarSmall, IsPublic) |
|
||||
| `sessions` | Chat-Sessions (Description, Image, ImageSmall, Workspace, Context, CustomCommands, TelegramBotId, TelegramChatId, AvatarScale/Offset, PreparerAgentId, ReviewAgentId, MaxReviewIterations, FinalizerAgentId, MultiModelMode, PanelAgentIds, MoaLayers) |
|
||||
| `chains` | Ein Request-Response-Zyklus (Tokens, Timing, Iterations, PlanMode, ToolChoice, ToolNames, StreamErrorInfo, ArchiveId) |
|
||||
| `chains` | Ein Request-Response-Zyklus (Tokens, Timing, Iterations, PlanMode, Effort, ToolChoice, ToolNames, StreamErrorInfo, ArchiveId, WorkflowInfo = JSON-Telemetrie eines dynamischen ultracode-Workflows, null = keiner) |
|
||||
| `messages` | Einzelne Nachrichten (text / tool_use / tool_result / error / web_search_result / auto_context), Tag für Sub-Agent-Markierung oder Import-Herkunft (`openclaw-import`), thought_signature (Gemini) |
|
||||
| `attachments` | Base64-Anhänge (Bilder, Dokumente) |
|
||||
| `archives` | Archivierte Chain-Gruppen |
|
||||
@@ -114,7 +114,7 @@ User 1──* TelegramBot 1──* Session (via telegram_bot_id)
|
||||
| Auth | `/api/auth/` | `POST login`, `POST logout`, `GET me`, `GET autologin` |
|
||||
| Chat | `/api/chat` | `POST` → SSE Stream, `POST stop` |
|
||||
| History | `/api/history` | GET history, poll, archive, marks, delete |
|
||||
| Sessions | `/api/sessions/` | CRUD + system-prompt, system-prompt-blocks, payload-preview, streaming, exec, git-status |
|
||||
| Sessions | `/api/sessions/` | CRUD + system-prompt, system-prompt-blocks, payload-preview, streaming, workflow-status, exec, git-status |
|
||||
| Agents | `/api/agents/` | CRUD + available (inkl. public agents, inline Skills) |
|
||||
| Admin | `/api/admin/` | User CRUD, impersonate, login-link (Admin-only) |
|
||||
| Workspace | `/api/workspace/` | tree, file read/write, mkdir, rmdir, browse, agent-files |
|
||||
@@ -162,6 +162,7 @@ User 1──* TelegramBot 1──* Session (via telegram_bot_id)
|
||||
| **EmbeddingBackgroundService** | Backfill bestehender Messages + Live-Embedding neuer Messages/Memory-Files |
|
||||
| **RemoteConnectionManager** | WebSocket-basierte Remote-Verbindungen, Command-Execution auf Remote-Clients |
|
||||
| **StreamingTracker** | Tracking aktiver Streams + CancellationTokenSource pro Session für Stop |
|
||||
| **WorkflowProgressTracker** | In-Memory-Telemetrie laufender dynamischer ultracode-Workflows pro Session (Subagent-Anzahl, Tool-Aufrufe + Aufschlüsselung, aggregierte Subagent-Tokens); LIVE während des Laufs, danach in `chains.workflow_info` persistiert (analog StreamingTracker) |
|
||||
| **AppPaths** | Zentrale Pfade (WwwRoot, Dev vs. Publish-Modus) (static) |
|
||||
| **KeyScannerService** | API-Key-Scanner (GitHub Code Search, GitHub Issues/PRs, HuggingFace Hub, Google/Serper), täglicher Scan |
|
||||
| **GitHubEventScannerService** | Echtzeit-Scanner: GitHub Public Events API (PushEvent-Diffs nach geleakten Keys), 30s Poll-Intervall |
|
||||
@@ -349,7 +350,7 @@ Eine Session kann bis zu **vier Agents** haben: Worker (Pflicht), Preparer (opti
|
||||
|
||||
## Multi-Modell-Panel (Fusion / MoA)
|
||||
|
||||
Zusätzlich zum Worker kann eine Session ein **Panel** aus mehreren Agenten haben (`multi_model_mode = "fusion"`/`"moa"`, `panel_agent_ids` = CSV der Agent-IDs, `moa_layers` = Proposer-Runden bei MoA, Default 2). Bei jeder Nachricht antworten alle Panel-Agenten **parallel** auf die Anfrage; ihre Antworten werden dem **Worker** als `<panel_responses>`-Block in die letzte User-Message gemergt, der daraus **eine** Gesamtantwort synthetisiert und normal streamt (OpenRouter-Fusion-/Mixture-of-Agents-Muster).
|
||||
Zusätzlich zum Worker kann eine Session ein **Panel** aus mehreren Agenten haben. In den Session-Einstellungen werden nur die **Mitglieder** persistiert (`panel_agent_ids` = CSV der Agent-IDs, `moa_layers` = Proposer-Runden bei MoA, Default 2). Der Panel-**Modus** (`fusion`/`moa`) wird **on-demand pro Anfrage** über den Composer-Toggle gewählt (transienter Request-Flag `multiModelMode`, nicht persistiert – wie Plan-/Ultracode-Modus). Ist er aktiv, antworten alle Panel-Agenten **parallel** auf die Anfrage; ihre Antworten werden dem **Worker** als `<panel_responses>`-Block in die letzte User-Message gemergt, der daraus **eine** Gesamtantwort synthetisiert und normal streamt (OpenRouter-Fusion-/Mixture-of-Agents-Muster).
|
||||
|
||||
**Fusion vs. MoA:** Fusion = 1 Proposer-Runde → Synthese. **MoA** = `moa_layers` Runden (1–5, Backstop): ab Runde 2 sieht jeder Member die Antworten der Vorrunde (`<previous_layer_responses>`, isoliert pro Member via `CloneForMember` gemergt) und verfeinert. `RunPanel(layers)` ist EINE Engine (Fusion = `layers==1`). **Alle** Runden werden persistiert (F5-sicher); JSON-Transkript pro Member enthält `layer`/`totalLayers` + Text-/Tool-Segmente (`Iteration = layer*100+index+1` macht DB-Zeilen layer-aware).
|
||||
|
||||
@@ -357,13 +358,13 @@ Zusätzlich zum Worker kann eine Session ein **Panel** aus mehreren Agenten habe
|
||||
|
||||
**Member streamen LIVE** (wie der Session-Agent): CLI forwardet `TextDelta`/`ToolStart`/`ToolResult`, Anthropic-API streamt pro Tool-Runde über `NonStreamingLoopCallbacks`, OpenAI = one-shot (Text am Ende). Ein `SemaphoreSlim`-Write-Lock in `SseHelper.TryWriteSse` serialisiert die SSE-Byte-Flushes paralleler Member (sonst vermischen sich die Frames).
|
||||
|
||||
**Subagent-Filterung:** Spawnt ein CLI-Member intern einen Subagenten (Claude-CLI-`Agent`-Tool), sendet die CLI dessen Interna als separate Top-Level-Events mit `parent_tool_use_id`. Der Parser (`ClaudeCliService.RunCliStreamingAsync`) verwirft diese **zentral am Ursprung** (Guard auf nicht-leeres `parent_tool_use_id`) → keine namenlosen Tool-Boxen, weder im Live-Stream noch in der History (beide lesen aus denselben gefilterten `CliStreamEvent`s). Die `Agent`-Tool-Box selbst (`parent_tool_use_id=null`) bleibt sichtbar; der finale `result`-Event (mit den **aggregierten** Subagent-Tokens) wird nicht gefiltert → Token-Bilanz vollständig.
|
||||
**Subagent-Filterung:** Spawnt ein CLI-Member intern einen Subagenten (Claude-CLI-`Agent`-Tool), sendet die CLI dessen Interna als separate Top-Level-Events mit `parent_tool_use_id`. Der Parser (`ClaudeCliService.RunCliStreamingAsync`) verwirft diese **zentral am Ursprung** (Guard auf nicht-leeres `parent_tool_use_id`) → keine namenlosen Tool-Boxen, weder im Live-Stream noch in der History (beide lesen aus denselben gefilterten `CliStreamEvent`s). Die `Agent`-Tool-Box selbst (`parent_tool_use_id=null`) bleibt sichtbar; der finale `result`-Event (mit den **aggregierten** Subagent-Tokens) wird nicht gefiltert → Token-Bilanz vollständig. Dieselben unterdrückten Subagent-Events werden zusätzlich **additiv** in den `WorkflowProgressTracker` abgegriffen (`TapSubagentEvent`, wirft nie) → speist den Workflow-Status, ohne den Guard/Stream zu verändern.
|
||||
|
||||
**Backend:** `ChatService.PrepareContext` baut `ChatContext.PanelAgents` (`List<SubAgentContext>`) + `MoaLayers`. `RunPanel(layers)` (AnthropicService.Panel.cs) läuft im ChatEndpoint-try VOR dem Worker-Dispatch: pro Runde Member parallel (`Task.WhenAll` + `SemaphoreSlim`), Provider-Weiche pro Member, **jede** Runde als `[Panel: Name]`-Message mit `Tag="panel"` persistiert (aus Worker-History gefiltert); JSON-Transkript = `{name, model, layer, totalLayers, segments:[Text/Tool…]}`. `NonStreamingLoopCallbacks.Persist=false` verhindert Doppel-Persistenz. Member-Tokens via `seedUsage` in Worker-`usage` gefaltet → Live-Badge **und** DB (kein separates Panel-Usage-Event). Synthese-Block in letzte User-Message gemergt. Panel-Fehler graceful.
|
||||
|
||||
**SSE-Events:** `panel_start` (mode/layers), `panel_layer_start`/`panel_layer_done` (MoA-Runden), `panel_member_start` (mit `layer`), `panel_member_text`/`panel_member_tool_use`/`panel_member_tool_end`/`panel_member_tool` (live), `panel_member_done` (inkl. per-Member Token-Feldern `inputTokens`/`outputTokens`/`cacheReadTokens`/`cacheCreationTokens`), `panel_done`.
|
||||
|
||||
**Frontend:** `stream-ui.js` rendert pro Member eine `.panel-bubble`, gekeyt per `layer_index` (MoA: gleiche Member über mehrere Runden; aktive Runde pro Index wird bei `panel_member_start` gemerkt, da Runden sequenziell laufen). `panel_layer_start` zeigt bei >1 Runde einen `.panel-layer-divider`. **History:** `history.js _renderPanelMemberBubble` parst JSON-Transkript → Text + Tool-Boxen interleaved; bei MoA Runden-Divider beim Layer-Wechsel (Back-Compat für Alt-Format). Picker: `#multiModelMode`-Dropdown (Aus/Fusion/MoA) + `#moaLayers` + Mehrfach-Agenten-Picker (`_openMultiPickerModal`). Vereinter Composer-Toggle: **ein** Button `#planModeBtn` (chat.js `cycleCompositeMode`) schaltet zyklisch Aus→Plan→Fusion→MoA durch. Plan ist transient (Compat-Var `_planModeActive`), Fusion/MoA werden sofort via `apiUpdateSession({multiModelMode})` persistiert – gleiche Quelle wie der Picker. Modus-PATCHes laufen über eine **serialisierte Promise-Kette** (`_modePersist`, letzter Klick gewinnt); `sendMessage`/`sendMessageDirect` **awaiten** sie, bevor der Chat-Request die Session lädt → verhindert Race (z.B. MoA mit nur 1 Runde, weil das Backend noch den alten Modus liest). **Per-Member-Token-Badge:** `buildPanelMemberMeta(d)` (`stream-ui.js`, global) baut ein kompaktes `.panel-member-meta`-Badge aus den Token-Feldern – Live aus `panel_member_done`, History aus dem persistierten JSON (gleicher Helper); graceful bei fehlenden Daten (OpenAI, Fehler-Member, Alt-History → kein Badge).
|
||||
**Frontend:** `stream-ui.js` rendert pro Member eine `.panel-bubble`, gekeyt per `layer_index` (MoA: gleiche Member über mehrere Runden; aktive Runde pro Index wird bei `panel_member_start` gemerkt, da Runden sequenziell laufen). `panel_layer_start` zeigt bei >1 Runde einen `.panel-layer-divider`. **History:** `history.js _renderPanelMemberBubble` parst JSON-Transkript → Text + Tool-Boxen interleaved; bei MoA Runden-Divider beim Layer-Wechsel (Back-Compat für Alt-Format). Settings-Picker: **nur** die Panel-Mitglieder (`#panelAgentIds` via `_openMultiPickerModal`) + `#moaLayers` – beide immer sichtbar und **persistent**. Der Panel-**Modus** wird NICHT mehr in den Settings gewählt. Composer-Toggle: **ein** Button `#planModeBtn` (chat.js) öffnet ein **Dropdown-Menü** (`_COMPOSITE_MODES`: Normal/Plan/Ultracode/Fusion/MoA, Muster wie der Emoji-Picker; aktiver Eintrag markiert, Button trägt das Modus-Icon). **Alle** Modi sind **transient** (pro Request, nicht persistiert, beim Chat-Wechsel via `initCompositeMode` auf `off` zurückgesetzt): der gewählte Modus wird als Flag in den `/api/chat`-Payload gelegt (`planMode`/`ultracode`; Fusion/MoA als `multiModelMode`, nur diese beiden Werte). Das Backend baut das Panel daraus **on-demand** (`ChatService.PrepareContext(multiModelMode)` → `effectiveMultiModelMode`), die Mitglieder weiterhin aus der persistierten `session.PanelAgentIds`. **Per-Member-Token-Badge:** `buildPanelMemberMeta(d)` (`stream-ui.js`, global) baut ein kompaktes `.panel-member-meta`-Badge aus den Token-Feldern – Live aus `panel_member_done`, History aus dem persistierten JSON (gleicher Helper); graceful bei fehlenden Daten (OpenAI, Fehler-Member, Alt-History → kein Badge).
|
||||
|
||||
**Web-Search Sources:**
|
||||
- `web_search_result`-Messages in DB gespeichert (Sources als JSON-Array, leere werden übersprungen)
|
||||
@@ -372,6 +373,18 @@ Zusätzlich zum Worker kann eine Session ein **Panel** aus mehreren Agenten habe
|
||||
|
||||
---
|
||||
|
||||
## Workflow-Status (dynamische ultracode-Workflows)
|
||||
|
||||
Live-Telemetrie der Subagenten, die ein **ultracode**-Lauf über das `Workflow`/`Task`-Tool der Claude CLI spawnt (diese Subagent-Events sind im Haupt-Stream/Verlauf bewusst ausgeblendet, siehe Subagent-Filterung oben).
|
||||
|
||||
**Backend:** `WorkflowProgressTracker` (Singleton, lokale Instanz in `Program.cs`, weil `ClaudeCliService` befüllt und `AnthropicService` beim Finalize persistiert – dieselbe Instanz). `ClaudeCliService.TapSubagentEvent` → `RecordSubagent(...)` führt pro Session In-Memory: Subagent-Anzahl, Tool-Aufrufe (+ Aufschlüsselung), aggregierte Input/Output-Tokens. Beim `FinalizeChain` (`AnthropicService.cs`) wird der Stand als JSON in `chains.workflow_info` (`ChainRecord.WorkflowInfo`, Migration via `TryAddColumn` in `DatabaseService.cs`, geschrieben in `DatabaseService.Sessions.cs` `UpdateChain`) geschrieben – mit ChainId-Abgleich, damit ein Panel-Member derselben Session ihn nicht löscht.
|
||||
|
||||
**Endpoint:** `GET /api/sessions/{id}/workflow-status` → LIVE-Snapshot aus dem Tracker, sonst persistiertes `workflow_info` der jüngsten Chain, sonst `status: "none"`. JSON: `{ sessionId, streaming, status (running/completed/none), chainId, workflow }`; das `workflow`-Objekt trägt camelCase `subagentCount`, `subagentToolCalls`, `subagentInputTokens`, `subagentOutputTokens`, `toolBreakdown`, `recentTools`, `startedAt`, `lastActivityAt`.
|
||||
|
||||
**Frontend:** Header-Menü-Eintrag **„Workflow-Status"** (`#workflowStatusBtn` in `index.html`, direkt nach „Payload anzeigen"), Wrapper `apiGetWorkflowStatus` (`api.js`), `showWorkflowStatusModal` (`modals.js`, pollt alle 2 s solange `status === 'running'`, stoppt von selbst beim Schließen des Modals), Click-Handler in `pages/chat-page.js`, `.workflow-status-modal`-Styles in `css/chat.css`.
|
||||
|
||||
---
|
||||
|
||||
## Wichtige Regeln beim Entwickeln
|
||||
|
||||
- ⚠️ **Quelldateien in `wwwroot/`** editieren, NICHT in `Build/*/publish/wwwroot/`
|
||||
|
||||
Reference in new issue
Block a user