Phase 5: Subagent-Hierarchie + ROADMAP erweitert

Subagent-Hierarchie:
- Agent Interface erweitert: parentAgentId, depth, model
- claude-bridge.js: Erkennt Task-Tool als Subagent-Start
- events.ts: Listener für subagent-started/stopped
- AgentView.svelte: Baumansicht mit Einrückung + Collapse

ROADMAP erweitert (Phase 5-16):
- Phase 5: Subagent-Hierarchie 
- Phase 6-9: Session, UI, Claude-DB, Context
- Phase 10: Sprach-Interface
- Phase 11: Multi-Agent-Modi (Solo/Handlanger/Experten)
- Phase 12: Hook-System
- Phase 13: VSCodium Integration
- Phase 14: Programm-Steuerung (Playwright, D-Bus)
- Phase 15: Schulungsmodus (Mermaid, animierter Code)
- Phase 16: System-Monitor (Debug-Panel)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Eddy 2026-04-14 12:35:29 +02:00
parent 18c8ef2f4f
commit 6cfcdb2c79
5 changed files with 1096 additions and 51 deletions

View file

@ -594,9 +594,668 @@ CREATE TABLE concept_cache (
--- ---
## Nicht geplant / Zukunft ## Phase 13: VSCodium Integration (IDE-Steuerung)
- [ ] Computer Use (VM mit Desktop) ### Das Ziel
Claude bedient VSCodium komplett — OHNE deine Maus zu blockieren.
### Architektur
```
Claude Desktop ←→ VSCode Extension Bridge ←→ VSCodium
│ │
│ ├─ VSCode Commands (Tabs, Panels, Git)
│ ├─ LSP (Refactoring, Go-To, Find References)
│ └─ Debug Adapter (Breakpoints, Step)
└─ Kein Computer Use! Keine Screenshots! Keine Maus-Simulation!
```
### Was Claude damit kann
| Aktion | Wie | Aktuell? |
|--------|-----|----------|
| Datei öffnen | `workbench.action.files.openFile` | ❌ Nein |
| Code formatieren | `editor.action.formatDocument` | ❌ Nein |
| Suche in Projekt | `workbench.action.findInFiles` | ❌ Nein |
| Git Commit | `git.commit` | ❌ (nur CLI) |
| Debugger starten | `workbench.action.debug.start` | ❌ Nein |
| Refactoring | LSP `textDocument/rename` | ❌ Nein |
| Go-To Definition | LSP `textDocument/definition` | ❌ Nein |
| Terminal öffnen | `workbench.action.terminal.new` | ❌ Nein |
### Aufgaben
- [ ] **VSCode Extension: Claude Bridge**
- [ ] Neues Extension-Projekt erstellen
- [ ] WebSocket-Server für Kommunikation mit Claude Desktop
- [ ] Command-Executor: VSCode Commands ausführen
- [ ] LSP-Bridge: Language Server Anfragen weiterleiten
- [ ] Status-Reporter: Was ist gerade offen? Wo ist Cursor?
- [ ] **Claude Desktop: IDE-Connector**
- [ ] `src-tauri/src/ide.rs` — Verbindung zur Extension
- [ ] `connectToIDE()` — WebSocket zu VSCodium
- [ ] `executeIDECommand(command, args)` — Befehl ausführen
- [ ] `getLSPInfo(file, position)` — Semantische Infos
- [ ] **Neue Tools für Claude**
- [ ] `IDE.openFile(path)` — Datei im Editor öffnen
- [ ] `IDE.goToLine(path, line)` — Zu Zeile springen
- [ ] `IDE.formatDocument()` — Formatieren
- [ ] `IDE.rename(oldName, newName)` — Refactoring
- [ ] `IDE.findReferences(symbol)` — Alle Verwendungen
- [ ] `IDE.startDebug(config)` — Debugger starten
- [ ] `IDE.gitCommit(message)` — Via Git-Panel
- [ ] **UI: IDE-Status anzeigen**
- [ ] Verbindungsstatus zu VSCodium
- [ ] Aktuell offene Datei
- [ ] Cursor-Position
### Warum NICHT Computer Use?
| Aspekt | Computer Use | Extension Commands |
|--------|--------------|-------------------|
| Geschwindigkeit | Langsam (Screenshots) | Sofort |
| Zuverlässigkeit | Fehleranfällig | 100% präzise |
| Maus blockiert? | Ja | Nein |
| Kosten | Hoch (viele API-Calls) | Minimal |
| Extra Display? | Ja (oder VM) | Nein |
### Verifikation
```bash
# Claude Desktop starten, VSCodium starten
# In Claude: "Öffne die Datei src/lib.rs"
# → VSCodium öffnet die Datei, DU kannst weiter tippen
# In Claude: "Formatiere das Dokument"
# → Code wird formatiert, ohne dass du was tust
```
---
## Phase 14: Programm-Steuerung (Nicht nur IDE!)
### Das Problem
Terminal ist ein Flaschenhals für:
- **GUI-Programme** (Dolibarr im Browser, Systemeinstellungen)
- **Visuelle Aufgaben** (Screenshots prüfen, UI testen)
- **Programme ohne CLI** (viele Desktop-Apps)
### Lösungs-Stack (je nach Programm-Typ)
```
┌────────────────────────────────────────────────────────────────┐
│ PROGRAMM-TYP │ STEUERUNG │
├────────────────────────────────────────────────────────────────┤
│ VSCodium/IDE │ Extension Commands (Phase 13) │
│ Browser/Web-Apps │ Playwright MCP ← Dolibarr! │
│ Terminal/CLI │ Bash (bereits vorhanden) │
│ Native Linux-Apps │ D-Bus + XDG-Portals │
│ Alles andere │ Computer Use (separates Display) │
└────────────────────────────────────────────────────────────────┘
```
### Priorität 1: Playwright für Web-Apps (Dolibarr!)
Der Playwright MCP-Server ist bereits konfiguriert! Damit kann Claude:
| Aktion | Playwright-Tool | Use Case |
|--------|----------------|----------|
| Seite öffnen | `browser_navigate` | Dolibarr aufrufen |
| Element klicken | `browser_click` | Buttons, Links |
| Formular ausfüllen | `browser_fill_form` | Daten eingeben |
| Screenshot | `browser_take_screenshot` | UI prüfen |
| Warten | `browser_wait_for` | Auf Ladezeit warten |
| Snapshot (DOM) | `browser_snapshot` | Struktur analysieren |
**Vorteil:** Läuft im Hintergrund, blockiert NICHT deine Maus!
### Priorität 2: D-Bus für Linux-Apps
Viele Linux-Programme sind über D-Bus steuerbar:
```bash
# Beispiele
dbus-send --session --dest=org.kde.dolphin ... # Dateimanager
dbus-send --session --dest=org.kde.kate ... # Kate Editor
qdbus org.freedesktop.Notifications ... # Benachrichtigungen
```
### Priorität 3: Computer Use (Notfall-Option)
Für Programme die KEINE API/CLI/D-Bus haben:
```
┌─────────────────────────────────────────────────────────────┐
│ LÖSUNG: Separates Display (:1) │
│ │
│ Display :0 (Haupt) │ Display :1 (Claude) │
│ ├─ Dein Desktop │ ├─ Xvfb (virtuell) │
│ ├─ Deine Maus ✓ │ ├─ Claude steuert │
│ └─ Kein Konflikt │ └─ VNC-Zugang optional │
│ │
│ Du arbeitest normal │ Claude bedient GUI-Programme │
└─────────────────────────────────────────────────────────────┘
```
### Aufgaben
- [ ] **Playwright Integration aktivieren**
- [ ] MCP-Server ist bereits da → Tools verfügbar machen
- [ ] Dolibarr-Login automatisieren (Session merken)
- [ ] UI in App: Browser-Aktionen anzeigen
- [ ] **D-Bus Modul**
- [ ] `src-tauri/src/dbus.rs` (NEU)
- [ ] `list_dbus_services()` — Was ist verfügbar?
- [ ] `call_dbus_method(service, path, method, args)`
- [ ] Wrapper für häufige Aktionen (Dolphin, Kate)
- [ ] **Computer Use (separates Display)**
- [ ] `src-tauri/src/xdisplay.rs` (NEU)
- [ ] `start_virtual_display()` — Xvfb auf :1 starten
- [ ] `capture_display(display_num)` — Screenshot
- [ ] `send_input(display_num, action)` — Maus/Tastatur
- [ ] VNC-Server optional für Monitoring
- [ ] **UI: Programm-Steuerung Panel**
- [ ] Browser-View (Playwright-Screenshots)
- [ ] D-Bus Services auflisten
- [ ] Computer-Use Display einbetten (VNC-Widget)
- [ ] Toggle: "Zeige was Claude gerade bedient"
### Entscheidungsbaum für Claude
```
Aufgabe erhalten
├─ Ist es eine Web-App? ────────────→ Playwright
├─ Ist es VSCodium/IDE? ────────────→ Extension Commands
├─ Hat das Programm CLI? ───────────→ Terminal/Bash
├─ Hat das Programm D-Bus? ─────────→ D-Bus
└─ Alles andere ────────────────────→ Computer Use (Display :1)
```
### Verifikation
```bash
# Test: Playwright
# Claude: "Öffne Dolibarr und zeige mir die offenen Rechnungen"
# → Browser öffnet, navigiert, Screenshot im App
# Test: D-Bus
# Claude: "Öffne Dolphin im Ordner ~/Downloads"
# → Dolphin öffnet sich mit dem Ordner
# Test: Computer Use
# Claude: "Öffne LibreOffice und erstelle ein Dokument"
# → Display :1 zeigt LibreOffice, VNC optional zum Zuschauen
```
---
## Phase 15: Präsentations- & Schulungsmodus (Lehrer-Modus)
### Das Ziel
Claude als **Lehrer** — erklärt, visualisiert, programmiert langsam und nachvollziehbar.
```
┌─────────────────────────────────────────────────────────────────┐
│ NORMALER MODUS │ SCHULUNGSMODUS │
│ • Claude arbeitet schnell │ • Claude erklärt jeden Schritt │
│ • Ergebnis im Chat │ • Visualisierungen live │
│ • Effizient │ • Code wird "getippt" │
│ │ • Pause/Play jederzeit │
│ │ • Geschwindigkeit einstellbar │
└─────────────────────────────────────────────────────────────────┘
```
### Features
#### 1. Präsentationsfenster
Separates Fenster (oder Panel) für Visualisierungen:
```
┌─────────────────────────────────────────────────────────────────┐
│ Präsentation [─][□][×] │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────┐ │
│ │ Thema │ │
│ └────┬────┘ │
│ ┌───────┼───────┐ │
│ ▼ ▼ ▼ │
│ ┌───────┐ ┌───────┐ ┌───────┐ │
│ │Punkt 1│ │Punkt 2│ │Punkt 3│ ← Live Mindmap │
│ └───────┘ └───────┘ └───────┘ │
│ │
├─────────────────────────────────────────────────────────────────┤
│ ◀◀ ▶ Pause ▶▶ │ Geschwindigkeit: [████░░░░] 60% │ 📢 TTS │
└─────────────────────────────────────────────────────────────────┘
```
#### 2. Visualisierungs-Typen
| Typ | Technologie | Use Case |
|-----|-------------|----------|
| **Mindmap** | Mermaid.js | Konzepte erklären |
| **Flowchart** | Mermaid.js | Programmablauf |
| **Sequenzdiagramm** | Mermaid.js | API-Calls, Prozesse |
| **Code** | Monaco Editor | Live Coding |
| **Architektur** | Mermaid/D3.js | System-Übersicht |
| **Freihand** | Excalidraw | Skizzen |
| **Terminal** | xterm.js | Befehle zeigen |
#### 3. Animiertes Coding
Claude "tippt" Code langsam — wie ein Mensch:
```typescript
// Geschwindigkeit einstellbar: 30-200 WPM
function animateCode(code: string, wpm: number) {
const charDelay = 60000 / (wpm * 5); // ~5 Zeichen/Wort
for (const char of code) {
await sleep(charDelay);
appendChar(char);
highlightLine(currentLine);
}
}
```
**Features:**
- Syntax-Highlighting während des Tippens
- Cursor-Animation
- Zeile wird hervorgehoben
- Erklärung erscheint parallel (Chat oder TTS)
#### 4. Erklär-Modus
Claude erklärt **während** er arbeitet:
```
┌─ Code-Panel ──────────────┬─ Erklärung ─────────────────────────┐
│ │ │
│ function add(a, b) { │ "Ich definiere eine Funktion │
│ return a + b; ← cursor │ namens 'add' mit zwei Parametern. │
│ } │ Diese gibt die Summe zurück." │
│ │ │
│ │ [🔊 Vorlesen] │
└───────────────────────────┴──────────────────────────────────────┘
```
#### 5. Interaktive Elemente
- **Pause** — Jederzeit anhalten
- **Zurück** — Letzten Schritt wiederholen
- **Frage stellen** — Unterbricht Präsentation
- **Geschwindigkeit** — Slider (langsam ↔ schnell)
- **TTS ein/aus** — Sprachausgabe toggle
- **Vollbild** — Für Präsentationen
### Schulungs-Modi
```
┌─────────────────────────────────────────────────────────────────┐
│ MODUS: ERKLÄRER │
│ Claude erklärt ein Konzept mit Visualisierungen │
│ "Erkläre mir wie async/await funktioniert" │
│ → Mindmap + Flowchart + Code-Beispiele │
├─────────────────────────────────────────────────────────────────┤
│ MODUS: LIVE CODING │
│ Claude programmiert langsam und erklärt jeden Schritt │
│ "Zeig mir wie man einen REST-API-Client baut" │
│ → Code wird getippt, jede Zeile erklärt │
├─────────────────────────────────────────────────────────────────┤
│ MODUS: WALKTHROUGH │
│ Claude führt durch existierenden Code │
│ "Erkläre mir diese Datei" │
│ → Code wird Zeile für Zeile durchgegangen │
├─────────────────────────────────────────────────────────────────┤
│ MODUS: KONFIGURATION │
│ Claude erklärt Config-Dateien │
│ "Erkläre mir die nginx.conf" │
│ → Optionen werden einzeln erklärt + visualisiert │
└─────────────────────────────────────────────────────────────────┘
```
### Aufgaben
- [ ] **Präsentationsfenster**
- [ ] Neues Tauri-Window für Präsentation
- [ ] Kann neben Hauptfenster existieren
- [ ] Steuerleiste (Play/Pause/Speed)
- [ ] Vollbild-Toggle
- [ ] **Mermaid.js Integration**
- [ ] `src/lib/components/MermaidDiagram.svelte`
- [ ] Live-Rendering von Diagrammen
- [ ] Animierte Knoten-Hinzufügung
- [ ] Zoom und Pan
- [ ] **Animiertes Code-Panel**
- [ ] `src/lib/components/AnimatedCode.svelte`
- [ ] Monaco Editor mit Tipp-Animation
- [ ] Geschwindigkeits-Kontrolle
- [ ] Zeilen-Highlighting
- [ ] Cursor-Animation
- [ ] **Erklär-System**
- [ ] Claude generiert Erklärungen parallel zum Code
- [ ] Synchronisation: Code-Position ↔ Erklärung
- [ ] Optional: TTS für Erklärungen (Phase 10)
- [ ] **Schulungs-Prompts**
- [ ] Spezieller System-Prompt für Lehrer-Modus
- [ ] "Du bist ein geduldiger Lehrer. Erkläre jeden Schritt."
- [ ] Strukturierte Ausgabe: `{code, explanation, visualization}`
- [ ] **UI: Modus-Wechsel**
- [ ] Button: "Schulungsmodus aktivieren"
- [ ] Oder: "Erkläre mir das" Button bei jeder Antwort
- [ ] Settings: Standard-Geschwindigkeit, TTS an/aus
- [ ] **Excalidraw-ähnliche Skizzen**
- [ ] Für Freihand-Diagramme
- [ ] Claude kann "zeichnen" (Koordinaten → Shapes)
- [ ] Export als PNG/SVG
### Beispiel-Ablauf
**User:** "Erkläre mir wie Git Branches funktionieren"
**Claude (Schulungsmodus):**
1. **Mindmap erscheint:**
```mermaid
mindmap
root((Git Branches))
Was ist ein Branch?
Zeiger auf Commit
Leichtgewichtig
Warum Branches?
Parallele Entwicklung
Features isolieren
Befehle
git branch
git checkout
git merge
```
2. **Code-Animation:**
```bash
# Neuen Branch erstellen
git branch feature-login ← tippt langsam
# Zum Branch wechseln
git checkout feature-login
```
3. **Erklärung (parallel):**
> "Ein Branch ist wie ein paralleles Universum für deinen Code.
> Du kannst Änderungen machen ohne den Hauptcode zu beeinflussen..."
4. **Flowchart:**
```mermaid
gitGraph
commit id: "Initial"
branch feature-login
commit id: "Login-Form"
checkout main
commit id: "Hotfix"
merge feature-login
```
### Verifikation
```bash
# Test: Schulungsmodus aktivieren
# "Erkläre mir Promises in JavaScript"
# → Präsentationsfenster öffnet sich
# → Mindmap erscheint animiert
# → Code wird langsam getippt
# → Erklärungen synchron angezeigt
# → Pause funktioniert
# → Geschwindigkeit änderbar
```
---
## Phase 16: System-Monitor (Debug-Panel)
### Das Ziel
**Echtzeit-Einblick** was im Hintergrund passiert — vom Programm generiert, NICHT von der KI!
```
┌─────────────────────────────────────────────────────────────────┐
│ System-Monitor [Filter ▼] │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 14:32:01.234 🔵 API → claude-3-opus [1.2s] 2.4k tok │
│ 14:32:02.891 🟢 HOOK SessionStart fired │
│ 14:32:03.012 🟡 TOOL Read src/lib.rs (245 lines) │
│ 14:32:03.456 🔵 API ← Response streaming... │
│ 14:32:04.123 🟡 TOOL Grep "handleError" (3 matches) │
│ 14:32:04.567 🟣 MCP playwright.browser_navigate │
│ 14:32:05.234 🔴 ERROR Connection timeout (retry 1/3) │
│ 14:32:05.890 🟢 HOOK PreToolUse → injected 2 hints │
│ │
├─────────────────────────────────────────────────────────────────┤
│ ▶ Details: API Request claude-3-opus [Copy] │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ Model: claude-3-opus-20240229 ││
│ │ Input: 12,456 tokens ││
│ │ Output: 2,341 tokens ││
│ │ Cost: $0.42 ││
│ │ Latency: 1,234ms (first token: 234ms) ││
│ │ System Prompt: 2,100 tokens ││
│ │ Context: Session abc123, Turn 5 ││
│ └─────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────┘
```
### Event-Typen (Farbcodiert)
| Farbe | Typ | Was wird geloggt |
|-------|-----|------------------|
| 🔵 Blau | **API** | Requests/Responses zu Claude API |
| 🟢 Grün | **HOOK** | Hook-Ausführungen (SessionStart, PreTool, etc.) |
| 🟡 Gelb | **TOOL** | Tool-Aufrufe (Read, Grep, Edit, Bash) |
| 🟣 Lila | **MCP** | MCP-Server Kommunikation |
| 🟠 Orange | **AGENT** | Subagent Start/Stop/Delegation |
| 🔴 Rot | **ERROR** | Fehler, Timeouts, Retries |
| ⚪ Grau | **DEBUG** | Interne Events (optional) |
### Zwei Ansichten
#### 1. Kompakt-Ansicht (Standard)
Eine Zeile pro Event:
```
14:32:01 🔵 API → claude-3-opus [1.2s] 2.4k tok
```
#### 2. Detail-Ansicht (Klick auf Event)
Vollständige Informationen:
```json
{
"type": "api_request",
"timestamp": "2026-04-14T14:32:01.234Z",
"model": "claude-3-opus-20240229",
"input_tokens": 12456,
"output_tokens": 2341,
"latency_ms": 1234,
"first_token_ms": 234,
"cost_usd": 0.42,
"session_id": "abc123",
"turn": 5,
"tools_available": ["Read", "Edit", "Bash", ...],
"system_prompt_tokens": 2100
}
```
### Was wird geloggt?
#### API-Events
- Request gesendet (Modell, Token-Schätzung)
- Streaming gestartet
- Response komplett (Token, Kosten, Latenz)
- Errors (Rate Limit, Timeout, etc.)
#### Hook-Events
- Hook Name + Zeitpunkt
- Payload (gekürzt)
- Ergebnis (injizierte Hints, blockiert, etc.)
- Dauer
#### Tool-Events
- Tool-Name + Parameter (gekürzt)
- Ergebnis-Zusammenfassung
- Dauer
- Fehler wenn vorhanden
#### MCP-Events
- Server-Name
- Tool/Resource aufgerufen
- Parameter + Ergebnis
- Verbindungsstatus
#### Agent-Events
- Agent gestartet (ID, Typ, Parent)
- Agent beendet (Tokens verbraucht)
- Delegation (von → an)
### Aufgaben
- [ ] **Event-System im Backend**
- [ ] `src-tauri/src/monitor.rs` (NEU)
- [ ] `EventBus` Struct mit Ringbuffer (max 10.000 Events)
- [ ] `emit_event(type, data)` — Thread-safe
- [ ] Events an Frontend via Tauri Events
- [ ] Persistierung optional (SQLite)
- [ ] **API-Interceptor**
- [ ] `scripts/claude-bridge.js` erweitern
- [ ] Vor/Nach jedem `query()` Call loggen
- [ ] Token zählen, Kosten berechnen
- [ ] Latenz messen (first token, total)
- [ ] **Hook-Logger**
- [ ] Bei jedem Hook-Fire: Event emittieren
- [ ] Payload (gekürzt auf 500 Zeichen)
- [ ] Ergebnis + Dauer
- [ ] **Tool-Logger**
- [ ] Wrapper um jedes Tool
- [ ] Parameter loggen (sensitive Daten maskieren)
- [ ] Ergebnis-Zusammenfassung
- [ ] Fehler + Stack Trace
- [ ] **UI: Monitor-Panel**
- [ ] `src/lib/components/MonitorPanel.svelte` (NEU)
- [ ] Virtualisierte Liste (Performance bei vielen Events)
- [ ] Farbcodierung nach Event-Typ
- [ ] Filter-Dropdown (nur API, nur Errors, etc.)
- [ ] Suchfeld
- [ ] Auto-Scroll (abschaltbar)
- [ ] Detail-Ansicht bei Klick
- [ ] Copy-Button für Details
- [ ] **Log-Export**
- [ ] Als JSON exportieren
- [ ] Als Text (lesbar) exportieren
- [ ] Zeitraum wählbar
- [ ] **Performance-Metriken**
- [ ] Sidebar-Widget: Requests/min, Avg Latency
- [ ] Token-Verbrauch Grafik (letzte Stunde)
- [ ] Kosten-Tracker (Session, Tag, Monat)
### Sensitive Daten maskieren!
```typescript
function maskSensitive(data: string): string {
return data
.replace(/password[=:]\s*\S+/gi, 'password=***')
.replace(/api[_-]?key[=:]\s*\S+/gi, 'api_key=***')
.replace(/bearer\s+\S+/gi, 'Bearer ***')
.replace(/sk-[a-zA-Z0-9]+/g, 'sk-***');
}
```
### Datenstruktur
```typescript
interface MonitorEvent {
id: string;
timestamp: Date;
type: 'api' | 'hook' | 'tool' | 'mcp' | 'agent' | 'error' | 'debug';
// Kompakt-Ansicht
summary: string; // "→ claude-3-opus [1.2s] 2.4k tok"
// Detail-Ansicht
details: {
// Je nach Typ unterschiedlich
[key: string]: any;
};
// Metadaten
session_id?: string;
agent_id?: string;
duration_ms?: number;
error?: string;
}
```
### SQLite-Schema (optional für Persistierung)
```sql
CREATE TABLE monitor_events (
id TEXT PRIMARY KEY,
timestamp TEXT NOT NULL,
type TEXT NOT NULL,
summary TEXT NOT NULL,
details JSON,
session_id TEXT,
agent_id TEXT,
duration_ms INTEGER,
error TEXT,
INDEX idx_timestamp (timestamp),
INDEX idx_type (type),
INDEX idx_session (session_id)
);
-- Automatisch alte Events löschen (> 7 Tage)
CREATE TRIGGER cleanup_old_events
AFTER INSERT ON monitor_events
BEGIN
DELETE FROM monitor_events
WHERE timestamp < datetime('now', '-7 days');
END;
```
### Verifikation
```bash
# Test: Monitor-Panel öffnen
# Nachricht senden → Events erscheinen live
# 🔵 API Request/Response sichtbar
# 🟡 Tool-Aufrufe sichtbar
# Auf Event klicken → Details erscheinen
# Filter auf "ERROR" → nur Fehler sichtbar
# Export → JSON-Datei mit allen Events
```
---
## Nicht geplant / Zukunft
- [ ] MCP-Server Integration in App - [ ] MCP-Server Integration in App
- [ ] Plugin-System - [ ] Plugin-System
- [ ] Multi-User / Team-Features - [ ] Multi-User / Team-Features

View file

@ -19,6 +19,10 @@ let activeAbort = null;
let currentAgentId = null; let currentAgentId = null;
let currentModel = process.env.CLAUDE_MODEL || 'opus'; let currentModel = process.env.CLAUDE_MODEL || 'opus';
// Subagent-Tracking
// Map: toolUseId → { agentId, parentId, type, task, depth }
const activeSubagents = new Map();
// Verfügbare Modelle // Verfügbare Modelle
const AVAILABLE_MODELS = [ const AVAILABLE_MODELS = [
{ id: 'haiku', name: 'Claude Haiku', description: 'Schnell & günstig' }, { id: 'haiku', name: 'Claude Haiku', description: 'Schnell & günstig' },
@ -26,6 +30,26 @@ const AVAILABLE_MODELS = [
{ id: 'opus', name: 'Claude Opus', description: 'Leistungsstark' }, { id: 'opus', name: 'Claude Opus', description: 'Leistungsstark' },
]; ];
// Tools die Subagents spawnen
const SUBAGENT_TOOLS = ['Task', 'Agent', 'spawn_agent', 'launch_agent'];
// Subagent-Typ aus Tool-Input ermitteln
function getSubagentType(toolName, input) {
if (input?.subagent_type) return input.subagent_type.toLowerCase();
if (input?.agent_type) return input.agent_type.toLowerCase();
// Fallback basierend auf description/prompt
const desc = (input?.description || input?.prompt || '').toLowerCase();
if (desc.includes('explore') || desc.includes('search') || desc.includes('find')) return 'explore';
if (desc.includes('plan') || desc.includes('design')) return 'plan';
if (desc.includes('bash') || desc.includes('command') || desc.includes('terminal')) return 'bash';
if (desc.includes('code') || desc.includes('implement') || desc.includes('write')) return 'code';
if (desc.includes('test') || desc.includes('verify')) return 'test';
if (desc.includes('review') || desc.includes('check')) return 'review';
return 'explore'; // Default
}
// ============ Kommunikation mit Tauri ============ // ============ Kommunikation mit Tauri ============
function sendToTauri(msg) { function sendToTauri(msg) {
@ -93,20 +117,73 @@ async function sendMessage(message, requestId, model = null) {
} }
break; break;
case 'tool_use': case 'tool_use': {
sendEvent('tool-start', { const toolId = event.tool_use_id || randomUUID();
id: event.tool_use_id || randomUUID(), const toolName = event.name || 'unknown';
tool: event.name || 'unknown', const toolInput = event.input || {};
input: event.input || {},
});
break;
case 'tool_result': // Prüfen ob dieses Tool einen Subagent startet
sendEvent('tool-end', { if (SUBAGENT_TOOLS.includes(toolName)) {
id: event.tool_use_id || '', const subagentId = randomUUID();
success: !event.is_error, const subagentType = getSubagentType(toolName, toolInput);
const subagentTask = toolInput.description || toolInput.prompt || toolInput.task || 'Subagent-Aufgabe';
const subagentModel = toolInput.model || useModel;
// Tiefe berechnen (Main = 0, erster Sub = 1, etc.)
// Für jetzt: immer depth 1 (direkter Subagent vom Main)
const depth = 1;
activeSubagents.set(toolId, {
agentId: subagentId,
parentId: currentAgentId,
type: subagentType,
task: subagentTask,
depth,
model: subagentModel,
});
sendEvent('subagent-started', {
id: subagentId,
parentAgentId: currentAgentId,
type: subagentType,
task: subagentTask.substring(0, 100),
depth,
model: subagentModel,
toolUseId: toolId,
});
}
sendEvent('tool-start', {
id: toolId,
tool: toolName,
input: toolInput,
agentId: currentAgentId,
}); });
break; break;
}
case 'tool_result': {
const toolId = event.tool_use_id || '';
// Prüfen ob dieser Tool-Call ein Subagent war
if (activeSubagents.has(toolId)) {
const subagent = activeSubagents.get(toolId);
sendEvent('subagent-stopped', {
id: subagent.agentId,
parentAgentId: subagent.parentId,
success: !event.is_error,
toolUseId: toolId,
});
activeSubagents.delete(toolId);
}
sendEvent('tool-end', {
id: toolId,
success: !event.is_error,
agentId: currentAgentId,
});
break;
}
case 'result': case 'result':
// Endergebnis // Endergebnis
@ -135,6 +212,17 @@ async function sendMessage(message, requestId, model = null) {
sendEvent('text', { text: `\n\n**Fehler:** ${err.message || err}` }); sendEvent('text', { text: `\n\n**Fehler:** ${err.message || err}` });
} }
} finally { } finally {
// Alle noch aktiven Subagents stoppen
for (const [toolId, subagent] of activeSubagents) {
sendEvent('subagent-stopped', {
id: subagent.agentId,
parentAgentId: subagent.parentId,
success: false, // Vorzeitig beendet
toolUseId: toolId,
});
}
activeSubagents.clear();
sendEvent('agent-stopped', { id: currentAgentId, code: 0 }); sendEvent('agent-stopped', { id: currentAgentId, code: 0 });
sendEvent('all-stopped'); sendEvent('all-stopped');
currentAgentId = null; currentAgentId = null;

View file

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { agents, selectedAgentId, agentCount } from '$lib/stores/app'; import { agents, selectedAgentId, agentCount, agentTree, type AgentTreeNode } from '$lib/stores/app';
import type { Agent } from '$lib/stores/app'; import type { Agent } from '$lib/stores/app';
// Status-Icons // Status-Icons
@ -10,14 +10,40 @@
stopped: '🔴' stopped: '🔴'
}; };
// Typ-Namen // Typ-Namen (erweitert für alle Agent-Typen)
const typeNames: Record<Agent['type'], string> = { const typeNames: Record<Agent['type'], string> = {
main: 'Main Agent', main: 'Main Agent',
explore: 'Explore', explore: 'Explore',
plan: 'Plan', plan: 'Plan',
bash: 'Bash' bash: 'Bash',
code: 'Code',
test: 'Test',
review: 'Review'
}; };
// Typ-Icons
const typeIcons: Record<Agent['type'], string> = {
main: '🤖',
explore: '🔍',
plan: '📋',
bash: '💻',
code: '✏️',
test: '🧪',
review: '👀'
};
// Collapsed State für Baumknoten
let collapsedNodes = new Set<string>();
function toggleCollapse(id: string) {
if (collapsedNodes.has(id)) {
collapsedNodes.delete(id);
} else {
collapsedNodes.add(id);
}
collapsedNodes = collapsedNodes; // Trigger reactivity
}
function selectAgent(id: string) { function selectAgent(id: string) {
$selectedAgentId = $selectedAgentId === id ? null : id; $selectedAgentId = $selectedAgentId === id ? null : id;
} }
@ -31,13 +57,84 @@
} }
return `${seconds}s`; return `${seconds}s`;
} }
// Rekursives Rendern von Baum-Knoten
function hasChildren(node: AgentTreeNode): boolean {
return node.children.length > 0;
}
</script> </script>
<!-- Rekursive Komponente für Baum-Knoten -->
{#snippet agentNode(node: AgentTreeNode, depth: number)}
{@const agent = node.agent}
{@const isCollapsed = collapsedNodes.has(agent.id)}
{@const hasKids = hasChildren(node)}
<div class="agent-node" style="--depth: {depth}">
<!-- Verbindungslinie -->
{#if depth > 0}
<div class="tree-line"></div>
{/if}
<button
class="agent-item"
class:selected={$selectedAgentId === agent.id}
class:active={agent.status === 'active'}
class:is-subagent={depth > 0}
on:click={() => selectAgent(agent.id)}
>
<!-- Collapse-Toggle wenn Kinder vorhanden -->
{#if hasKids}
<button
class="collapse-toggle"
on:click|stopPropagation={() => toggleCollapse(agent.id)}
title={isCollapsed ? 'Aufklappen' : 'Zuklappen'}
>
{isCollapsed ? '▶' : '▼'}
</button>
{:else}
<span class="collapse-spacer"></span>
{/if}
<div class="agent-content">
<div class="agent-main">
<span class="agent-status">{statusIcons[agent.status]}</span>
<span class="agent-type-icon" title={typeNames[agent.type]}>{typeIcons[agent.type]}</span>
<span class="agent-type">{typeNames[agent.type]}</span>
{#if agent.model}
<span class="agent-model">{agent.model}</span>
{/if}
<span class="agent-duration">({formatDuration(agent.startedAt)})</span>
</div>
<div class="agent-task">{agent.task}</div>
<div class="agent-meta">
<span class="agent-tools">🔧 {agent.toolCalls.length}</span>
{#if depth > 0}
<span class="agent-depth">Ebene {depth}</span>
{/if}
</div>
</div>
</button>
<!-- Kinder rekursiv rendern -->
{#if hasKids && !isCollapsed}
<div class="agent-children">
{#each node.children as child}
{@render agentNode(child, depth + 1)}
{/each}
</div>
{/if}
</div>
{/snippet}
<div class="agent-view"> <div class="agent-view">
<div class="agent-header"> <div class="agent-header">
<h2>🤖 Agents & Sub-Agents</h2> <h2>🤖 Agents</h2>
<div class="agent-summary"> <div class="agent-summary">
{$agentCount.total} gesamt | {$agentCount.active} aktiv {$agentCount.total} gesamt |
{$agentCount.mainAgents} Main |
{$agentCount.subAgents} Sub |
{$agentCount.active} aktiv
</div> </div>
</div> </div>
@ -47,24 +144,9 @@
<p class="hint">Agents erscheinen hier wenn Claude arbeitet.</p> <p class="hint">Agents erscheinen hier wenn Claude arbeitet.</p>
</div> </div>
{:else} {:else}
<div class="agent-list"> <div class="agent-tree">
{#each $agents as agent} {#each $agentTree as rootNode}
<button {@render agentNode(rootNode, 0)}
class="agent-item"
class:selected={$selectedAgentId === agent.id}
class:active={agent.status === 'active'}
on:click={() => selectAgent(agent.id)}
>
<div class="agent-main">
<span class="agent-status">{statusIcons[agent.status]}</span>
<span class="agent-type">{typeNames[agent.type]}</span>
<span class="agent-duration">({formatDuration(agent.startedAt)})</span>
</div>
<div class="agent-task">{agent.task}</div>
<div class="agent-tools">
Tools: {agent.toolCalls.length} Aufrufe
</div>
</button>
{/each} {/each}
</div> </div>
@ -73,7 +155,8 @@
{@const selectedAgent = $agents.find((a) => a.id === $selectedAgentId)} {@const selectedAgent = $agents.find((a) => a.id === $selectedAgentId)}
{#if selectedAgent} {#if selectedAgent}
<div class="agent-details"> <div class="agent-details">
<h3>Details: {typeNames[selectedAgent.type]}</h3> <h3>{typeIcons[selectedAgent.type]} {typeNames[selectedAgent.type]}</h3>
<div class="detail-row"> <div class="detail-row">
<span class="detail-label">Status:</span> <span class="detail-label">Status:</span>
<span class="detail-value">{statusIcons[selectedAgent.status]} {selectedAgent.status}</span> <span class="detail-value">{statusIcons[selectedAgent.status]} {selectedAgent.status}</span>
@ -82,6 +165,12 @@
<span class="detail-label">Aufgabe:</span> <span class="detail-label">Aufgabe:</span>
<span class="detail-value">{selectedAgent.task}</span> <span class="detail-value">{selectedAgent.task}</span>
</div> </div>
{#if selectedAgent.model}
<div class="detail-row">
<span class="detail-label">Modell:</span>
<span class="detail-value">{selectedAgent.model}</span>
</div>
{/if}
<div class="detail-row"> <div class="detail-row">
<span class="detail-label">Gestartet:</span> <span class="detail-label">Gestartet:</span>
<span class="detail-value">{selectedAgent.startedAt.toLocaleTimeString('de-DE')}</span> <span class="detail-value">{selectedAgent.startedAt.toLocaleTimeString('de-DE')}</span>
@ -90,6 +179,18 @@
<span class="detail-label">Laufzeit:</span> <span class="detail-label">Laufzeit:</span>
<span class="detail-value">{formatDuration(selectedAgent.startedAt)}</span> <span class="detail-value">{formatDuration(selectedAgent.startedAt)}</span>
</div> </div>
{#if selectedAgent.parentAgentId}
<div class="detail-row">
<span class="detail-label">Parent:</span>
<span class="detail-value parent-link" on:click={() => selectAgent(selectedAgent.parentAgentId!)}>
{selectedAgent.parentAgentId.substring(0, 8)}...
</span>
</div>
{/if}
<div class="detail-row">
<span class="detail-label">Tiefe:</span>
<span class="detail-value">{selectedAgent.depth}</span>
</div>
<h4>Tool-Aufrufe ({selectedAgent.toolCalls.length})</h4> <h4>Tool-Aufrufe ({selectedAgent.toolCalls.length})</h4>
<div class="tool-list"> <div class="tool-list">
@ -150,13 +251,34 @@
margin-top: var(--spacing-sm); margin-top: var(--spacing-sm);
} }
.agent-list { /* Baum-Ansicht */
.agent-tree {
flex: 1; flex: 1;
overflow-y: auto; overflow-y: auto;
padding: var(--spacing-sm); padding: var(--spacing-sm);
} }
.agent-node {
position: relative;
margin-left: calc(var(--depth) * 1.5rem);
}
/* Verbindungslinie für Subagents */
.tree-line {
position: absolute;
left: -1rem;
top: 0;
bottom: 50%;
width: 1rem;
border-left: 2px solid var(--bg-tertiary);
border-bottom: 2px solid var(--bg-tertiary);
border-bottom-left-radius: 4px;
}
.agent-item { .agent-item {
display: flex;
align-items: flex-start;
gap: var(--spacing-xs);
width: 100%; width: 100%;
text-align: left; text-align: left;
padding: var(--spacing-sm) var(--spacing-md); padding: var(--spacing-sm) var(--spacing-md);
@ -180,15 +302,57 @@
border-left: 3px solid var(--success); border-left: 3px solid var(--success);
} }
.agent-item.is-subagent {
background: var(--bg-primary);
border-style: dashed;
}
.collapse-toggle {
padding: 2px 4px;
font-size: 0.625rem;
background: none;
border: none;
cursor: pointer;
color: var(--text-secondary);
flex-shrink: 0;
}
.collapse-toggle:hover {
color: var(--text-primary);
}
.collapse-spacer {
width: 18px;
flex-shrink: 0;
}
.agent-content {
flex: 1;
min-width: 0;
}
.agent-main { .agent-main {
display: flex; display: flex;
align-items: center; align-items: center;
gap: var(--spacing-sm); gap: var(--spacing-xs);
margin-bottom: var(--spacing-xs); flex-wrap: wrap;
}
.agent-type-icon {
font-size: 0.875rem;
} }
.agent-type { .agent-type {
font-weight: 600; font-weight: 600;
font-size: 0.8rem;
}
.agent-model {
font-size: 0.625rem;
padding: 1px 4px;
background: var(--accent);
color: white;
border-radius: var(--radius-sm);
} }
.agent-duration { .agent-duration {
@ -202,14 +366,27 @@
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
margin-top: 2px;
} }
.agent-tools { .agent-meta {
display: flex;
gap: var(--spacing-sm);
font-size: 0.625rem; font-size: 0.625rem;
color: var(--text-secondary); color: var(--text-secondary);
margin-top: var(--spacing-xs); margin-top: var(--spacing-xs);
} }
.agent-depth {
padding: 1px 4px;
background: var(--bg-tertiary);
border-radius: var(--radius-sm);
}
.agent-children {
margin-top: var(--spacing-xs);
}
/* Detail-Ansicht */ /* Detail-Ansicht */
.agent-details { .agent-details {
padding: var(--spacing-md); padding: var(--spacing-md);
@ -242,6 +419,15 @@
min-width: 80px; min-width: 80px;
} }
.parent-link {
cursor: pointer;
color: var(--accent);
}
.parent-link:hover {
text-decoration: underline;
}
.tool-list { .tool-list {
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View file

@ -5,11 +5,15 @@ import { writable, derived } from 'svelte/store';
// Typen // Typen
export interface Agent { export interface Agent {
id: string; id: string;
type: 'main' | 'explore' | 'plan' | 'bash'; type: 'main' | 'explore' | 'plan' | 'bash' | 'code' | 'test' | 'review';
status: 'active' | 'waiting' | 'idle' | 'stopped'; status: 'active' | 'waiting' | 'idle' | 'stopped';
task: string; task: string;
startedAt: Date; startedAt: Date;
toolCalls: ToolCall[]; toolCalls: ToolCall[];
// Subagent-Hierarchie
parentAgentId?: string; // undefined = Main Agent
depth: number; // 0 = Main, 1 = direkter Subagent, etc.
model?: string; // Welches Modell nutzt dieser Agent
} }
export interface ToolCall { export interface ToolCall {
@ -74,9 +78,34 @@ export const agentCount = derived(agents, ($agents) => ({
total: $agents.length, total: $agents.length,
active: $agents.filter((a) => a.status === 'active').length, active: $agents.filter((a) => a.status === 'active').length,
waiting: $agents.filter((a) => a.status === 'waiting').length, waiting: $agents.filter((a) => a.status === 'waiting').length,
idle: $agents.filter((a) => a.status === 'idle').length idle: $agents.filter((a) => a.status === 'idle').length,
mainAgents: $agents.filter((a) => !a.parentAgentId).length,
subAgents: $agents.filter((a) => a.parentAgentId).length,
})); }));
// Agent-Baum Typen und Builder (muss vor agentTree Store sein)
export interface AgentTreeNode {
agent: Agent;
children: AgentTreeNode[];
}
export function buildAgentTree(agentsList: Agent[]): AgentTreeNode[] {
// Nur Root-Agents (ohne Parent)
const roots = agentsList.filter((a) => !a.parentAgentId);
function buildNode(agent: Agent): AgentTreeNode {
const children = agentsList
.filter((a) => a.parentAgentId === agent.id)
.map(buildNode);
return { agent, children };
}
return roots.map(buildNode);
}
// Agent-Baum als reaktiver Store
export const agentTree = derived(agents, ($agents) => buildAgentTree($agents));
// Aktionen // Aktionen
export function addMessage(role: Message['role'], content: string, agentId?: string) { export function addMessage(role: Message['role'], content: string, agentId?: string) {
messages.update((msgs) => [ messages.update((msgs) => [
@ -91,8 +120,27 @@ export function addMessage(role: Message['role'], content: string, agentId?: str
]); ]);
} }
export function addAgent(type: Agent['type'], task: string): string { export interface AddAgentOptions {
const id = crypto.randomUUID(); id?: string;
parentAgentId?: string;
model?: string;
}
export function addAgent(type: Agent['type'], task: string, options?: AddAgentOptions): string {
const id = options?.id || crypto.randomUUID();
const parentAgentId = options?.parentAgentId;
// Tiefe berechnen: Parent-Tiefe + 1 (oder 0 wenn kein Parent)
let depth = 0;
if (parentAgentId) {
agents.subscribe((ags) => {
const parent = ags.find((a) => a.id === parentAgentId);
if (parent) {
depth = parent.depth + 1;
}
})();
}
agents.update((ags) => [ agents.update((ags) => [
...ags, ...ags,
{ {
@ -101,12 +149,30 @@ export function addAgent(type: Agent['type'], task: string): string {
status: 'active', status: 'active',
task, task,
startedAt: new Date(), startedAt: new Date(),
toolCalls: [] toolCalls: [],
parentAgentId,
depth,
model: options?.model,
} }
]); ]);
return id; return id;
} }
// Subagent hinzufügen (Kurzform)
export function addSubAgent(
parentId: string,
type: Agent['type'],
task: string,
options?: Omit<AddAgentOptions, 'parentAgentId'>
): string {
return addAgent(type, task, { ...options, parentAgentId: parentId });
}
// Alle Kinder eines Agents finden
export function getChildAgents(parentId: string, agentsList: Agent[]): Agent[] {
return agentsList.filter((a) => a.parentAgentId === parentId);
}
export function updateAgentStatus(id: string, status: Agent['status']) { export function updateAgentStatus(id: string, status: Agent['status']) {
agents.update((ags) => agents.update((ags) =>
ags.map((a) => (a.id === id ? { ...a, status } : a)) ags.map((a) => (a.id === id ? { ...a, status } : a))

View file

@ -11,6 +11,7 @@ import {
isProcessing, isProcessing,
addMessage, addMessage,
addAgent, addAgent,
addSubAgent,
updateAgentStatus, updateAgentStatus,
addToolCall, addToolCall,
completeToolCall, completeToolCall,
@ -19,7 +20,8 @@ import {
sessionStats, sessionStats,
currentSessionId, currentSessionId,
messageToDb, messageToDb,
type Message type Message,
type Agent
} from './app'; } from './app';
// Event-Typen vom Backend // Event-Typen vom Backend
@ -28,6 +30,18 @@ interface AgentEvent {
type?: string; type?: string;
task?: string; task?: string;
code?: number; code?: number;
model?: string;
}
interface SubagentEvent {
id: string;
parentAgentId: string;
type?: string;
task?: string;
depth?: number;
model?: string;
toolUseId?: string;
success?: boolean;
} }
interface ToolEvent { interface ToolEvent {
@ -137,6 +151,30 @@ export async function initEventListeners(): Promise<void> {
}) })
); );
// Subagent gestartet
listeners.push(
await listen<SubagentEvent>('subagent-started', (event) => {
const { id, parentAgentId, type, task, depth, model } = event.payload;
console.log('🤖 Subagent gestartet:', id, type, '(Parent:', parentAgentId, ')');
addSubAgent(
parentAgentId,
mapAgentType(type || 'explore'),
task || 'Subagent-Aufgabe',
{ id, model }
);
})
);
// Subagent gestoppt
listeners.push(
await listen<SubagentEvent>('subagent-stopped', (event) => {
const { id, success } = event.payload;
console.log('⏹️ Subagent gestoppt:', id, success ? 'OK' : 'FEHLER');
updateAgentStatus(id, 'stopped');
})
);
// Tool Start // Tool Start
listeners.push( listeners.push(
await listen<ToolEvent>('tool-start', (event) => { await listen<ToolEvent>('tool-start', (event) => {
@ -241,17 +279,25 @@ export async function cleanupEventListeners(): Promise<void> {
} }
// Agent-Typ mappen // Agent-Typ mappen
function mapAgentType(type: string): 'main' | 'explore' | 'plan' | 'bash' { function mapAgentType(type: string): Agent['type'] {
const typeMap: Record<string, 'main' | 'explore' | 'plan' | 'bash'> = { const typeMap: Record<string, Agent['type']> = {
main: 'main', main: 'main',
'Main Agent': 'main', 'Main Agent': 'main',
Main: 'main', Main: 'main',
explore: 'explore', explore: 'explore',
Explore: 'explore', Explore: 'explore',
'general-purpose': 'explore',
plan: 'plan', plan: 'plan',
Plan: 'plan', Plan: 'plan',
bash: 'bash', bash: 'bash',
Bash: 'bash' Bash: 'bash',
code: 'code',
Code: 'code',
implement: 'code',
test: 'test',
Test: 'test',
review: 'review',
Review: 'review',
}; };
return typeMap[type] || 'main'; return typeMap[type] || 'explore';
} }