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:
parent
18c8ef2f4f
commit
6cfcdb2c79
5 changed files with 1096 additions and 51 deletions
663
ROADMAP.md
663
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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<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';
|
||||
|
||||
// Status-Icons
|
||||
|
|
@ -10,14 +10,40 @@
|
|||
stopped: '🔴'
|
||||
};
|
||||
|
||||
// Typ-Namen
|
||||
// Typ-Namen (erweitert für alle Agent-Typen)
|
||||
const typeNames: Record<Agent['type'], string> = {
|
||||
main: 'Main Agent',
|
||||
explore: 'Explore',
|
||||
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) {
|
||||
$selectedAgentId = $selectedAgentId === id ? null : id;
|
||||
}
|
||||
|
|
@ -31,13 +57,84 @@
|
|||
}
|
||||
return `${seconds}s`;
|
||||
}
|
||||
|
||||
// Rekursives Rendern von Baum-Knoten
|
||||
function hasChildren(node: AgentTreeNode): boolean {
|
||||
return node.children.length > 0;
|
||||
}
|
||||
</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-header">
|
||||
<h2>🤖 Agents & Sub-Agents</h2>
|
||||
<h2>🤖 Agents</h2>
|
||||
<div class="agent-summary">
|
||||
{$agentCount.total} gesamt | {$agentCount.active} aktiv
|
||||
{$agentCount.total} gesamt |
|
||||
{$agentCount.mainAgents} Main |
|
||||
{$agentCount.subAgents} Sub |
|
||||
{$agentCount.active} aktiv
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -47,24 +144,9 @@
|
|||
<p class="hint">Agents erscheinen hier wenn Claude arbeitet.</p>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="agent-list">
|
||||
{#each $agents as agent}
|
||||
<button
|
||||
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>
|
||||
<div class="agent-tree">
|
||||
{#each $agentTree as rootNode}
|
||||
{@render agentNode(rootNode, 0)}
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
|
|
@ -73,7 +155,8 @@
|
|||
{@const selectedAgent = $agents.find((a) => a.id === $selectedAgentId)}
|
||||
{#if selectedAgent}
|
||||
<div class="agent-details">
|
||||
<h3>Details: {typeNames[selectedAgent.type]}</h3>
|
||||
<h3>{typeIcons[selectedAgent.type]} {typeNames[selectedAgent.type]}</h3>
|
||||
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">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-value">{selectedAgent.task}</span>
|
||||
</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">
|
||||
<span class="detail-label">Gestartet:</span>
|
||||
<span class="detail-value">{selectedAgent.startedAt.toLocaleTimeString('de-DE')}</span>
|
||||
|
|
@ -90,6 +179,18 @@
|
|||
<span class="detail-label">Laufzeit:</span>
|
||||
<span class="detail-value">{formatDuration(selectedAgent.startedAt)}</span>
|
||||
</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>
|
||||
<div class="tool-list">
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<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']) {
|
||||
agents.update((ags) =>
|
||||
ags.map((a) => (a.id === id ? { ...a, status } : a))
|
||||
|
|
|
|||
|
|
@ -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<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
|
||||
listeners.push(
|
||||
await listen<ToolEvent>('tool-start', (event) => {
|
||||
|
|
@ -241,17 +279,25 @@ export async function cleanupEventListeners(): Promise<void> {
|
|||
}
|
||||
|
||||
// Agent-Typ mappen
|
||||
function mapAgentType(type: string): 'main' | 'explore' | 'plan' | 'bash' {
|
||||
const typeMap: Record<string, 'main' | 'explore' | 'plan' | 'bash'> = {
|
||||
function mapAgentType(type: string): Agent['type'] {
|
||||
const typeMap: Record<string, Agent['type']> = {
|
||||
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';
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue