diff --git a/ROADMAP.md b/ROADMAP.md index 39dc447..fd7f923 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -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 - [ ] Plugin-System - [ ] Multi-User / Team-Features diff --git a/scripts/claude-bridge.js b/scripts/claude-bridge.js index 6b12d4c..9c1b8e4 100644 --- a/scripts/claude-bridge.js +++ b/scripts/claude-bridge.js @@ -19,6 +19,10 @@ let activeAbort = null; let currentAgentId = null; let currentModel = process.env.CLAUDE_MODEL || 'opus'; +// Subagent-Tracking +// Map: toolUseId → { agentId, parentId, type, task, depth } +const activeSubagents = new Map(); + // Verfügbare Modelle const AVAILABLE_MODELS = [ { id: 'haiku', name: 'Claude Haiku', description: 'Schnell & günstig' }, @@ -26,6 +30,26 @@ const AVAILABLE_MODELS = [ { 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 ============ function sendToTauri(msg) { @@ -93,20 +117,73 @@ async function sendMessage(message, requestId, model = null) { } break; - case 'tool_use': - sendEvent('tool-start', { - id: event.tool_use_id || randomUUID(), - tool: event.name || 'unknown', - input: event.input || {}, - }); - break; + case 'tool_use': { + const toolId = event.tool_use_id || randomUUID(); + const toolName = event.name || 'unknown'; + const toolInput = event.input || {}; - case 'tool_result': - sendEvent('tool-end', { - id: event.tool_use_id || '', - success: !event.is_error, + // Prüfen ob dieses Tool einen Subagent startet + if (SUBAGENT_TOOLS.includes(toolName)) { + const subagentId = randomUUID(); + 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; + } + + 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': // Endergebnis @@ -135,6 +212,17 @@ async function sendMessage(message, requestId, model = null) { sendEvent('text', { text: `\n\n**Fehler:** ${err.message || err}` }); } } 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('all-stopped'); currentAgentId = null; diff --git a/src/lib/components/AgentView.svelte b/src/lib/components/AgentView.svelte index 081a17b..28c5c91 100644 --- a/src/lib/components/AgentView.svelte +++ b/src/lib/components/AgentView.svelte @@ -1,5 +1,5 @@ + +{#snippet agentNode(node: AgentTreeNode, depth: number)} + {@const agent = node.agent} + {@const isCollapsed = collapsedNodes.has(agent.id)} + {@const hasKids = hasChildren(node)} + +
+ + {#if depth > 0} +
+ {/if} + + + {:else} + + {/if} + +
+
+ {statusIcons[agent.status]} + {typeIcons[agent.type]} + {typeNames[agent.type]} + {#if agent.model} + {agent.model} + {/if} + ({formatDuration(agent.startedAt)}) +
+
{agent.task}
+
+ 🔧 {agent.toolCalls.length} + {#if depth > 0} + Ebene {depth} + {/if} +
+
+ + + + {#if hasKids && !isCollapsed} +
+ {#each node.children as child} + {@render agentNode(child, depth + 1)} + {/each} +
+ {/if} +
+{/snippet} +
-

🤖 Agents & Sub-Agents

+

🤖 Agents

- {$agentCount.total} gesamt | {$agentCount.active} aktiv + {$agentCount.total} gesamt | + {$agentCount.mainAgents} Main | + {$agentCount.subAgents} Sub | + {$agentCount.active} aktiv
@@ -47,24 +144,9 @@

Agents erscheinen hier wenn Claude arbeitet.

{:else} -
- {#each $agents as agent} - +
+ {#each $agentTree as rootNode} + {@render agentNode(rootNode, 0)} {/each}
@@ -73,7 +155,8 @@ {@const selectedAgent = $agents.find((a) => a.id === $selectedAgentId)} {#if selectedAgent}
-

Details: {typeNames[selectedAgent.type]}

+

{typeIcons[selectedAgent.type]} {typeNames[selectedAgent.type]}

+
Status: {statusIcons[selectedAgent.status]} {selectedAgent.status} @@ -82,6 +165,12 @@ Aufgabe: {selectedAgent.task}
+ {#if selectedAgent.model} +
+ Modell: + {selectedAgent.model} +
+ {/if}
Gestartet: {selectedAgent.startedAt.toLocaleTimeString('de-DE')} @@ -90,6 +179,18 @@ Laufzeit: {formatDuration(selectedAgent.startedAt)}
+ {#if selectedAgent.parentAgentId} +
+ Parent: + selectAgent(selectedAgent.parentAgentId!)}> + {selectedAgent.parentAgentId.substring(0, 8)}... + +
+ {/if} +
+ Tiefe: + {selectedAgent.depth} +

Tool-Aufrufe ({selectedAgent.toolCalls.length})

@@ -150,13 +251,34 @@ margin-top: var(--spacing-sm); } - .agent-list { + /* Baum-Ansicht */ + .agent-tree { flex: 1; overflow-y: auto; 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 { + display: flex; + align-items: flex-start; + gap: var(--spacing-xs); width: 100%; text-align: left; padding: var(--spacing-sm) var(--spacing-md); @@ -180,15 +302,57 @@ 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 { display: flex; align-items: center; - gap: var(--spacing-sm); - margin-bottom: var(--spacing-xs); + gap: var(--spacing-xs); + flex-wrap: wrap; + } + + .agent-type-icon { + font-size: 0.875rem; } .agent-type { 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 { @@ -202,14 +366,27 @@ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; + margin-top: 2px; } - .agent-tools { + .agent-meta { + display: flex; + gap: var(--spacing-sm); font-size: 0.625rem; color: var(--text-secondary); 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 */ .agent-details { padding: var(--spacing-md); @@ -242,6 +419,15 @@ min-width: 80px; } + .parent-link { + cursor: pointer; + color: var(--accent); + } + + .parent-link:hover { + text-decoration: underline; + } + .tool-list { display: flex; flex-direction: column; diff --git a/src/lib/stores/app.ts b/src/lib/stores/app.ts index dd2b2ef..23a5778 100644 --- a/src/lib/stores/app.ts +++ b/src/lib/stores/app.ts @@ -5,11 +5,15 @@ import { writable, derived } from 'svelte/store'; // Typen export interface Agent { id: string; - type: 'main' | 'explore' | 'plan' | 'bash'; + type: 'main' | 'explore' | 'plan' | 'bash' | 'code' | 'test' | 'review'; status: 'active' | 'waiting' | 'idle' | 'stopped'; task: string; startedAt: Date; 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 { @@ -74,9 +78,34 @@ export const agentCount = derived(agents, ($agents) => ({ total: $agents.length, active: $agents.filter((a) => a.status === 'active').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 export function addMessage(role: Message['role'], content: string, agentId?: string) { 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 { - const id = crypto.randomUUID(); +export interface AddAgentOptions { + 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) => [ ...ags, { @@ -101,12 +149,30 @@ export function addAgent(type: Agent['type'], task: string): string { status: 'active', task, startedAt: new Date(), - toolCalls: [] + toolCalls: [], + parentAgentId, + depth, + model: options?.model, } ]); return id; } +// Subagent hinzufügen (Kurzform) +export function addSubAgent( + parentId: string, + type: Agent['type'], + task: string, + options?: Omit +): 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']) { agents.update((ags) => ags.map((a) => (a.id === id ? { ...a, status } : a)) diff --git a/src/lib/stores/events.ts b/src/lib/stores/events.ts index 7bd42d8..b2e3d56 100644 --- a/src/lib/stores/events.ts +++ b/src/lib/stores/events.ts @@ -11,6 +11,7 @@ import { isProcessing, addMessage, addAgent, + addSubAgent, updateAgentStatus, addToolCall, completeToolCall, @@ -19,7 +20,8 @@ import { sessionStats, currentSessionId, messageToDb, - type Message + type Message, + type Agent } from './app'; // Event-Typen vom Backend @@ -28,6 +30,18 @@ interface AgentEvent { type?: string; task?: string; code?: number; + model?: string; +} + +interface SubagentEvent { + id: string; + parentAgentId: string; + type?: string; + task?: string; + depth?: number; + model?: string; + toolUseId?: string; + success?: boolean; } interface ToolEvent { @@ -137,6 +151,30 @@ export async function initEventListeners(): Promise { }) ); + // Subagent gestartet + listeners.push( + await listen('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('subagent-stopped', (event) => { + const { id, success } = event.payload; + console.log('⏹️ Subagent gestoppt:', id, success ? 'OK' : 'FEHLER'); + updateAgentStatus(id, 'stopped'); + }) + ); + // Tool Start listeners.push( await listen('tool-start', (event) => { @@ -241,17 +279,25 @@ export async function cleanupEventListeners(): Promise { } // Agent-Typ mappen -function mapAgentType(type: string): 'main' | 'explore' | 'plan' | 'bash' { - const typeMap: Record = { +function mapAgentType(type: string): Agent['type'] { + const typeMap: Record = { main: 'main', 'Main Agent': 'main', Main: 'main', explore: 'explore', Explore: 'explore', + 'general-purpose': 'explore', plan: 'plan', Plan: 'plan', 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'; }