Files
gott/ARCHITECTURE_QUICK.md
T
MelekiandClaude Opus 4.8 2b95161596 Read workflow-status telemetry from CLI transcript files
Dynamic ultracode workflows run as a detached background task with their
own transcript dir, so their subagent events never flow through the main
-p stream. The previous parent_tool_use_id tap therefore captured nothing
for real workflows and the status stayed "none" the whole run.

Replace the tap with a file-based reader:
- TapWorkflowLaunch parses the "Transcript dir:" line from the Workflow
  tool_result and registers the run.
- WorkflowProgressTracker becomes a per-session registry of transcript
  paths (no longer a stream accumulator).
- WorkflowTelemetryReader reads journal.jsonl for live counts (running)
  and <runId>.json for rich per-agent telemetry (completed); FinalizeChain
  persists the rich JSON into chains.workflow_info.
- Modal renders phases + a per-agent list (label/phase/state/tokens/
  tool-calls/duration) instead of the obsolete tool breakdown.

Sync ARCHITECTURE.md, ARCHITECTURE_QUICK.md and README.md to the new
mechanism.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 18:14:42 +02:00

406 lines
34 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ARCHITECTURE_QUICK.md – Schnellreferenz
> Kurzfassung der [ARCHITECTURE.md](ARCHITECTURE.md) für schnelles Nachschlagen beim Entwickeln.
---
## Tech-Stack
**.NET 10** Minimal API · **SQLite** + Dapper · **Anthropic Claude API** (SSE Streaming + Claude CLI) · **OpenAI-kompatible APIs** (Nvidia, Pollinations, Gemini, OpenAI) · **ONNX Runtime** (Sentence-Embeddings) · **Vanilla JS** SPA · **KaTeX** (Math) · **Kestrel** auf Port 19000
---
## Verzeichnisstruktur (nur Schlüssel-Dateien)
```
gott/
├── Program.cs # Startup, DI, Middleware, Routing
├── appsettings.json # Tokens, API-Keys, Limits
├── Models/ # Records (reine Daten, inkl. ApiKeyRecord)
├── Services/ # Business-Logic (Singletons + Static Helpers)
│ ├── DatabaseService*.cs # Partial Classes (Core, Sessions, Agents, Users, Integrations)
│ ├── AnthropicService*.cs # Partial Classes (Core, Streaming, Agents, Cli)
│ ├── ClaudeCliService.cs # Claude Code CLI Wrapper (OAuth-basiert)
│ ├── SseHelper.cs # Shared SSE-Utilities
│ ├── EmbeddingService.cs # Lokales Sentence-Embedding (ONNX)
│ ├── VectorSearchService.cs # Semantische Suche
│ ├── RemoteConnectionManager.cs # WebSocket Remote-Verbindungen
│ ├── AppPaths.cs # Zentrale Pfade (WwwRoot, Dev vs. Publish)
│ ├── KeyScannerService.cs # GitHub API-Key-Scanner
│ ├── GitHubEventScannerService.cs # Echtzeit-Scanner (GitHub Events API)
│ ├── KeyPatterns.cs # Zentrale Key-Pattern-Definitionen (Regex + Typ-Erkennung)
│ ├── KeyCheckDispatcher.cs # Zentraler Key-Check-Dispatcher (alle Provider)
│ ├── AnthropicKeyChecker.cs # Anthropic Key-Validierung + Usage-Auswertung
│ ├── OpenAiKeyChecker.cs # OpenAI Key-Validierung
│ ├── GoogleKeyChecker.cs # Google/Gemini Key-Validierung
│ └── StripeKeyChecker.cs # Stripe Key-Validierung
├── Endpoints/ # HTTP API (Minimal API MapXxx-Pattern)
├── Middleware/ # AuthMiddleware
├── Templates/ # Vorlagen für neue Agents (IDENTITY, SOUL, USER)
├── wwwroot/ # ⚠️ HIER Frontend editieren!
│ ├── js/app.js # App-Init
│ ├── js/router.js # Hash-basiertes SPA-Routing
│ ├── js/api.js # fetch()-Wrapper
│ ├── js/ui-helpers.js # DOM-Helpers ($, esc, truncate), Toast, ScrollDown
│ ├── js/markdown.js # Markdown-Rendering (renderMarkdown, LaTeX-Schutz)
│ ├── js/cards.js # Card-Builder (buildSessionCard, buildAgentCard, buildTelegramBotCard)
│ ├── js/msg-actions.js # Nachrichten-Aktionen (Delete, Mark, Share, Reply)
│ ├── js/tool-ui.js # Tool-Boxes, Diffs, Visibility-Logik
│ ├── js/modals.js # Modale Dialoge (System-Prompt, Command-Output)
│ ├── js/stream-ui.js # Stream-Bubble, Review-Streaming
│ ├── js/bubbles.js # Message Bubbles (User, Assistant, Error, Choice)
│ ├── js/chat.js # SSE-Stream-Handling, sendMessage, Plan-Mode
│ ├── js/history.js # History laden, Polling, Rendering
│ ├── js/archive.js # Archiv-Loading
│ ├── js/reply.js # Reply/Quote-Handling
│ ├── js/files.js # File-Upload, Workspace-Refs
│ ├── js/typing-phrases.js # Zufällige Typing-Indicator-Texte
│ ├── js/pages/ # Seiten-Komponenten (inkl. api-keys-page.js, telegram-bot-page.js, db-explorer-page.js)
│ └── css/ # Styles (theme.css = CSS Variables)
├── WindowsApp/ # WPF + WebView2 Desktop Client
└── Build/ # ⛔ NICHT editieren!
├── Linux/
│ ├── bin/ obj/ publish/
└── Windows/
├── bin/ obj/ publish/
└── data/
├── gott.db # SQLite DB
├── agents/{id}/ # Memory-Dateien pro Agent
├── skills/{slug}/ # Skill-Dateien
└── images/ # Krea-generierte Bilder
```
---
## Datenmodell (Kurzform)
```
User 1──* Agent 1──* Session 1──* Chain 1──* Message 1──* Attachment
Agent *──* Skill (via agent_skills)
Session 1──* Cronjob
Session 1──* Archive
User 1──* SharedMessage ──1 Message
User 1──* TelegramBot 1──* Session (via telegram_bot_id)
```
| Tabelle | Zweck |
|---------|-------|
| `users` | Login, DisplayName, IsAdmin, Persona, Settings (JSON), DefaultPath |
| `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, 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 |
| `cronjobs` | Geplante Tasks (Cron-Expression + Prompt) |
| `agent_skills` | N:M Agent ↔ Skill |
| `shared_messages` | Geteilte Nachrichten (öffentliche Links via Share-Token) |
| `telegram_bots` | Telegram Bots (BotToken, WebhookSecret, Name, IsPublic) – eigenständige Entität, 1 Bot → N Sessions |
| `api_keys` | API-Keys (key_id, provider, name, api_key, is_active, status, plan, usage_7d, usage_5h, source_url, is_public, refresh_token, access_token_expires_at, oauth_provider) – mehrere Keys pro Provider möglich, Zuweisung pro Agent |
| `agent_api_keys` | API-Key-Zuweisung pro User+Agent (user_id, agent_id, key_id) – ersetzt globales `users.selected_api_key_id` |
| `scanned_keys` | Gescannte GitHub-Keys (api_key, source_url, source, type, status, plan, usage_7d, usage_5h, adopted_api_key_id, deleted) |
| `scanner_logs` | Scanner-Logs (timestamp, type, message, scanner) |
| `server_settings` | Server-Settings (key, value, type) – verwaltet über `ServerSettingsService` |
| `message_embeddings` | Sentence-Embeddings für semantische Suche (message_id, vector BLOB) |
| `memory_file_embeddings` | Embeddings von Memory-Dateien (agent_id, file_path, chunk_index, vector BLOB) |
---
## API-Endpunkte (Übersicht)
| Gruppe | Route | Wichtigstes |
|--------|-------|-------------|
| 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, 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 |
| Telegram | `/api/telegram/` | Bot-CRUD, webhook/{botId} (unauth), session-config, registration |
| OpenClaw | `/api/openclaw/` | Import von OpenClaw-Sessions (sessions, import) |
| Cronjobs | `/api/cronjobs/` | CRUD + toggle, manual run |
| Krea | `/api/images/` | generate, list, file-serve, thumb, status (file/* ohne Auth) |
| Skills | `/api/skills` | list, agent-mapping, output-files |
| Profile | `/api/profile` | GET, PUT, password |
| Settings | `/api/settings` | GET, PUT (JSON blob) |
| Backup | `/api/backup` | POST → tar.gz download (Admin) |
| Shared | `/api/shared/`, `/api/messages/{id}/share` | Share erstellen, öffentlich abrufen, löschen |
| Models | `/api/models/` | `GET nvidia`, `GET pollinations`, `GET gemini`, `GET openai`, `POST refresh` |
| API Keys | `/api/admin/api-keys/` | CRUD + check, check-all, scan, event-scanner, scanned (recheck/adopt/delete), scanner-logs (Admin) + `/api/api-keys/` available, select, selected (User) |
| Remote | `/api/remote` | WebSocket für Remote-Clients, `GET connections`, `POST assign/unassign`, `GET /remote.ps1` (Windows), `GET /remote.sh` (Linux/macOS) |
| Internal | `/api/internal/` | MCP-Server: remote-execute, ask-user-choice, memory-read/write/list (Token-gesichert) + `POST /api/chat/choice-respond` (normale Auth) |
| DB Explorer | `/api/admin/db/` | tables, table-data (Admin-only) |
| Info | `/api/info` | GET → Build-Zeitstempel + Embedding-Verfügbarkeit |
| ServerSettings | `/api/admin/server-settings` | `GET`, `PUT` (Admin-only, liest/schreibt `appsettings.Production.json`) |
---
## Services (alle Singletons)
| Service | Was es tut |
|---------|-----------|
| **DatabaseService** | Alles DB: Schema, Migrationen, CRUD |
| **AuthService** | Token-Auth, Passwort-Hashing (SHA256) |
| **ChatService** | Chat-Kontext aufbauen, History aus DB laden |
| **AnthropicService** | Claude API: SSE Streaming + Non-Streaming + CLI-Fallback, passive Usage-Aktualisierung (partial class: Core, Streaming, Agents, Cli) |
| **ClaudeCliService** | Claude Code CLI Wrapper (OAuth-basiert, kein API-Key nötig) |
| **OpenAiService** | OpenAI-kompatible Provider (Nvidia, Pollinations, Gemini, OpenAI) mit Tool-Calling, Streaming + Non-Streaming, Modell-Caching |
| **SseHelper** | Shared SSE-Utilities: WriteSseEvent, TryWriteSse, FinalizeChain, TruncateToolResult, `TokenUsage.Add` (static) |
| **ToolService** | Tool-Definitionen + Ausführung (19 Tools, inkl. github_code_search, serper_web_search) |
| **WorkspaceHelper** | Workspace-Pfad-Auflösung (relativ → absolut), Path-Traversal-Schutz (static) |
| **SystemPromptBuilder** | System-Prompt aus modularen Blöcken zusammenbauen + Reminder-Generierung (static) |
| **MemoryService** | Agent-Gedächtnis auf Dateisystem |
| **SkillService** | Skill-Discovery aus Dateisystem |
| **TelegramService** | Telegram Bot API (Bot-basiert, Multi-Session-Routing), Provider-/Key-Dreiteilung: CLI-Fallback für keylose Anthropic + Server-Key-Auflösung für nvidia/gemini/minimax/pollinations |
| **CronjobService** | BackgroundService, minütlicher Cron-Check, gleiche Provider-/Key-Dreiteilung wie TelegramService (CLI-Fallback + Server-Key-Auflösung) |
| **KreaService** | Krea API Bildgenerierung + Job-Polling (eine Anfrage pro Bild – Krea akzeptiert kein `batchSize` mehr) |
| **ImageService** | Bild-Resize + WebP-Konvertierung (512px + 256px Thumbnail) |
| **EmbeddingService** | Lokales Sentence-Embedding via all-MiniLM-L6-v2 (ONNX, 384 Dimensionen) |
| **VectorSearchService** | Semantische Suche über Message-Embeddings + Memory-File-Embeddings |
| **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** | **Registry** der Transcript-Pfade pro Session für dynamische ultracode-Workflows (`WorkflowRunRef`: ChainId/RunId/JournalPath/RichJsonPath/StartedAt) – kein Stream-Akkumulator. Die Telemetrie wird on demand über `WorkflowTelemetryReader` aus den CLI-Transcript-Dateien gelesen: `journal.jsonl` (live) bzw. `<runId>.json` (abgeschlossen, reich) und beim Finalize in `chains.workflow_info` persistiert |
| **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 |
| **KeyPatterns** | Zentrale Key-Pattern-Definitionen für alle Scanner (Regex, Typ-Erkennung, Display-Names) (static) |
| **KeyCheckDispatcher** | Zentraler Dispatcher für Key-Checks aller Provider, normalisiertes Ergebnis (static) |
| **AnthropicKeyChecker** | Key-Validierung via Anthropic API, Usage-Header-Auswertung (Plan, 7d/5h Usage) |
| **OpenAiKeyChecker** | OpenAI Key-Validierung (Models-Endpoint, Rate-Limits, Organization) |
| **GoogleKeyChecker** | Google/Gemini Key-Validierung (Models + Quota-Check) |
| **StripeKeyChecker** | Stripe Key-Validierung (Balance, Account-Info, Payouts/Charges) |
| **ServerSettingsService** | Server-Settings Verwaltung (In-Memory-Cache + DB), Cleanup obsoleter Settings |
---
## Chat-Pipeline (Flow)
```
User-Input → POST /api/chat (SSE)
→ ChatService.BuildContext()
→ History aus DB laden (letzte N Chains)
→ SystemPromptBuilder: 11 Blöcke zusammensetzen (+ Plan-Modus/Preparer-Blöcke)
→ Tool-Definitionen vom ToolService holen (im Plan-Modus: nur Read-only)
→ API-Key auflösen: GetAgentApiKeyRecord(userId, agentId) – Anthropic ohne Key → CLI-Fallback oder Fehler, keyless Provider → OK
→ Preparer-Phase (optional, wenn PreparerAgentId gesetzt):
→ RunPreparer() → Provider-Weiche: Anthropic → RunAgentChat(), OpenAI → ChatNonStreaming()
→ Antwort ohne [QUESTION] → automatisch weiterleiten (Default = Forward)
→ [QUESTION] → Rückfrage an User, Flow stoppt
→ AnthropicService.StreamChat() / StreamChatViaCli() / OpenAiService.StreamChat()
→ API Call (SSE)
→ Token-für-Token an Client streamen
→ Bei tool_use → ToolService.Execute() → Ergebnis zurück → neuer API Call
→ Bei ask_user_choice → SSE user_choice Event → Flow stoppt (awaiting_user_choice)
→ Loop bis end_turn oder max_iterations (100)
→ Bei end_turn + ReviewAgent → Review-Phase:
→ Review-Agent prüft (eigene Tools, max 20 Tool-Iterationen pro Run, bis zu N Feedback-Runden)
→ [APPROVED] → Fertig | Feedback → Worker korrigiert → erneuter Review
→ Bei FinalizerAgent → Finalizer-Phase:
→ Finalizer-Agent arbeitet (eigene Tools, z.B. git commit)
→ Einmalig, kein Feedback-Loop
→ Stop-Button: POST /api/chat/stop → StreamingTracker.Cancel()
→ CancellationToken durchgereicht bis ToolService → Prozess-Kill
→ client_disconnected
→ Chain + Messages in DB speichern
```
**Passive Usage-Aktualisierung:** Nach jedem API-Request (Streaming, Sub-Agent, NonStreaming) werden die Response-Header ausgelesen und Plan/Usage des aktiven API-Keys in der DB aktualisiert – kein separater Check nötig.
**History-Optimierung:** Max 220 Messages (`MaxHistory`-Setting). Ältere Chains nur Text-Messages (keine Tool-Calls). Letzte Chain vollständig. Tool-Results auf 20.000 Zeichen gekürzt. Attachments nur für letzte User-Message. System-Prompt-Reminder alle ~30 Messages.
**Auto-Context (Vector-Search):** Wenn `UseAutoContext` aktiv, wird vor jedem Request via `VectorSearchService` semantisch passender Kontext aus früheren Messages + Memory-Files gesucht und als `<recalled_context>`-Block in den System-Prompt injiziert. Wird im Frontend als aufklappbare **Auto-Context-Box** angezeigt (Live via SSE-Event `auto_context` + History via `messages`-Eintrag mit `Role="system"`, `Type="auto_context"`). Wird aus der API-History gefiltert (kein Doppel-Send).
**Rendering:** Markdown via marked.js + LaTeX via KaTeX (`$$...$$` Block, `$...$` Inline). Timestamps im Format `dd.MM.yyyy HH:mm:ss` on hover.
---
## Tool-System (19 Tools)
| Tool | Funktion |
|------|----------|
| `read_file` | Datei lesen |
| `write_file` | Datei erstellen/überschreiben |
| `edit_file` | Exakten Textabschnitt ersetzen |
| `list_files` | Verzeichnisinhalt auflisten |
| `delete_file` | Datei/leeres Verzeichnis löschen |
| `execute` | Shell-Befehl ausführen (bash, konfigurierbares Timeout) |
| `background_execute` | Shell-Befehl als Hintergrund-Prozess starten (gibt PID zurück) |
| `glob` | Dateien per Glob-Pattern finden |
| `grep` | Dateien per Regex/Text durchsuchen |
| `todo_write` | Aufgabenliste schreiben (wird im Prompt angezeigt) |
| `memory_read` | Memory-Datei lesen |
| `memory_write` | Memory-Datei schreiben/appenden |
| `memory_list` | Memory-Dateien auflisten |
| `memory_search` | Semantische Suche über vergangene Chat-Nachrichten und Memory-Dateien |
| `remote_execute` | Shell-Befehl auf verbundenem Remote-Client ausführen (via WebSocket) |
| `send_to_session` | Nachricht an eine andere Session senden und Agent-Antwort auslösen |
| `ask_user_choice` | Interaktive Rückfrage mit 2-5 Auswahlmöglichkeiten (wenn AllowUserChoice aktiv) |
| `github_code_search` | Durchsucht öffentliche GitHub-Repos nach Code (GitHub Code Search API) |
| `serper_web_search` | Internet-Suche via Google (Serper.dev API) |
**Sicherheit:** Alle Pfade werden auf das Workspace-Verzeichnis beschränkt (`..` wird blockiert). Tools sind pro Agent konfigurierbar (Feld `tools` als komma-separierte Liste).
**Tool-Choice:** Automatisch `any` wenn Workspace/Skills/Memory vorhanden, sonst `auto`. Wird zu `none` bei `/no-tools`. Einzelne Tools forcierbar via `/tool:toolname`.
---
## System-Prompt (11+ Blöcke, in Reihenfolge)
1. **Identity** – `IDENTITY.md` des Agents
2. **Soul** – `SOUL.md` des Agents
3. **Anweisungen** – `TaskInstruction` des Agents (optional, wenn gesetzt)
4. **User** – `USER.md` (globale User-Infos) + Persona
5. **Workspace** – Pfad-Info, Tool-Liste
6. **Rückfragen** – Hinweis zu `ask_user_choice` (wenn AllowUserChoice aktiv)
7. **Context** – Session-Kontext (custom system prompt)
8. **Skills** – Zugeordnete Skill-Prompts
9. **Memory** – Aktueller MEMORY.md-Inhalt + Regeln
10. **Git** – Commit-Verhalten (wenn AutoCommit konfiguriert)
11. **Markierte Nachrichten** – Vom User als wichtig markiert
- **[Plan-Modus]** – Überspringt 10; Read-only Tools; "Nur analysieren"
- **[Preparer-Block]** – Instruktionen für Vorbereiter-Agents
**Tool-Choice:** Automatisch `any` wenn Workspace/Skills/Memory vorhanden, sonst `auto`. Nicht konfigurierbar pro Agent.
**History-Reminder:** Alle ~30 Messages wird ein automatisch generierter Reminder injiziert (`[System Reminder] Beachte weiterhin den System-Prompt (...)`) – bekämpft "Lost-in-the-Middle"-Problem.
---
## Frontend-Routing
| Hash-Route | Page-Datei | Beschreibung |
|------------|-----------|--------------|
| `#/` | `dashboard.js` | Session-Übersicht (sortiert nach letzter Chat-Nachricht) |
| `#/chat/{id}` | `chat-page.js` | Chat (inkl. Usage-Badge-Overlay + Key-Switch-Modal) |
| `#/session/{id}` | `session-page.js` | Session-Settings |
| `#/agent/{id}` | `agent-page.js` | Agent-Editor |
| `#/telegram-bot/{id}` | `telegram-bot-page.js` | Telegram-Bot-Editor |
| `#/cronjobs` | `cronjob-page.js` | Cronjobs |
| `#/profile` | `profile-page.js` | Profil |
| `#/settings` | `settings-page.js` | Einstellungen |
| `#/admin` | `admin-page.js` | Benutzerverwaltung (Admin) |
| `#/api-keys` | `api-keys-page.js` | API-Key-Verwaltung + GitHub-Scanner + Event-Scanner (Admin) |
| `#/db-explorer` | `db-explorer-page.js` | Datenbank-Explorer (Admin) |
| `#/server-settings` | `server-settings-page.js` | Servereinstellungen (Admin) |
| `#/login` | `login-page.js` | Login |
| `#/shared/{token}` | `shared-page.js` | Geteilte Nachricht (öffentlich, kein Auth) |
---
## Auth-Flow
1. `POST /api/auth/login` → Token generiert, als `gott_token` HttpOnly Cookie gesetzt
2. Jeder `/api/`-Request → `AuthMiddleware` prüft Cookie → setzt `UserId` + `IsAdmin`
3. Ausnahmen: Login, Autologin, Telegram-Webhook (`/api/telegram/webhook/{botId}`), `GET /api/shared/{token}`, `GET /api/images/file/*`, `/api/remote` (WebSocket) brauchen kein Auth
4. **Krea API-Key**: `/api/images/`-Routen akzeptieren `X-Api-Key`-Header (Config: `KreaApiKey`) für Automation ohne User-Kontext
5. **Impersonate**: Admin kann via `POST /api/admin/users/{id}/impersonate` als anderer User einloggen
6. **Login-Link**: Admin generiert via `POST /api/admin/users/{id}/login-link` einen Autologin-Link (Token-basiert, `GET /api/auth/autologin?token=xxx`)
7. **DefaultPath**: Pro User ein Basis-Ordner (nur vom Admin setzbar), Session-Workspaces sind relativ dazu
---
## Konfiguration (appsettings.json)
```json
{
"Port": 19000 // Server-Port (Default 19000, erfordert Neustart)
}
```
> **Hinweis:** `AnthropicToken` wird über die **API-Key-Verwaltung** im Admin-Bereich verwaltet (DB-Tabelle `api_keys`), nicht mehr über `appsettings.json`.
Verwaltung über **Admin-UI** (`#/server-settings`): Liest `appsettings.Production.json` (Fallback: `appsettings.json` → Default). Schreibt immer in `appsettings.Production.json`.
---
## Deployment
```bash
./deploy.sh # dotnet publish → systemctl restart gott
```
Läuft als `systemd`-Service, Kestrel auf konfigurierbarem Port (Default 19000), typisch hinter Nginx/Caddy Reverse Proxy.
---
## Multi-Agent System (Preparer + Review + Finalizer)
Eine Session kann bis zu **vier Agents** haben: Worker (Pflicht), Preparer (optional, `preparer_agent_id`), Review (optional, `review_agent_id`, `max_review_iterations`), Finalizer (optional, `finalizer_agent_id`).
**Flow:** Preparer analysiert User-Anfrage (→ Vorarbeit leisten oder `[QUESTION]` Rückfrage) → Worker-Agent arbeitet → Review-Agent prüft (`[APPROVED]` / Feedback-Loop) → Finalizer-Agent arbeitet (einmalig) → Fertig.
**Preparer:** Default = Forward (jede Antwort wird automatisch weitergeleitet, `[QUESTION]` stoppt). Bekommt eigene History mit `includeTag: "preparer"`. Aufbereiteter Prompt wird als `[Vorbereitet von AgentName]:` User-Message gespeichert.
**Backend:** `ChatService` baut `SubAgentContext` für Preparer, Review und Finalizer. `RunPreparer()` im ChatEndpoint (vor StreamChat). `RunReview()` + `RunFinalizer()` in AnthropicService. Alle nutzen Provider-Weiche: Anthropic → `RunAgentChat()`, OpenAI → `ChatNonStreaming()` (mit `Tag`-Markierung, Sub-Agent-Messages aus API-History gefiltert).
**CLI-Fallback für Sub-Agents:** Wenn ein Sub-Agent (Preparer, Review, Finalizer) keinem API-Key zugewiesen ist und die Claude CLI verfügbar ist, wird automatisch `RunSubAgentViaCli()` genutzt. Erst wenn weder Key noch CLI verfügbar sind, wird der Sub-Agent übersprungen (kein Hard-Error mehr).
**SSE-Events:**
- Preparer: `preparer_start`, `preparer_text`, `preparer_tool_use`, `preparer_done`
- Review: `review_start`, `review_text`, `review_tool_use`, `review_feedback`, `review_approved`
- Finalizer: `finalizer_start`, `finalizer_text`, `finalizer_tool_use`, `finalizer_done`
**Frontend-Rendering:**
- Streaming: `chat.js` rendert Preparer-/Review-/Finalizer-Events als eigene Bubbles
- History: `renderMarkdown` parst `[Preparer: AgentName]`, `[Vorbereitet von AgentName]`, `[Review: AgentName]`, `[Finalizer: AgentName]`, filtert `[APPROVED]`, `[FORWARD]`, `[QUESTION]`
- Bubble-Klassen: `.preparer-bubble` (Info-Farbe), `.review-bubble` (sekundäre Farbe), `.finalizer-bubble` (grüne Farbe)
## Multi-Modell-Panel (Fusion / MoA)
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).
**Member laufen read-only** (Anthropic: `FilterReadOnlyTools` via `PlanMode`, CLI: `--permission-mode plan`, OpenAI: tool-los via `OpenAiService.CompleteText`) → keine parallelen Schreibkonflikte. Concurrency-Cap (3) begrenzt parallele CLI-Children.
**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. Der Guard blendet diese klassischen Subagent-Events nur **aus** (`continue`) – er tappt sie nicht mehr ab. Der Workflow-Status der dynamischen ultracode-Läufe liest seine Telemetrie ohnehin aus den Transcript-Dateien (siehe unten), nicht aus dem Stream.
**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). 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)
- History + Shared-Page: Tool-Box mit klickbaren Source-Links (Domain + Title)
- Streaming: `pauseStreamBubble()` hält Bubble offen zwischen Web-Searches (keine Doppel-Bubbles)
---
## Workflow-Status (dynamische ultracode-Workflows)
Telemetrie eines **ultracode**-Laufs, den die Claude CLI über das `Workflow`-Tool startet. **Entscheidend:** Solche Workflows laufen als **detachter Hintergrund-Task mit eigenem Transcript-Verzeichnis** – ihre Subagent-Events fließen NICHT durch den Haupt-(`-p`)-Stream (anders als klassische Task-Subagenten mit gesetztem `parent_tool_use_id`). Die echte Telemetrie liegt in zwei Dateien des CLI-Transcripts und wird **on demand** gelesen, nicht aus dem Stream abgegriffen.
**Capture:** Im tool_result-Pfad ruft `ClaudeCliService.TapWorkflowLaunch(sessionId, chainId, resultContent)` auf; enthält der Inhalt „Workflow launched", parst die Methode die Zeile „Transcript dir: <absoluter Pfad>" und ruft `_workflowTracker.Register(...)`. Rein additiv, wirft nie. (Der `parent_tool_use_id`-Guard blendet klassische Subagent-Events weiterhin aus dem sichtbaren Stream aus – er tappt sie aber NICHT mehr.)
**Registry:** `WorkflowProgressTracker` (Singleton, lokale Instanz in `Program.cs`, weil `ClaudeCliService` befüllt und `AnthropicService` beim Finalize liest/persistiert – dieselbe Instanz) ist NUR eine Registry, kein Stream-Akkumulator. Speichert pro Session einen `WorkflowRunRef { ChainId, RunId, JournalPath, RichJsonPath, StartedAt }`, abgeleitet aus der Transcript-Dir. Methoden: `Register` / `Get` / `ChainIdFor` / `End` – in-memory pro Session, defensiv.
**Reader:** Die statische `WorkflowTelemetryReader`-Klasse liest die Telemetrie aus zwei Quellen:
- LIVE `<transcriptDir>/journal.jsonl` (append-only; pro Subagent `started`/`result`-Zeilen) → `ReadLive` liefert `status: "running"`, `subagentCount` (= Anzahl `started`), `completedCount` (= Anzahl `result`), `durationMs`. Keine Tokens/Tool-Calls live verfügbar.
- ABGESCHLOSSEN `<proj>/workflows/<runId>.json` (erst am Laufende geschrieben) → `ReadCompleted` liefert `status: "completed"`, `workflowName`, `summary`, `durationMs`, `phases[]` (index/title/detail), `agents[]` (index/label/phaseTitle/state/model/tokens/toolCalls/durationMs/resultPreview), `totalTokens`, `totalToolCalls`, `subagentCount`, `completedCount`.
`Read()` bevorzugt die reiche, fertige `<runId>.json` und fällt sonst auf das Live-`journal.jsonl` zurück.
**Persistenz:** Beim `FinalizeChain` (`AnthropicService.cs`) wird die reiche `WorkflowProgress`-JSON in `chains.workflow_info` (`ChainRecord.WorkflowInfo`, Migration via `TryAddColumn` in `DatabaseService.cs`, geschrieben in `DatabaseService.Sessions.cs` `UpdateChain`) geschrieben – mit ChainId-Abgleich (`ChainIdFor`), damit ein Panel-Member derselben Session ihn nicht löscht; danach wird der Tracker-Eintrag via `End` freigegeben.
**Endpoint:** `GET /api/sessions/{id}/workflow-status` (`SessionEndpoint.HandleWorkflowStatus`), Reihenfolge: (1) ist für die Session ein Lauf registriert → Telemetrie aus den Transcript-Dateien (running aus `journal.jsonl` / completed aus `<runId>.json`); (2) sonst die jüngste aktive Chain mit nicht-leerem `workflow_info` → `status: "completed"`; (3) sonst `status: "none"`, `workflow: null`. JSON (running/completed): `{ sessionId, streaming, status, chainId, workflow }`; im `none`-Zweig: `{ sessionId, streaming, status: "none", workflow: null }` (KEIN `chainId`). Das `workflow`-Objekt trägt camelCase `status`, `chainId`, `workflowName`, `summary`, `startedAt`, `subagentCount`, `completedCount`, `totalTokens`, `totalToolCalls`, `durationMs`, `phases[]` (index/title/detail), `agents[]` (index/label/phaseTitle/state/model/tokens/toolCalls/durationMs/resultPreview).
**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`. Das Modal rendert **Phasen** + eine **Per-Agent-Liste** (Label, Phase, State, Tokens, Tool-Calls, Dauer) + Summen-Kacheln (`totalTokens`/`totalToolCalls`) – keine `toolBreakdown`/`recentTools` mehr.
---
## Wichtige Regeln beim Entwickeln
- ⚠️ **Quelldateien in `wwwroot/`** editieren, NICHT in `Build/*/publish/wwwroot/`
- ⛔ **`Build/*/publish/data/gott.db`** niemals manuell modifizieren/löschen
- ⛔ **`Build/`** generell nicht anfassen
- 🔧 **.NET 10** ohne Nullable, ohne Implicit Usings
- 📦 DB-Migrationen sind idempotent (IF NOT EXISTS Pattern in `DatabaseService`)
- 🗂️ Native Pfade (Windows: `\`, Linux/macOS: `/`) – Backend liefert OS-Pfade, Frontend matcht beide via `[\\/]`