Compare commits

..

No commits in common. "48c7b2a30ce2afd26b340b3748d9f5386810f019" and "9cc97311b02fcf5425258457cbb9c2e14c1aa2a0" have entirely different histories.

7 changed files with 554 additions and 381 deletions

192
CLAUDE.md
View file

@ -1,131 +1,107 @@
# Claude Desktop — Projekt-Kontext # Claude Desktop
Native Tauri-2.0-Desktop-App die Claude Code/Agent SDK als Backend nutzt. Native Tauri-2.0-Desktop-App die Claude Code/Agent SDK als Backend nutzt. Sprach-Interface, Live-Aktivität, Guard-Rails. Eigenes Pendant zur VSCodium-Sidebar mit voller Kontrolle über das System.
Schneller und maechtiger als Codium + Claude Code Extension.
**Stand: 22.04.2026 — Alle Roadmap-Phasen (3-6) abgeschlossen.** Detail-Übersicht + Status: [README.md](README.md). Phasen-Stand: [ROADMAP.md](ROADMAP.md).
Detail-Uebersicht: [README.md](README.md) | Phasen-Status: [ROADMAP.md](ROADMAP.md) | Aenderungen: [CHANGELOG.md](CHANGELOG.md)
## Tech-Stack ## Tech-Stack
- **Backend**: Rust 2021, Tauri 2 (`tray-icon`), tokio, mysql_async, rusqlite, reqwest, libc - **Backend**: Rust 2021, Tauri 2 (`tauri = "2"` mit `tray-icon`-Feature), tokio, mysql_async, rusqlite, reqwest
- **Frontend**: SvelteKit 2, Svelte 5 (Runes: $state/$derived/$effect), TypeScript, Vite 5, paneforge - **Frontend**: SvelteKit 2, Svelte 5, TypeScript, Vite 5, paneforge (Multi-Pane-Layout)
- **AI**: `@anthropic-ai/claude-agent-sdk` (query-Funktion), Ollama (optional, lokal) - **AI**: `@anthropic-ai/claude-agent-sdk` + `@anthropic-ai/claude-code`
- **Sprache**: whisper-cli (STT, lokal), piper-tts (TTS, lokal, 5 deutsche Stimmen) - **Sprache**: Whisper (STT lokal), TTS Cloud-Streaming
- **DB**: MySQL (claude-db, 192.168.155.11) fuer Wissensbasis, SQLite lokal fuer Sessions/Settings/Queue - **DB**: claude-DB (MySQL 192.168.155.11 `claude`) für Wissensbasis, SQLite lokal für Sessions/Persistenz
- **IPC**: Unix Domain Socket (bevorzugt) oder stdio JSON-Lines - **Build**: NixOS-Dev-Shell (`shell.nix`), Forgejo CI für AppImage
- **Build**: NixOS shell.nix, Forgejo Actions → AppImage
## Wichtige Pfade ## Wichtige Pfade
| Was | Pfad | - Rust-Module: [src-tauri/src/](src-tauri/src/) — 16 Module (`main.rs`, `lib.rs`, `claude.rs`, `db.rs`, `guard.rs`, `voice.rs`, `hooks.rs`, `ide.rs`, …)
|-----|------| - UI-Komponenten: [src/lib/components/](src/lib/components/) — 24 Panels
| Rust-Module (18) | `src-tauri/src/` | - Routes: [src/routes/](src/routes/) (`+layout.svelte`, `+page.svelte`, `presentation/+page.svelte`)
| UI-Komponenten (27) | `src/lib/components/` | - VS-Code-Extension: [vscode-extension/](vscode-extension/) (Bridge auf WebSocket-Port 7890)
| Stores | `src/lib/stores/` (app.ts, events.ts) | - Workflow: [.forgejo/workflows/build-appimage.yml](.forgejo/workflows/build-appimage.yml)
| Bridge | `scripts/claude-bridge.js` (~1100 Zeilen) | - Dev-Shell: [shell.nix](shell.nix)
| Hauptlayout | `src/routes/+page.svelte` | - Tauri-Config: [src-tauri/tauri.conf.json](src-tauri/tauri.conf.json), [src-tauri/Cargo.toml](src-tauri/Cargo.toml)
| Chat-Fenster | `src/routes/chat-window/+page.svelte` |
| Praesentation | `src/routes/presentation/+page.svelte` |
| CI/CD | `.forgejo/workflows/build-appimage.yml` |
| Dev-Shell | `shell.nix` |
## Architektur-Kurzreferenz ## Build & Run
### Kommunikationskette
```
User → ChatPanel.svelte → invoke('send_message') → claude.rs
→ Bridge (UDS/stdio) → query() → Claude API → Events zurueck
→ claude.rs handle_bridge_message() → emit() → events.ts → UI
```
### Bridge-Daemon (Phase 3)
- **Socket**: `/tmp/claude-bridge.sock`
- **PID-File**: `/tmp/claude-bridge.pid`
- Start: `node claude-bridge.js --socket /tmp/claude-bridge.sock`
- Rust verbindet sich per `UnixStream::connect()`
- Auto-Reconnect bei Verbindungsverlust (3 Versuche)
- Fallback auf stdio wenn UDS fehlschlaegt
### ClaudeState (claude.rs)
```rust
pub struct ClaudeState {
pub connection: Option<BridgeConnection>, // Stdio oder Uds
pub request_counter: u64,
pub agents: Vec<AgentStatus>,
}
```
### Agent-Modi (claude-bridge.js)
| Modus | Beschreibung | Prompt |
|-------|-------------|--------|
| `solo` | Claude arbeitet direkt | Kein Orchestrator |
| `handlanger` | Haupt-Agent delegiert an Haiku-Worker | ORCHESTRATOR_PROMPTS.handlanger |
| `experten` | 4 Experten: Research/Implement/Test/Review | ORCHESTRATOR_PROMPTS.experten |
| `auto` | Heuristik waehlt Modus nach Nachrichtenlaenge/-inhalt | chooseAutoMode() |
### MCP-Hub (Phase 4)
- 6 Server aus `~/.claude.json` → beim Bridge-Start per `set-mcp-servers` injiziert
- Tauri-Commands: `list_mcp_servers`, `add_mcp_server`, `remove_mcp_server`
- Bridge: `queryOptions.mcpServers = mcpServerConfigs`
### Offline-Queue (Phase 5)
- SQLite-Tabelle `offline_queue`
- Commands: `queue_message`, `list_queued_messages`, `flush_offline_queue`, `clear_offline_queue`, `queue_count`
## Build & Deploy
### Native Dev (Hot-Reload)
```bash ```bash
# Dev (Hot-Reload) nix-shell shell.nix --run 'npm ci && npm run tauri:dev'
CARGO_TARGET_DIR=/tmp/claude-desktop-target nix-shell --run "npx tauri dev"
# Produktion
CARGO_TARGET_DIR=/tmp/claude-desktop-target nix-shell --run "npx tauri build -- --bundles appimage"
# CI: Commit mit [appimage] → Forgejo baut automatisch
# WICHTIG: CARGO_TARGET_DIR nie auf SMB-Mount!
``` ```
### CI-Eigenheiten ### Native Production-Build (NixOS — **Pflicht-Weg auf NixOS!**)
- Runner: `16-Forgejo-Runner-AppImage` (Debian Bookworm) ```bash
- AppImage-Filename mit Leerzeichen → `tr ' ' '-'` vor Upload # Cargo-target NICHT auf SMB-Mount! → I/O-Errors. Lokales tmpfs nutzen:
- DELETE vor PUT (Forgejo 409 Conflict) CARGO_TARGET_DIR=/tmp/claude-target \
- Custom-AppRun muss `apprun-hooks/linuxdeploy-plugin-gtk.sh` sourcen (KB #384) nix-shell shell.nix --run 'npm run tauri build -- --bundles appimage'
- Ntfy-Notifications auf Topic `vk-builds` # Bundling kann mit pkg-config-Bug crashen — Binary unter
# /tmp/claude-target/release/claude-desktop ist trotzdem fertig & startbar:
nix-shell shell.nix --run /tmp/claude-target/release/claude-desktop
```
## NixOS-Spezialfall ### CI-Build via Forgejo Actions
Commit mit `[appimage]` in der Message → Build auf `16-Forgejo-Runner-AppImage` (Debian) → Upload in Package-Registry. Triggert auch bei Tag-Push `v*`.
## NixOS-Spezialfall — AppImage funktioniert NICHT
Das vom CI gebaute AppImage hat auf NixOS einen WebKit2GTK ↔ Mesa ABI-Konflikt (`EGL_BAD_PARAMETER` im WebKitWebProcess). **Keine** Kombination aus `WEBKIT_DISABLE_DMABUF_RENDERER` / `_COMPOSITING_MODE` / `_SANDBOX_THIS_IS_DANGEROUS` / `GDK_BACKEND=x11` / `LIBGL_ALWAYS_SOFTWARE` löst es. Workarounds erschöpft → **immer nativ bauen via `shell.nix`**. Vollständige Diagnose: KB-Eintrag #381.
AppImage hat WebKit2GTK/Mesa ABI-Konflikt auf NixOS → **immer nativ bauen** via `shell.nix`.
Auf Debian/Ubuntu/Fedora/Arch funktioniert das AppImage problemlos. Auf Debian/Ubuntu/Fedora/Arch funktioniert das AppImage problemlos.
## Konventionen ## Konventionen
- **Sprache**: Deutsch in Code-Kommentaren, README, Commits, UI - **Sprache**: Deutsch in Code-Kommentaren, README, CHANGELOG, Commit-Messages, UI-Strings
- **Svelte 5**: Runes (`$state`, `$derived`, `$effect`), `onclick` statt `on:click` - **Modell-Defaults**: Sonnet 4.6 für normale Tasks, Opus für Komplex, Haiku für Routineanfragen — UI-Auswahl im Footer
- **Guard-Rails**: Alle System-Aktionen ueber `guard.rs` klassifizieren - **Guard-Rails**: alle System-Aktionen über `guard.rs` klassifizieren (Safe / Moderate / Critical / Blocked) — siehe `src-tauri/src/guard.rs`
- **Audit**: Jede Tool-Ausfuehrung im Audit-Log - **Audit**: jede Tool-Ausführung landet im Audit-Log (`audit.rs` → SQLite + UI-Panel)
- **Sessions**: Persistent in SQLite, nach Projekt gefiltert - **Sessions**: persistent in SQLite, restartbar
- **PaneForge**: `{#key}` verwenden wenn Panes dynamisch ein-/ausgeblendet werden - **Bridge zur VSCodium-Extension**: Port 7890, einfaches WebSocket-Protokoll, in `ide.rs` definiert
## Haeufige Probleme & Loesungen ## Workflow-Eigenheiten (CI/CD)
| Problem | Loesung | Beim Anpassen von [.forgejo/workflows/build-appimage.yml](.forgejo/workflows/build-appimage.yml) **nicht vergessen**:
|---------|---------| - AppImage-Filename hat Leerzeichen → vor Upload `tr ' ' '-'` (curl-URL-Bug)
| Rusqlite Borrow-Lifetime | `let result = stmt.query_map(...)?.collect(); result` | - Vor jedem Upload **DELETE auf `latest/` UND `VERSION/`** (Forgejo wirft 409 Conflict bei PUT auf existing)
| UTF-8 Panic bei Truncation | `char_indices().take_while()` statt `&s[..n]` | - Custom-AppRun **muss `apprun-hooks/linuxdeploy-plugin-gtk.sh` sourcen + `AppRun.wrapped` aufrufen**, sonst finden WebKit-Subprozesse ihre Helpers nicht (KB #384)
| PaneForge Resize nach Detach | `{#key $chatDetached}` um PaneGroup | - Re-Bundle nach AppRun-Patch mit `appimagetool --no-appstream`
| Piper-TTS nicht gefunden | `nix-env -f '<nixpkgs>' -iA piper-tts`, Modelle in `~/.local/share/claude-desktop/models/` | - Ntfy-Notifications inline (nicht via `data/ntfy-action`, weil das Repo cross-org ist — für `data-it/*`-Repos siehe KB #220 für vollständige URL-Variante)
| Bridge-Nachricht nicht parsbar | `match serde_json::from_str()` statt `if let Ok()` |
| Unhandled Promise Rejection | Global Handler in Bridge (uncaughtException + unhandledRejection) |
## Wissensbasis-Referenzen ## Wissensbasis (Claude-DB)
| KB-ID | Thema | Bei spezifischen Bug-Themen vorab `mysql_search_knowledge` mit Schlüsselwort:
|-------|-------|
| #248 | Tauri + SvelteKit shell.nix | | Thema | KB-ID |
| #311 | CI/CD Pipeline | |---|---|
| #371 | Debian Forgejo-Runner | | Pipeline-Übersicht | #311 |
| #381 | NixOS WebKit-EGL-Crash | | Debian-Runner Setup | #371 |
| #382 | Cargo auf SMB | | libssl-dev / openssl-sys | #372 |
| #384 | Custom-AppRun | | NixOS WebKit-EGL-Crash | #381 |
| Cargo auf SMB → CARGO_TARGET_DIR | #382 |
| Custom-AppRun + linuxdeploy-Hook | #384 |
| Bluetooth 5.2 ≠ LE Audio | #385 |
| Tauri shell.nix-Vorlage | #248 |
| Forgejo Package-Registry 409 | #161 |
| Ntfy-Pattern (cross-org URL) | #190/#191/#220 |
## Häufige Befehle
```bash
# Build-Status checken
curl -sS -u "token:<forgejo-token>" \
'https://git.data-it-solution.de/api/v1/repos/data/claude-desktop/actions/tasks?limit=3' | jq
# AppImage frisch ziehen (Standard-Linux)
rm -f ~/Applications/Claude-Desktop.AppImage
curl -sSL -o ~/Applications/Claude-Desktop.AppImage \
-u "token:<forgejo-token>" \
'https://git.data-it-solution.de/api/packages/data/generic/claude-desktop/latest/Claude-Desktop_0.1.0_amd64.AppImage'
chmod +x ~/Applications/Claude-Desktop.AppImage
# Native NixOS-Start (nach einmaligem Build)
nix-shell shell.nix --run /tmp/claude-target/release/claude-desktop
# Build-Logs aus Forgejo-Container ziehen (bei Failure)
ssh unraid "docker cp 18-Forgejo:\$(docker exec 18-Forgejo find /data/gitea/actions_log/data/claude-desktop -name '<RUN_ID>.log.zst' | head -1) /tmp/build.log.zst"
ssh unraid "zstd -d -c /tmp/build.log.zst" | tail -100
```

643
README.md
View file

@ -1,242 +1,491 @@
# Claude Desktop # Claude Desktop — Nativer AI-Assistent
Nativer AI-Desktop-Assistent fuer Linux. Schneller und maechtiger als Codium + Claude Code Extension. Eigenständige Desktop-Anwendung die Claude Code als Backend nutzt, mit nativem UI, Live-Übersicht und kontrolliertem OS-Zugriff.
**Tech-Stack:** Tauri 2.0 (Rust) + SvelteKit 5 + Claude Agent SDK (Node.js) ## Status (Stand 2026-04-20)
**Codebase:** ~24.000 Zeilen, 154 Commits, 27 Svelte-Komponenten, 18 Rust-Module
--- **Variante A (Native Desktop-App) ist umgesetzt** — Tauri 2.0 + SvelteKit 5, Phase 1-13 fertig (siehe [ROADMAP.md](ROADMAP.md)). Variante B (autonome VM) bleibt Vision.
## Schnellstart Was läuft:
- 4-Panel-Layout, 24 UI-Komponenten (Chat, Activity, Memory, Audit, Knowledge, Voice, Hooks, IDE, Programs, Performance, Settings, …)
- 16 Rust-Backend-Module (`claude.rs`, `db.rs`, `guard.rs`, `memory.rs`, `voice.rs`, `hooks.rs`, `ide.rs`, …)
- Sprach-Interface mit Push-to-Talk (`VoicePanel`), Whisper STT
- Modell-Auswahl Haiku/Sonnet/Opus, Token-/Kosten-Anzeige
- Subagent-Hierarchie, Multi-Agent-Modi, Hook-System
- Session-Persistenz (SQLite), Audit-Log
- VS-Code-Extension `claude-desktop-bridge` (steuert VSCodium aus der App heraus, WebSocket-Port 7890)
- CI/CD-Pipeline (Forgejo Actions) → AppImage in Package-Registry
## Installation
### AppImage (Debian/Ubuntu/Fedora/Arch — **nicht NixOS**)
```bash ```bash
# Development (Hot-Reload) mkdir -p ~/Applications
cd "/mnt/17 - Entwicklungen/20 - Projekte/ClaudeDesktop" curl -sSL -o ~/Applications/Claude-Desktop.AppImage \
CARGO_TARGET_DIR=/tmp/claude-desktop-target nix-shell --run "npx tauri dev" -u "token:<dein-forgejo-token>" \
'https://git.data-it-solution.de/api/packages/data/generic/claude-desktop/latest/Claude-Desktop_0.1.0_amd64.AppImage'
# Produktion (AppImage) chmod +x ~/Applications/Claude-Desktop.AppImage
CARGO_TARGET_DIR=/tmp/claude-desktop-target nix-shell --run "npx tauri build -- --bundles appimage" ~/Applications/Claude-Desktop.AppImage
# CI: Commit mit [appimage] im Message → Forgejo Runner baut + uploaded automatisch
``` ```
Auf NixOS hat das AppImage einen WebKit2GTK ↔ Mesa ABI-Konflikt → siehe nächster Abschnitt.
### NixOS — nativer Build (Pflicht)
Das AppImage läuft auf NixOS nicht (KB-Eintrag #381 zur Diagnose). Stattdessen lokal bauen mit der mitgelieferten `shell.nix`:
```bash
cd "/mnt/17 - Entwicklungen/20 - Projekte/ClaudeDesktop"
# Cargo-Target auf lokales tmpfs (SMB-Mount macht Cargo-Build-Errors)
CARGO_TARGET_DIR=/tmp/claude-target \
nix-shell shell.nix --run 'npm ci && npm run tauri build -- --bundles appimage'
# Tauri-Bundling kann an pkg-config-Bug scheitern — Binary reicht aber:
nix-shell shell.nix --run /tmp/claude-target/release/claude-desktop
```
Permanenter Wrapper:
```bash
cat > ~/.local/bin/claude-desktop <<'EOF'
#!/usr/bin/env bash
cd "/mnt/17 - Entwicklungen/20 - Projekte/ClaudeDesktop"
exec nix-shell shell.nix --run /tmp/claude-target/release/claude-desktop
EOF
chmod +x ~/.local/bin/claude-desktop
```
### Development
```bash
cd "/mnt/17 - Entwicklungen/20 - Projekte/ClaudeDesktop"
nix-shell shell.nix --run 'npm ci && npm run tauri:dev' # mit Hot-Reload
```
## CI/CD-Pipeline
Workflow: `.forgejo/workflows/build-appimage.yml`
| Trigger | Was passiert |
|---|---|
| Push auf `main` mit `[appimage]` in Commit-Message | AppImage bauen + in Package-Registry hochladen |
| Push eines Tags `v*` | zusätzlich als Release-Asset anhängen |
**Runner:** `16-Forgejo-Runner-AppImage` (Debian Bookworm, glibc, mit linuxdeploy + appimagetool + Tauri-Build-Stack vorinstalliert). Der Standard-Alpine-Runner kann Tauri-AppImages nicht bauen (musl-Inkompatibilität von linuxdeploy). Setup des Runners: KB-Eintrag #371.
**Workflow-Eigenheiten** (gut zu wissen wenn was ändern):
- AppImage-Filename mit Leerzeichen (`Claude Desktop_…`) wird vor Upload zu `Claude-Desktop_…` umbenannt (curl-URL-Bug)
- Vor jedem Upload werden alte Versionen gelöscht (Forgejo Package-Registry weist PUT auf existierenden Pfad mit HTTP 409 ab)
- Custom-AppRun mit NixOS-Detection wird nach `tauri build` eingesetzt + mit `appimagetool` re-bundled. Der Hook `apprun-hooks/linuxdeploy-plugin-gtk.sh` darf dabei nicht überschrieben werden (KB #384), sonst finden WebKit-Subprozesse ihre Helpers nicht
- Ntfy-Notifications (Build-Start/Success/Failure) — Topic `vk-builds`
Ntfy-Setup für andere Projekte: KB #190/#191/#220.
## VS-Code-Extension `claude-desktop-bridge`
Eigenes Subprojekt unter [vscode-extension/](vscode-extension/). Ermöglicht Claude Desktop, VSCodium fernzusteuern (Datei öffnen, Cursor-Position setzen, Terminal-Befehle absetzen) über einen WebSocket-Server (Port 7890 default).
```bash
cd vscode-extension
npm ci && npm run compile
# Verpacktes vsix: claude-desktop-bridge-0.1.0.vsix
codium --install-extension claude-desktop-bridge-0.1.0.vsix
```
Befehle in VSCodium: `Claude Desktop: Verbindung starten/beenden`. Die App meldet sich beim Start automatisch.
## Projektstruktur (Stand)
```
ClaudeDesktop/
├── src-tauri/src/ # 16 Rust-Module
│ ├── main.rs # Entry (setzt WEBKIT_DISABLE_*-Defaults für Linux)
│ ├── lib.rs # Tauri-App-Setup
│ ├── claude.rs # Claude Agent SDK Integration
│ ├── db.rs # MySQL (claude-DB) + SQLite-Persistierung
│ ├── guard.rs # Guard-Rails (Critical/Moderate/Safe)
│ ├── hooks.rs # Hook-System
│ ├── memory.rs # Memory/Knowledge-Graph
│ ├── voice.rs # Whisper STT + Voice-Activity-Detection
│ ├── ide.rs # Bridge zur VSCodium-Extension
│ ├── audit.rs, knowledge.rs, programs.rs, session.rs,
│ │ teaching.rs, update.rs, context.rs
│ └── ...
├── src/ # SvelteKit Frontend
│ ├── routes/+layout.svelte
│ ├── routes/+page.svelte
│ ├── routes/presentation/+page.svelte
│ └── lib/components/ # 24 UI-Panels
├── vscode-extension/ # VSCodium-Bridge
├── .forgejo/workflows/ # CI/CD
├── shell.nix # NixOS Dev-Shell
├── ROADMAP.md # Phase-Status
├── TEST-ROADMAP.md # Test-Plan
├── tools.yaml # MCP-Tool-Inventar
└── package.json
```
## Wissensbasis-Referenzen (für künftige Sessions)
| KB-ID | Thema |
|---|---|
| #311 | Diese Pipeline (Workflow-Tricks) |
| #371 | Debian Forgejo-Runner Setup |
| #372 | libssl-dev für openssl-sys |
| #381 | NixOS WebKit2GTK EGL-Crash → native build |
| #382 | Cargo auf SMB → CARGO_TARGET_DIR umleiten |
| #384 | Custom-AppRun + linuxdeploy-Hook |
| #248 | Tauri 2.0 + SvelteKit auf NixOS shell.nix |
--- ---
## Architektur ## Motivation
Claude Code in VSCodium funktioniert, hat aber Grenzen:
- Sidebar-Chat ist eng, keine eigene Fensterverwaltung
- Kein Überblick was Claude gerade tut (Dateien, Befehle, DB-Queries)
- Kein "Stopp"-Button bei laufenden Aktionen
- Keine Präsentations-Ansicht für Ergebnisse
- Keine native OS-Integration (Fenster steuern, Programme öffnen)
## Vision
### Variante A: Native Desktop-App (begleitend)
Claude arbeitet begleitend — der User sieht alles mit und kann jederzeit eingreifen.
``` ```
┌──────────────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────────┐
│ SvelteKit Frontend │ │ Claude Desktop [─][□][×]│
│ 27 Svelte-Komponenten (Chat, Voice, Guard-Rails, ...) │ ├──────────────┬──────────────────────────────────────┤
├──────────────────────────────────────────────────────────┤ │ │ │
│ Tauri IPC │ │ 💬 Chat │ 📋 Live-Aktivität │
├──────────────────────────────────────────────────────────┤ │ │ │
│ Rust Backend │ │ Du: Fixe │ ▶ Lese product/price.php:1609 │
│ 18 Module (claude.rs, db.rs, guard.rs, voice.rs, ...) │ │ den Bug in │ ▶ Grep "addMoreActions" in 3 Files │
├──────────────────────┬───────────────────────────────────┤ │ der Preis- │ ▶ Edit actions_produktkarte.class.php│
│ SQLite (lokal) │ Unix Domain Socket / stdio │ │ seite │ ✓ Deploy nach /var/www/dolibarr/... │
│ Sessions, Settings │ ↕ │ │ │ │
│ Memory, Audit │ Claude Bridge (Node.js) │ │ Claude: │ ⚠ Will deployen auf PROD │
│ Offline-Queue │ @anthropic-ai/claude-agent-sdk │ │ Gefunden, │ [Erlauben] [Ablehnen] │
├──────────────────────┤ ↕ │ │ der Hook... │ │
│ MySQL (claude-db) │ Claude API (Anthropic) │ │ ├──────────────────────────────────────┤
│ Wissensbasis │ + MCP-Server (6 Stueck) │ │ │ 📊 Ergebnis-Präsentation │
└──────────────────────┴───────────────────────────────────┘ │ │ │
│ │ Vorher: list=0 (unsichtbar) │
│ │ Nachher: list=3 (auf Karte) │
│ │ │
│ │ [Diff anzeigen] [Screenshot] │
│ │ │
├──────────────┴──────────────────────────────────────┤
│ [⏹ STOPP] CPU: 2% │ DB: claude │ Git: main │
└─────────────────────────────────────────────────────┘
```
**Kernfeatures:**
- **Chat-Panel** — Aufgaben eingeben, Antworten lesen
- **Live-Aktivität** — Echtzeit was Claude tut (Dateien, Befehle, Queries)
- **Kritische Aktionen** — Popup bei Prod-Deploy, DB-Änderungen, Git Push
- **Präsentations-View** — Nach einer Aufgabe: Vorher/Nachher, Diffs, Screenshots
- **STOPP-Button** — Sofort alles abbrechen
- **Statusleiste** — Aktive DB, Git-Branch, CPU/RAM
**Technologie-Stack:**
- **Tauri 2.0** (Rust + WebView) — native App, ~5 MB statt 200 MB Electron
- **SvelteKit** — Frontend (gleiche Technologie wie Leckerbuch, VDE Katalog)
- **Claude Code SDK** (`@anthropic-ai/claude-code`) — AI-Backend
- **Claude DB** — Direkte MySQL-Anbindung (kein REST-Umweg)
- **MCP-Tools** — Docker, Forgejo, Wissensbasis
### Variante B: Autonome VM (selbständig arbeitend)
Claude hat einen eigenen Rechner (VM) mit Desktop und arbeitet Aufgaben selbständig ab. Der User überwacht remote.
```
┌──────────────────────────────────────────┐
│ Unraid Server │
│ │
│ ┌────────────────────────────────────┐ │
│ │ VM: Claude Agent │ │
│ │ │ │
│ │ ┌──────────┐ ┌───────────────┐ │ │
│ │ │ Desktop │ │ Claude Agent │ │ │
│ │ │ (XFCE) │←→│ Computer Use │ │ │
│ │ │ │ │ Terminal │ │ │
│ │ │ Dolibarr │ │ Claude DB │ │ │
│ │ │ Browser │ │ MCP Tools │ │ │
│ │ │ IDE │ │ Git/Forgejo │ │ │
│ │ └──────────┘ └───────────────┘ │ │
│ │ ↕ │ │
│ │ VNC/noVNC (Port 6080) │ │
│ └────────────────────────────────────┘ │
│ │
│ Netzwerk: ISOLIERT von Prod! │
│ - Eigenes VLAN / Bridge │
│ - Zugriff nur auf Test-DB │
│ - Kein SSH zu Unraid │
│ - Kein Zugriff auf Prod-Dolibarr │
└──────────────────────────────────────────┘
Eddy (Browser → VNC)
Sieht Claude arbeiten
Kann jederzeit eingreifen
```
**Wie Claude Computer Use funktioniert:**
1. Claude bekommt einen Screenshot des Desktops
2. Claude analysiert was er sieht
3. Claude sendet Maus-/Tastatur-Befehle
4. Nächster Screenshot → nächste Aktion
5. Kann jedes Programm bedienen das ein Mensch bedienen kann
**Sicherheitskonzept für die VM:**
- **Netzwerk-Isolation** — eigenes VLAN, kein Zugriff auf Prod-Server
- **Nur Test-DB** — dolibarr_test auf 192.168.155.11, nie Prod-DB
- **Kein SSH nach außen** — keine SSH-Keys zu Unraid oder anderen Servern
- **Snapshot-basiert** — VM-Snapshot vor jeder Aufgabe, Rollback bei Problemen
- **Audit-Log** — jeder Befehl, jede Aktion wird geloggt
- **Zeitlimit** — maximale Laufzeit pro Aufgabe
- **Kill-Switch** — ein Befehl stoppt alles sofort
**Use Cases für die VM:**
- Dolibarr-Module entwickeln und testen (Browser + Terminal)
- Automatische Code-Reviews über mehrere Repos
- Dokumentation erstellen mit Screenshots
- UI-Tests durchführen (Dolibarr durchklicken, Fehler finden)
- Batch-Aufgaben (alle Module updaten, Lang-Dateien synchronisieren)
## Empfehlung: Stufenweise vorgehen
### Stufe 1: Native Desktop-App (Variante A)
- Sofort umsetzbar mit vorhandenem Wissen (Svelte, Tauri)
- Claude arbeitet begleitend, User behält Kontrolle
- Ersetzt die VSCodium-Sidebar durch bessere UX
- Geschätzter Aufwand: 2-3 Wochen Grundgerüst
### Stufe 2: Autonome VM (Variante B) — später
- Erst wenn Stufe 1 stabil läuft und Vertrauen aufgebaut ist
- Guard-Rails und Audit-Log müssen wasserdicht sein
- Netzwerk-Isolation auf Unraid einrichten
- Geschätzter Aufwand: 1-2 Wochen Setup, dann iterativ
## Architektur — Variante A im Detail
### Projektstruktur
```
ClaudeDesktop/
├── src-tauri/ # Rust-Backend (Tauri)
│ ├── src/
│ │ ├── main.rs # App-Einstiegspunkt
│ │ ├── claude.rs # Claude SDK Integration
│ │ ├── db.rs # Direkte MySQL-Anbindung
│ │ └── guard.rs # Sicherheits-Regeln
│ ├── Cargo.toml
│ └── tauri.conf.json
├── src/ # SvelteKit Frontend
│ ├── routes/
│ │ ├── +layout.svelte # Haupt-Layout (Chat + Panels)
│ │ ├── chat/ # Chat-Ansicht
│ │ ├── activity/ # Live-Aktivität
│ │ ├── presentation/ # Ergebnis-Präsentation
│ │ └── settings/ # Einstellungen
│ ├── lib/
│ │ ├── claude-bridge.ts # Kommunikation mit Tauri-Backend
│ │ ├── db.ts # Claude-DB Queries
│ │ └── stores.ts # Svelte Stores (State)
│ └── app.html
├── package.json
├── svelte.config.js
├── vite.config.ts
└── README.md
``` ```
### Datenfluss ### Datenfluss
```
User-Eingabe (Chat)
SvelteKit Frontend
↓ (Tauri IPC)
Rust Backend
Claude Code SDK (Node.js child process)
Claude API (Anthropic)
Tool-Ausführung (Bash, Dateien, DB, MCP)
Ergebnis zurück an Frontend
Live-Aktivität + Präsentation anzeigen
```
1. **User** → Eingabe im ChatPanel (Text/Sprache/File-Drop) ### Claude-DB Integration
2. **Frontend**`invoke('send_message')` via Tauri IPC Statt REST-API (aktuell) → direkte MySQL-Verbindung im Rust-Backend:
3. **Rust** → Sticky Context + KB-Hints laden, an Bridge senden ```rust
4. **Bridge**`query()` mit Claude Agent SDK, Events streamen // Direkte DB-Abfrage, kein MCP-Umweg
5. **Claude API** → Antwort + Tool-Calls (Bash, Read, Edit, MCP, ...) let results = db.query(
6. **Bridge** → Events via JSON-Lines an Rust zurueck "SELECT * FROM knowledge WHERE MATCH(title,content) AGAINST(? IN BOOLEAN MODE)",
7. **Rust**`emit()` Events ans Frontend &[search_term]
8. **Frontend** → Live-Rendering (Text, Tools, Subagents) ).await?;
```
- Schneller (kein HTTP-Roundtrip)
- Keine Token-Limits bei großen Ergebnissen
- Full-Text-Search direkt in MySQL
### Bridge-Modi ### Guard-Rails im nativen Programm
```rust
enum ActionRisk {
Safe, // Dateien lesen, Code schreiben → automatisch
Moderate, // Git commit, lokaler Deploy → Statusbar-Hinweis
Critical, // Prod-Deploy, DB-Schema, Git Push → Popup + Bestätigung
Blocked, // rm -rf, force push main → hart blockiert
}
```
| Modus | Beschreibung | Wann | ## Sprach-Interface — Reden mit Claude
|-------|-------------|------|
| **UDS-Daemon** | Bridge als eigenstaendiger Prozess, ueberlebt App-Neustart | Standard (bevorzugt) |
| **stdio** | Bridge als Child-Process mit stdin/stdout | Fallback wenn UDS fehlschlaegt |
Socket-Pfad: `/tmp/claude-bridge.sock`, PID-File: `/tmp/claude-bridge.pid` ### Konzept
--- Echtes Gespräch mit Claude — reden, unterbrechen, weiterreden. Kein "Aufnahme starten/stoppen", sondern natürlicher Dialog.
## Dateistruktur ```
┌─────────────────────────────────────────────────┐
│ │
│ 🎤 Du sprichst │
│ ↓ │
│ Whisper (Speech-to-Text, lokal) │
│ ↓ │
│ VAD erkennt: "User hat aufgehört zu reden" │
│ ↓ │
│ Text → Claude API → Antwort-Text │
│ ↓ │
│ TTS (Text-to-Speech) → Lautsprecher 🔊 │
│ ↓ │
│ Du unterbrichst → VAD erkennt Sprache │
│ → TTS stoppt sofort │
│ → Whisper nimmt deine neue Eingabe auf │
│ → Kreislauf beginnt von vorn │
│ │
└─────────────────────────────────────────────────┘
```
### Rust Backend (`src-tauri/src/`) ### Technologie
| Datei | Zeilen | Funktion | | Komponente | Technologie | Läuft wo |
|-------|--------|----------| |---|---|---|
| `lib.rs` | ~340 | App-Setup, Tray-Icon, Global Hotkey (Super+C), Plugin-Init | | **Speech-to-Text** | OpenAI Whisper (whisper.cpp) | Lokal auf NixOS, kein Cloud-Upload |
| `claude.rs` | ~1000 | **Kernmodul**: Bridge-Kommunikation (UDS/stdio), MCP-Hub, Ollama, send_message | | **Voice Activity Detection** | Silero VAD oder WebRTC VAD | Lokal, erkennt Sprache vs. Stille |
| `db.rs` | ~800 | SQLite: Sessions, Messages, Settings, Projekte, Monitor-Events, Fehler-Tracking | | **Text-to-Speech** | OpenAI TTS API oder ElevenLabs | Cloud (Streaming) |
| `knowledge.rs` | ~1000 | MySQL-Wissensbasis: Suche, Cache (60s TTL), KB-Hints, Smart Hints v2 | | **Interrupt-Erkennung** | VAD + sofortiger TTS-Stopp | Lokal |
| `session.rs` | ~300 | Session-CRUD, Offline-Queue (queue/flush/clear) |
| `guard.rs` | ~250 | Guard-Rails: Safe/Moderate/Critical/Blocked, Permissions |
| `memory.rs` | ~200 | Persistentes Gedaechtnis: Auto-Load, Patterns, Cross-Session |
| `context.rs` | ~300 | 3-Schichten Context: Sticky/Projekt/Hints, Render fuer Prompt |
| `voice.rs` | ~355 | Whisper STT + Piper TTS (5 deutsche Stimmen), offline |
| `hooks.rs` | ~200 | Hook-System: SessionStart, PreToolUse, PostToolUse |
| `audit.rs` | ~150 | Audit-Log: Alle Aktionen mit Timestamp + Risk-Level |
| `programs.rs` | ~300 | D-Bus Aktionen, Screenshot-Capture, Xvfb |
| `ide.rs` | ~200 | VSCodium-Extension Bridge (WebSocket Port 7890) |
| `clipboard.rs` | ~150 | Clipboard-Watch: Code/URL/Fehler erkennen |
| `teaching.rs` | ~150 | Praesentations-/Schulungsmodus (separates Fenster) |
| `chat_window.rs` | ~100 | Chat-Detach: Separates Fenster herausloesen/zurueckholen |
| `update.rs` | ~250 | Auto-Updater: Forgejo Package Registry, Lock-Datei |
| `commands.rs` | ~100 | Slash-Command Registry (scannt ~/.claude/commands/) |
### Frontend (`src/lib/components/`) ### Gesprächs-Modi
| Komponente | Funktion | **Freies Gespräch** — wie mit einem Kollegen reden:
|-----------|----------| - Du redest, Claude hört zu (Whisper transkribiert live)
| `ChatPanel.svelte` | **Hauptkomponente**: Nachrichtenliste, Eingabe, File-Drop, Modus-Indikator | - Pause > 1,5 Sekunden → Claude antwortet
| `SessionList.svelte` | Sidebar: Sessions, Projekt-Wechsel, Suche | - Du unterbrichst → Claude stoppt sofort, hört dir zu
| `ActivityPanel.svelte` | Live-Aktivitaet: Tool-Calls, Agent-Status | - Claude kann nachfragen wenn etwas unklar ist
| `AgentView.svelte` | Subagent-Hierarchie als Baumansicht |
| `MonitorPanel.svelte` | System-Monitor: API/Tool/Error Events |
| `PerformancePanel.svelte` | Token-/Kosten-Statistiken |
| `GuardRailsPanel.svelte` | 3-Tab: Live-Feed / Regeln / Blockiert |
| `VoicePanel.svelte` | Push-to-Talk, Gespraechsmodus, TTS-Wiedergabe |
| `ContextPanel.svelte` | Sticky Context anzeigen/editieren |
| `MemoryPanel.svelte` | Persistentes Gedaechtnis CRUD |
| `KnowledgePanel.svelte` | Wissensbasis durchsuchen |
| `HooksPanel.svelte` | Hook-Verwaltung |
| `SettingsPanel.svelte` | VS-Code-artiges Settings mit Suche/Kategorien |
| `ProgramsPanel.svelte` | D-Bus Aktionen, Screenshot, Xvfb |
| `IdePanel.svelte` | VSCodium-Verbindung Status |
| `AuditLog.svelte` | Audit-Eintraege durchsuchen |
| `QuickActions.svelte` | Ctrl+K Kommandopalette |
| `CommandPalette.svelte` | Slash-Command Autocomplete (/-Eingabe) |
| `CodeBlock.svelte` | Syntax-Highlighting + Copy-Button |
| `DiffView.svelte` | Diff-Anzeige fuer Code-Aenderungen |
| `MermaidDiagram.svelte` | Mermaid-Diagramme rendern |
| `StopButton.svelte` | Abbruch-Button waehrend Agent laeuft |
| `UpdateDialog.svelte` | Auto-Update Bestaetigungsdialog |
| `FilePreview.svelte` | Datei-Vorschau (Text/Bild) |
| `AnimatedCode.svelte` | Code-Tipp-Animation fuer Praesentation |
| `AutoCorrectionModal.svelte` | Fehler-Pattern Vorschlag |
### Bridge (`scripts/claude-bridge.js`) **Diktier-Modus** — Claude führt aus was du sagst:
- "Fixe den Bug in der Preisseite, der Umrechnungsfaktor wird nicht berücksichtigt"
- Claude arbeitet, kommentiert per Sprache was er tut
- Du kannst jederzeit "Stopp" oder "Warte mal" sagen
~1100 Zeilen Node.js. Zentrale Datei fuer die Claude-Kommunikation: **Präsentations-Modus** — Claude erklärt was er gemacht hat:
- "Zeig mir was du geändert hast"
- Claude öffnet Diff-View und erklärt per Sprache die Änderungen
- Du kannst zwischenfragen: "Warum hast du das so gemacht?"
- **query()** via `@anthropic-ai/claude-agent-sdk` — streamt Events ### Stimmen-Optionen
- **Multi-Agent-Modi**: Solo / Handlanger (Haiku-Worker) / Experten (4 spezialisierte Agents) / Auto
- **Subagent-Tracking**: Map mit toolUseId → agentId/type/depth
- **MCP-Server Injection**: Configs von Rust empfangen, in queryOptions injizieren
- **Ollama-Integration**: Auto-Detect beim Start, local-query fuer einfache Tasks
- **Auto-Retry**: 3x Backoff bei Rate-Limit/5xx/Netzwerkfehler
- **Session-Resume**: Stale Session-ID → automatisch neue Session starten
- **UDS-Server-Modus**: `--socket /tmp/claude-bridge.sock` fuer Daemon-Betrieb
### Stores (`src/lib/stores/`) **OpenAI TTS API:**
- 6 Stimmen (alloy, echo, fable, onyx, nova, shimmer)
- Sehr natürlich, Streaming-fähig (~200ms Latenz)
- Kosten: ~$15 pro 1M Zeichen
| Store | Inhalt | **ElevenLabs:**
|-------|--------| - Hunderte Stimmen, eigene Stimmen klonbar
| `app.ts` | activeSession, activeProject, agentMode, chatDetached, processingPhase | - Noch natürlicher, emotionaler
| `events.ts` | Event-Handler fuer alle Bridge-Events (text, result, tool-start/end, ...) | - Deutsch-Support gut
| `updateTrigger.ts` | Reactive Trigger fuer Panel-Updates | - Kosten: ab $5/Monat (30 Min)
--- **Lokal (Piper TTS):**
- Kostenlos, keine Cloud
- Deutsche Stimmen verfügbar
- Qualität gut aber nicht so natürlich wie Cloud
- Keine Latenz durch Netzwerk
## Features komplett ### Latenz-Budget (Ziel: < 2 Sekunden)
### Phase 1-2 (v0.1.0 — 14.04.2026) ```
- Tauri 2.0 + SvelteKit 5 Grundgeruest VAD erkennt Stille: ~300ms
- 4-Panel Layout, Claude Bridge (stdio), Guard-Rails Whisper transkribiert: ~500ms (lokal, whisper.cpp mit GPU)
- SQLite + Session-Management, Claude-DB Integration Claude API Antwort: ~800ms (erstes Token, Streaming)
- 3-Schichten Context, Multi-Agent-Modi, Hook-System TTS erstes Audio: ~200ms (Streaming)
- VSCodium-Extension, Programm-Steuerung (D-Bus, Xvfb) ─────────────────────────────────
- Praesentationsmodus, System-Monitor, Subagent-Hierarchie Gesamt bis erste Silbe: ~1.800ms
- Auto-Updater, Slash-Commands, KB-Hints, Error-Patterns ```
### Phase 3: Performance (22.04.2026) Mit Streaming: Claude beginnt zu "reden" während er noch denkt — wie ein Mensch der anfängt zu antworten bevor der Gedanke fertig ist.
- KB-Cache (RAM, 60s TTL), Bridge Warm-Start, Lazy Panel-Load
- Session-Resume Fix, Auto-Retry (3x Backoff), Heartbeat
- FIFO Message Queue, **Bridge-Daemon** (UDS), **Unix Socket IPC**
### Phase 4: Codium-Killer Features ### Integration in die Desktop-App
- Projekt-Wechsel (Ein-Klick), **MCP-Hub nativ** (6 Server)
- Guard-Rails UI (3 Tabs), Persistent Memory, Quick-Actions (Ctrl+K)
- Voice (Whisper + Piper, offline), Settings-Panel, Chat-Detach
- Aktivitaets-Phasen (Denkt/Streamt/Tool/Subagent)
### Phase 5: Lokale KI + Offline ```
- Whisper.cpp + Piper-TTS (komplett offline, 5 deutsche Stimmen) ┌─────────────────────────────────────────────────────┐
- **Ollama-Integration** (Auto-Detect, local-query) │ Claude Desktop [─][□][×]│
- **Offline-Queue** (SQLite, flush bei Reconnect) ├──────────────┬──────────────────────────────────────┤
│ │ │
│ 💬 Chat │ 📋 Live-Aktivität │
│ │ │
│ (Text wird │ ▶ Lese product/price.php │
│ live mit- │ ▶ Edit actions_produktkarte.class.php│
│ geschrieben │ ✓ Deploy nach /var/www/dolibarr/... │
│ während │ │
│ gesprochen) │ │
│ │ │
├──────────────┴──────────────────────────────────────┤
│ 🎤 ████████░░░░░░ Zuhören... [🔇 Stumm] [⏹] │
└─────────────────────────────────────────────────────┘
```
### Phase 6: Desktop-Integration - Mikrofon-Leiste unten zeigt Pegel
- D-Bus Actions (10 Aktionen), Clipboard-Watch, File-Drop - Gesprochenes wird als Text im Chat mitgeschrieben (Transkript)
- Screenshot-Analyse, Global Hotkey (Super+C) - Claudes Antwort wird gleichzeitig als Text angezeigt und vorgelesen
- Stumm-Taste schaltet Mikrofon aus (nur Text-Modus)
--- ### Whisper lokal auf NixOS
## MCP-Server (6 Stueck, automatisch geladen) ```nix
# In configuration.nix
environment.systemPackages = with pkgs; [
whisper-cpp # C++ Port, schnell, CPU/GPU
# oder
openai-whisper # Original Python, braucht mehr RAM
];
```
| Name | Funktion | Transport | Whisper "small" oder "medium" Modell reicht für Deutsch — ~500 MB RAM, Echtzeit auf CPU.
|------|----------|-----------|
| `forgejo` | Git: Repos, Issues, PRs, Releases | stdio |
| `claude-db` | MySQL Wissensbasis: Suche, CRUD | SSE (MCP-Hub) |
| `playwright` | Browser-Automatisierung | stdio |
| `context7` | Library-Dokumentation | stdio |
| `portainer` | Docker-Container-Management | stdio |
| `homeassistant` | Smart-Home Steuerung | stdio |
Configs in `~/.claude.json` → werden beim Bridge-Start automatisch injiziert. ## Voraussetzungen
--- ### Für Variante A (Desktop-App)
- NixOS: `rustc`, `cargo`, `nodejs`, `tauri-cli` in der Nix-Config
- Anthropic API Key (bereits vorhanden)
- Claude Code SDK npm-Paket
- MySQL-Client-Library für Rust (`sqlx` oder `mysql_async`)
## CI/CD ### Für Variante B (VM)
- Unraid: VM mit Linux + Desktop (Ubuntu/Debian + XFCE)
- VNC-Server in der VM
- noVNC-Container auf Unraid für Browser-Zugriff
- Isoliertes Netzwerk (VLAN oder Bridge)
- Claude API Key + Computer Use Beta-Zugang
**Workflow:** `.forgejo/workflows/build-appimage.yml` ## Offene Fragen
**Runner:** `16-Forgejo-Runner-AppImage` (Debian Bookworm)
**Trigger:** `[appimage]` in Commit-Message auf `main`
1. Commit mit `[appimage]` → Push - [ ] Anthropic API Key Kosten für Computer Use (Screenshot-intensiv)?
2. Forgejo Runner baut AppImage (~8-10 Min) - [ ] Tauri 2.0 auf NixOS — Nix-Paket verfügbar?
3. Upload in Forgejo Package Registry - [ ] Claude Code SDK — stabil genug für Production?
4. Ntfy-Notification (Topic: `vk-builds`) - [ ] VM auf Unraid — genug RAM/CPU frei?
5. App zieht Update beim naechsten Start (Auto-Updater)
**NixOS:** AppImage hat WebKit2GTK/Mesa ABI-Konflikt → lokaler Build mit `shell.nix` noetig. Nix-Wrapper in `~/.local/bin/claude-desktop`.
---
## Tastenkuerzel
| Kuerzel | Aktion |
|---------|--------|
| `Super+C` | Claude-Eingabe von ueberall oeffnen |
| `Ctrl+K` | Quick-Actions Palette |
| `Ctrl+Enter` | Nachricht senden |
| `Escape` | Quick-Actions/Autocomplete schliessen |
| `/` | Slash-Command Autocomplete |
---
## Wissensbasis-Referenzen
| KB-ID | Thema |
|-------|-------|
| #248 | Tauri 2.0 + SvelteKit auf NixOS shell.nix |
| #311 | CI/CD Pipeline (Workflow-Tricks) |
| #371 | Debian Forgejo-Runner Setup |
| #381 | NixOS WebKit2GTK EGL-Crash |
| #382 | Cargo auf SMB → CARGO_TARGET_DIR |
| #384 | Custom-AppRun + linuxdeploy-Hook |
---
## Nicht geplant
- Multi-User / Team-Features
- Cloud-Sync
- Plugin-System (overkill fuer 1-User-App)
- Electron-Port (Tauri bleibt)

View file

@ -1089,39 +1089,16 @@ process.on('SIGINT', () => { clearInterval(keepAlive); cleanupDaemon(); process.
process.on('exit', () => { cleanupDaemon(); }); process.on('exit', () => { cleanupDaemon(); });
// Globale Fehler-Handler — Bridge darf nicht still abstürzen // Globale Fehler-Handler — Bridge darf nicht still abstürzen
// WICHTIG: err.stack ist ein lazy Getter der bei jedem Zugriff neu formatiert wird
// und bei V8-OOM selbst einen OOM-Abort auslösen kann. Daher: einmal lesen, kürzen,
// try/catch drumrum — der Handler darf nicht selbst crashen.
function safeStack(err) {
try {
const raw = (err && err.stack) ? String(err.stack) : '';
return raw.length > 2000 ? raw.slice(0, 2000) + '\n...[gekürzt]' : raw;
} catch {
return '[stack nicht lesbar]';
}
}
process.on('uncaughtException', (err) => { process.on('uncaughtException', (err) => {
try { process.stderr.write(`❌ Unbehandelter Fehler: ${err.message}\n${err.stack}\n`);
const msg = (err && err.message) ? String(err.message).slice(0, 500) : String(err).slice(0, 500); sendEvent('bridge-error', { type: 'uncaughtException', message: err.message, stack: err.stack });
const stack = safeStack(err); sendMonitorEvent('error', `Bridge Crash: ${err.message}`, { stack: err.stack });
process.stderr.write(`❌ Unbehandelter Fehler: ${msg}\n${stack}\n`);
sendEvent('bridge-error', { type: 'uncaughtException', message: msg, stack });
sendMonitorEvent('error', `Bridge Crash: ${msg}`, {});
} catch (inner) {
try { process.stderr.write(`❌ Handler-Fehler: ${inner && inner.message}\n`); } catch {}
}
}); });
process.on('unhandledRejection', (reason) => { process.on('unhandledRejection', (reason) => {
try { const msg = reason instanceof Error ? reason.message : String(reason);
const msg = reason instanceof Error
? String(reason.message).slice(0, 500)
: String(reason).slice(0, 500);
process.stderr.write(`❌ Unhandled Promise Rejection: ${msg}\n`); process.stderr.write(`❌ Unhandled Promise Rejection: ${msg}\n`);
sendEvent('bridge-error', { type: 'unhandledRejection', message: msg }); sendEvent('bridge-error', { type: 'unhandledRejection', message: msg });
sendMonitorEvent('error', `Unhandled Rejection: ${msg}`, {}); sendMonitorEvent('error', `Unhandled Rejection: ${msg}`, {});
} catch (inner) {
try { process.stderr.write(`❌ Handler-Fehler: ${inner && inner.message}\n`); } catch {}
}
}); });
// Bereit-Signal (im stdio-Modus sofort senden, im Daemon-Modus pro Client bei Connect) // Bereit-Signal (im stdio-Modus sofort senden, im Daemon-Modus pro Client bei Connect)

View file

@ -173,10 +173,7 @@ fn start_daemon(script_path: &std::path::Path) -> Result<u32, String> {
println!("🔌 Starte Bridge-Daemon: {:?} --socket {}", script_path, SOCKET_PATH); println!("🔌 Starte Bridge-Daemon: {:?} --socket {}", script_path, SOCKET_PATH);
// --max-old-space-size=4096: Node-Default (~2GB) reicht nicht bei langen Sessions
// mit großen Thinking-Blocks/Agent-SDK-History (KB #crash-oom-stacktrace).
let child = Command::new("node") let child = Command::new("node")
.arg("--max-old-space-size=4096")
.arg(script_path) .arg(script_path)
.arg("--socket") .arg("--socket")
.arg(SOCKET_PATH) .arg(SOCKET_PATH)
@ -331,7 +328,6 @@ fn start_bridge_stdio(app: &AppHandle, script_path: &std::path::Path) -> Result<
println!("📂 Bridge Arbeitsverzeichnis: {:?}", project_dir); println!("📂 Bridge Arbeitsverzeichnis: {:?}", project_dir);
let mut child = Command::new("node") let mut child = Command::new("node")
.arg("--max-old-space-size=4096")
.arg(script_path) .arg(script_path)
.current_dir(project_dir) .current_dir(project_dir)
.stdin(Stdio::piped()) .stdin(Stdio::piped())

View file

@ -208,18 +208,6 @@
activeSessionId = session.id; activeSessionId = session.id;
$currentSessionId = session.id; $currentSessionId = session.id;
clearAll(); clearAll();
// Projekt automatisch mitwechseln wenn Session ein working_dir hat
if (session.working_dir) {
const matchingProject = projects.find(p => p.working_dir === session.working_dir);
if (matchingProject && matchingProject.id !== activeProject?.id) {
// Projekt auf Rust-Seite wechseln (CWD, Context), aber Sessions NICHT neu laden
const project: Project = await invoke('switch_project', { projectId: matchingProject.id });
activeProject = project;
console.log(`📂 Projekt mitwechselt: ${project.name}`);
}
}
// Nachrichten aus DB laden // Nachrichten aus DB laden
await loadSessionMessages(session.id); await loadSessionMessages(session.id);
} catch (err) { } catch (err) {

View file

@ -246,8 +246,8 @@ export function updateAgentStatus(id: string, status: Agent['status']) {
); );
} }
export function addToolCall(agentId: string, tool: string, args: Record<string, unknown>, fixedId?: string): string { export function addToolCall(agentId: string, tool: string, args: Record<string, unknown>): string {
const id = fixedId || crypto.randomUUID(); const id = crypto.randomUUID();
const call: ToolCall = { const call: ToolCall = {
id, id,
agentId, agentId,
@ -270,32 +270,20 @@ export function addToolCall(agentId: string, tool: string, args: Record<string,
} }
export function completeToolCall(id: string, result: unknown, failed = false) { export function completeToolCall(id: string, result: unknown, failed = false) {
toolCalls.update((calls) => { toolCalls.update((calls) =>
// Exakte ID-Suche calls.map((c) =>
let found = calls.some((c) => c.id === id);
if (found) {
return calls.map((c) =>
c.id === id c.id === id
? { ...c, status: (failed ? 'failed' : 'completed') as ToolCall['status'], completedAt: new Date(), result } ? {
: c ...c,
); status: failed ? 'failed' : 'completed',
completedAt: new Date(),
result
} }
// Fallback: Letzten laufenden Tool-Call abschließen (für Events ohne passende ID)
const lastRunning = [...calls].reverse().find((c) => c.status === 'running');
if (lastRunning) {
return calls.map((c) =>
c.id === lastRunning.id
? { ...c, status: (failed ? 'failed' : 'completed') as ToolCall['status'], completedAt: new Date(), result }
: c : c
)
); );
} }
return calls;
});
}
export function clearAll() { export function clearAll() {
agents.set([]); agents.set([]);
toolCalls.set([]); toolCalls.set([]);

View file

@ -226,8 +226,8 @@ export async function initEventListeners(): Promise<void> {
// Tool Start // Tool Start
listeners.push( listeners.push(
await listen<ToolEvent>('tool-start', async (event) => { await listen<ToolEvent>('tool-start', async (event) => {
const { id, tool, input } = event.payload; const { tool, input } = event.payload;
console.log('🔧 Tool Start:', tool, id); console.log('🔧 Tool Start:', tool);
// Inline-Aktivitätsanzeige aktualisieren // Inline-Aktivitätsanzeige aktualisieren
currentTool.set({ tool: tool || 'unknown', input: input || {} }); currentTool.set({ tool: tool || 'unknown', input: input || {} });
@ -236,8 +236,7 @@ export async function initEventListeners(): Promise<void> {
agents.update((ags) => { agents.update((ags) => {
const activeAgent = ags.find((a) => a.status === 'active'); const activeAgent = ags.find((a) => a.status === 'active');
if (activeAgent) { if (activeAgent) {
// Backend-ID durchreichen damit tool-end den Call matchen kann addToolCall(activeAgent.id, tool || 'unknown', input || {});
addToolCall(activeAgent.id, tool || 'unknown', input || {}, id);
} }
return ags; return ags;
}); });