From 7f5f31db30f31147ffe9f27b76ac96aaf83b27b3 Mon Sep 17 00:00:00 2001
From: ValueOn AG
Date: Mon, 16 Mar 2026 22:55:50 +0100
Subject: [PATCH] concepts filesystem and mobile mic
---
concepts/Agent-Toolbox-Dateisystem-Konzept.md | 594 ++++++++++++++++++
...coach-Voice-Recording-Streaming-Konzept.md | 334 ++++++++++
deployment/poweron_sec.kdbx | Bin 18222 -> 18878 bytes
3 files changed, 928 insertions(+)
create mode 100644 concepts/Agent-Toolbox-Dateisystem-Konzept.md
create mode 100644 concepts/Commcoach-Voice-Recording-Streaming-Konzept.md
diff --git a/concepts/Agent-Toolbox-Dateisystem-Konzept.md b/concepts/Agent-Toolbox-Dateisystem-Konzept.md
new file mode 100644
index 0000000..fdbd8e6
--- /dev/null
+++ b/concepts/Agent-Toolbox-Dateisystem-Konzept.md
@@ -0,0 +1,594 @@
+# Konzept: Agent Toolbox & Dateisystem-Erweiterung
+
+> Poweron Platform -- Erweiterung der Agent-Fähigkeiten und des Dateisystems
+> Status: Konzept / Version 1.2 / März 2026
+
+---
+
+## 1. Ausgangslage (IST-Zustand)
+
+### 1.1 Datenmodelle
+
+Die Grundstruktur für ein hierarchisches Dateisystem existiert bereits, wird aber kaum genutzt.
+
+**FileItem** (`gateway/modules/datamodels/datamodelFiles.py`):
+
+| Feld | Typ | Beschreibung |
+|------|-----|-------------|
+| `id` | str | Primary Key (UUID) |
+| `fileName` | str | Dateiname |
+| `mimeType` | str | MIME-Typ |
+| `fileSize` | int | Grösse in Bytes |
+| `folderId` | Optional[str] | Verweis auf übergeordneten Folder |
+| `featureInstanceId` | Optional[str] | Feature-Instanz-Zuordnung |
+| `tags` | Optional[List[str]] | Tags |
+| `status` | Optional[str] | Verarbeitungsstatus |
+| `description` | Optional[str] | Beschreibung |
+
+**FileFolder** (`gateway/modules/datamodels/datamodelFileFolder.py`):
+
+| Feld | Typ | Beschreibung |
+|------|-----|-------------|
+| `id` | str | Primary Key (UUID) |
+| `name` | str | Ordnername |
+| `parentId` | Optional[str] | Übergeordneter Folder (None = Root) |
+| `mandateId` | Optional[str] | Mandant |
+| `featureInstanceId` | Optional[str] | Feature-Instanz-Zuordnung |
+
+### 1.2 Backend-Operationen
+
+```
+Vorhanden Fehlend
+───────────────────── ──────────────────────
+Files: Files:
+ createFile ✓ copyFile ✗
+ deleteFile ✓
+ updateFile ✓ (Metadaten)
+ createFileData ✓ (Update-Pfad: Logik aus Codeeditor applyEdit kopieren)
+ getAllFiles ✓
+
+Folders: Folders:
+ createFolder ✓ deleteFolder ✗
+ listFolders ✓ renameFolder ✗
+ moveFolder ✗
+ getFolder ✗
+```
+
+### 1.3 Agent-Tools (27 registriert)
+
+Dateibezogene Tools im aktuellen Agent:
+
+| Tool | Funktion | Status |
+|------|----------|--------|
+| `readFile` | Datei lesen | Vorhanden |
+| `listFiles` | Dateien auflisten | Vorhanden |
+| `searchFiles` | Dateien suchen | Vorhanden |
+| `writeFile` | Neue Datei erstellen | Vorhanden |
+| `moveFile` | Datei verschieben (via `updateFile({folderId})`) | Vorhanden |
+| `tagFile` | Tags setzen | Vorhanden |
+| `createFolder` | Ordner erstellen | Vorhanden |
+| `listFolders` | Ordner auflisten | Vorhanden |
+| `deleteFile` | Datei löschen | **Fehlt** (Backend existiert) |
+| `renameFile` | Datei umbenennen | **Fehlt** (Backend existiert) |
+| `editFile` | Dateiinhalt ändern | **Fehlt** (Logik aus Codeeditor `applyEdit` extrahieren) |
+| `copyFile` | Datei duplizieren | **Fehlt** (Backend muss gebaut werden) |
+
+### 1.4 Codeeditor-Feature (wird entfernt -- Logik extrahieren)
+
+Das bestehende **Codeeditor-Feature** (`gateway/modules/features/codeeditor/`) wird perspektivisch entfernt. Es enthält jedoch bewährte File-Edit-Logik, die vor dem Entfernen in den Workspace-Agent **kopiert** werden muss:
+
+**Zu extrahierende Backend-Logik:**
+
+| Datei | Was kopieren |
+|-------|-------------|
+| `routeFeatureCodeeditor.py` | `applyEdit()`-Logik: File-Write via `createFileData(fileId, contentBytes)`, MIME-Type-Erkennung |
+| `datamodelCodeeditor.py` | `FileEditProposal`, `EditStatusEnum` (PENDING → ACCEPTED / REJECTED) |
+| `responseParser.py` | `file_edit`-Block-Parsing (`fileName`, `oldContent`, `newContent`) |
+
+**Zu extrahierende Frontend-Logik:**
+
+| Datei | Was kopieren |
+|-------|-------------|
+| `DiffPreviewPanel.tsx` | Diff-View mit Accept/Reject-Buttons → als Shared-Komponente extrahieren |
+| `useCodeEditor.ts` | `acceptEdit` / `rejectEdit` Handlers → in Workspace-Hook übernehmen |
+
+### 1.5 Frontend
+
+- **FileBrowser** (`frontend_nyla/src/pages/views/workspace/FileBrowser.tsx`): Gruppiert Dateien nach `featureInstanceId`, ignoriert `folderId` und Folder komplett. Kein Folder-Tree.
+- **FilesPage** (`frontend_nyla/src/pages/basedata/FilesPage.tsx`): Flache Tabelle mit FormGeneratorTable. Keine Ordner-Navigation.
+- **DataSourcePanel**: Externer Datenquellen-Tree existiert als Referenzimplementierung für Lazy-Loading-Tree-UI.
+
+---
+
+## 2. Dateisystem-Erweiterung (Backend)
+
+### 2.1 Übersicht
+
+```mermaid
+graph TB
+ subgraph fileSystem [Hierarchisches Dateisystem]
+ Root["(Global) Root"]
+ FolderA["Folder: Projekte"]
+ FolderB["Folder: Analysen"]
+ FolderC["Folder: Q1"]
+ FileA["File: Report.pdf"]
+ FileB["File: Daten.xlsx"]
+ FileC["File: Summary.md"]
+ Root --> FolderA
+ Root --> FolderB
+ FolderA --> FolderC
+ FolderA --> FileA
+ FolderC --> FileB
+ FolderB --> FileC
+ end
+
+ subgraph agent [Agent Tools]
+ CRUD_Files["Files: create, read, edit, copy, delete, rename, move"]
+ CRUD_Folders["Folders: create, delete, rename, move, list"]
+ end
+
+ agent -->|"operiert auf"| fileSystem
+```
+
+### 2.2 Neue/Erweiterte DB-Methoden
+
+Alle Methoden in `interfaceDbManagement.py` ergänzen:
+
+**Übergreifende Regeln für alle Folder-Operationen:**
+- **Unique-Name-Constraint:** Innerhalb eines Folders darf ein Foldername nur einmal vorkommen (unique pro `parentId`)
+- **Geschützter Name:** Der Name `"(Global)"` ist reserviert für den virtuellen Root und darf nicht für echte Folder verwendet werden
+- Validation bei `createFolder`, `renameFolder`, `moveFolder`
+
+**deleteFolder(folderId, recursive=False)**
+- Wenn `recursive=True`: Alle Dateien und Unterordner kaskadierend löschen
+- Wenn `recursive=False` und Folder nicht leer: Fehler werfen
+- RBAC-Check auf Folder-Ebene
+
+**renameFolder(folderId, newName)**
+- Via `recordModify(FileFolder, folderId, {"name": newName})`
+- **Unique-Name-Constraint:** Prüfen, dass im gleichen `parentId` kein anderer Folder mit `newName` existiert
+- **Geschützter Name:** `"(Global)"` darf nicht als Foldername verwendet werden (reserviert für virtuellen Root)
+
+**moveFolder(folderId, targetParentId)**
+- Validierung: Zirkelverweis verhindern (Folder darf nicht in eigenen Unterbaum verschoben werden)
+- **Unique-Name-Constraint:** Prüfen, dass im Ziel-Folder kein Folder mit gleichem Namen existiert
+- Via `recordModify(FileFolder, folderId, {"parentId": targetParentId})`
+
+**copyFile(sourceFileId, targetFolderId=None, newFileName=None)**
+- FileItem duplizieren mit neuer ID
+- **FileData vollständig duplizieren** (eigenständige Kopie, damit die Kopie unabhängig bearbeitet werden kann)
+- Optional: neuer Name, anderer Ziel-Folder
+
+**updateFileData(fileId, data)** -- Logik aus Codeeditor `applyEdit` kopieren
+- `createFileData(fileId, contentBytes)` zum Überschreiben bestehender Dateien
+- Falls `createFileData` bei existierenden Daten überspringt: vorher `deleteFileData(fileId)`, dann `createFileData(fileId, newData)`
+- FileItem-Metadaten aktualisieren (fileSize, fileHash via `updateFile`)
+- Hinweis: Codeeditor wird später entfernt -- relevante Logik vorher in `interfaceDbManagement` oder Agent-Tool extrahieren
+
+### 2.3 Root-Folder "(Global)"
+
+Der Root-Folder ist **virtuell** und wird nicht als DB-Eintrag gespeichert:
+- Dateien mit `folderId=None` gehören zum Root
+- Ordner mit `parentId=None` sind Top-Level-Ordner unter Root
+- Anzeige im UI als "(Global)" Label
+
+### 2.4 Migration: featureInstanceId entfernen
+
+Die Gruppierung nach `featureInstanceId` wird durch die Ordnerstruktur ersetzt:
+
+1. **Spalte beibehalten** aber nicht mehr für Gruppierung nutzen
+2. `featureInstanceId` als informatives Metadatum ("Erstellt in: Workspace A") in einer zusätzlichen Tabellenspalte anzeigen
+3. Bestehende Dateien: `folderId` bleibt `None` (Root), keine Migration nötig
+4. Frontend-Gruppierung nach `featureInstanceId` entfernen, stattdessen Folder-Baum nutzen
+
+---
+
+## 3. Neue Agent-Tools
+
+### 3.1 Phase 1 -- Quick Wins (Backend existiert)
+
+#### deleteFile
+
+| Eigenschaft | Wert |
+|-------------|------|
+| Backend | `interfaceDbManagement.deleteFile(fileId)` |
+| Parameter | `fileId` (required) |
+| readOnly | false |
+| Hinweis | Knowledge-Store-Einträge ebenfalls bereinigen |
+
+#### renameFile
+
+| Eigenschaft | Wert |
+|-------------|------|
+| Backend | `interfaceDbManagement.updateFile(fileId, {"fileName": newName})` |
+| Parameter | `fileId` (required), `newName` (required) |
+| readOnly | false |
+
+#### readUrl
+
+| Eigenschaft | Wert |
+|-------------|------|
+| Backend | `serviceWeb._performWebCrawl(urls=[url])` |
+| Parameter | `url` (required) |
+| readOnly | true |
+| Hinweis | Ergänzt `webSearch`: Während `webSearch` nach Informationen sucht, liest `readUrl` gezielt eine bekannte URL |
+
+#### translateText
+
+| Eigenschaft | Wert |
+|-------------|------|
+| Backend | `interfaceVoiceObjects.translateText(text, targetLanguage)` + Google Cloud Translation |
+| Parameter | `text` (required), `targetLanguage` (required), `sourceLanguage` (optional, auto-detect) |
+| readOnly | true |
+| Hinweis | Effizienter als AI-basierte Übersetzung für grosse Textmengen |
+
+### 3.2 Phase 2 -- Dateisystem-Tools
+
+#### deleteFolder
+
+| Eigenschaft | Wert |
+|-------------|------|
+| Backend | Neu: `interfaceDbManagement.deleteFolder()` |
+| Parameter | `folderId` (required), `recursive` (optional, default false) |
+| readOnly | false |
+| Sicherheit | Agent muss vor kaskadierendem Löschen bestätigen (Anzahl betroffener Dateien nennen) |
+
+#### renameFolder
+
+| Eigenschaft | Wert |
+|-------------|------|
+| Backend | Neu: `interfaceDbManagement.renameFolder()` |
+| Parameter | `folderId` (required), `newName` (required) |
+| readOnly | false |
+
+#### moveFolder
+
+| Eigenschaft | Wert |
+|-------------|------|
+| Backend | Neu: `interfaceDbManagement.moveFolder()` |
+| Parameter | `folderId` (required), `targetParentId` (required, null für Root) |
+| readOnly | false |
+| Validierung | Zirkelverweis-Check: Ziel darf kein Unterordner der Quelle sein |
+
+#### copyFile
+
+| Eigenschaft | Wert |
+|-------------|------|
+| Backend | Neu: `interfaceDbManagement.copyFile()` |
+| Parameter | `fileId` (required), `targetFolderId` (optional), `newFileName` (optional) |
+| readOnly | false |
+| Implementierung | Vollständige Duplikation: FileItem + FileData werden als eigenständige Kopie erstellt (editierbar) |
+
+#### editFile
+
+| Eigenschaft | Wert |
+|-------------|------|
+| Backend | `dbManagement.createFileData(fileId, contentBytes)` (Logik aus Codeeditor kopiert, siehe 5.2) |
+| Parameter | `fileId` (required), `content` (required, Text-Inhalt) |
+| readOnly | false |
+| Einschränkung | Nur für Text-basierte Dateien (text/\*, application/json, etc.) |
+| UX-Option | Optional: Approve/Reject-Mode (Agent schlägt Edit als Diff vor, User bestätigt) |
+
+### 3.3 Phase 3 -- Erweiterte Fähigkeiten
+
+#### speechToText
+
+| Eigenschaft | Wert |
+|-------------|------|
+| Backend | `connectorVoiceGoogle.speechToText()` |
+| Parameter | `fileId` (required, Audio-Datei), `language` (optional, auto-detect) |
+| readOnly | true |
+| Hinweis | Gegenstück zu `textToSpeech`. Akzeptiert Audio-Dateien aus dem Workspace |
+
+#### detectLanguage
+
+| Eigenschaft | Wert |
+|-------------|------|
+| Backend | `interfaceVoiceObjects.detectLanguage()` |
+| Parameter | `text` (required) |
+| readOnly | true |
+
+#### searchImages
+
+| Eigenschaft | Wert |
+|-------------|------|
+| Backend | Neu: Google Custom Search API (Konzept in `local/pending/doc_enhancement_web_image_actions_pending.md`) |
+| Parameter | `query` (required), `maxResults` (optional), `imageType` (optional), `size` (optional) |
+| readOnly | true |
+| Abhängigkeit | Google Custom Search API Key + Engine ID |
+| Hinweis | Bilder werden als URLs zurückgegeben, können via `downloadFromDataSource` oder einem neuen Download-Mechanismus heruntergeladen werden |
+
+#### neutralizeData
+
+| Eigenschaft | Wert |
+|-------------|------|
+| Backend | `serviceNeutralization.processText()` / `processFile()` |
+| Parameter | `text` (optional) oder `fileId` (optional, eines von beiden required) |
+| readOnly | true (gibt anonymisierten Text zurück, ändert Original nicht) |
+
+#### executeCode
+
+| Eigenschaft | Wert |
+|-------------|------|
+| Backend | Neu: Sandboxed Code Execution |
+| Parameter | `code` (required), `language` (required: "python" oder "javascript") |
+| readOnly | true |
+| Sicherheitskonzept | Siehe Abschnitt 5.1 |
+
+---
+
+## 4. UI-Umbau
+
+### 4.1 Dateien-Seite (Split-View)
+
+```
+┌──────────────────────────────────────────────────────────┐
+│ Dateien │
+├───────────────────┬──────────────────────────────────────┤
+│ │ [+ Upload / Drag&Drop] [Filter]│
+│ (Global) │ ─────────────────────────────────── │
+│ ├── Projekte │ Name Typ Grösse Quelle │
+│ │ ├── Q1 │ ─────────────────────────────────── │
+│ │ └── Q2 │ Report.pdf PDF 1.2 MB Workspace│
+│ ├── Analysen │ Daten.xlsx XLSX 450 KB Chat AI │
+│ └── Archiv │ Notes.md MD 12 KB Chat AI │
+│ │ │
+│ [+ Neuer Ordner] │ │
+│ │ │
+├───────────────────┴──────────────────────────────────────┤
+│ ◄═══════════════ Divider (verschiebbar) ═══════════════►│
+└──────────────────────────────────────────────────────────┘
+
+Wichtig: Die Action-Buttons (Upload, Filter) sind OBERHALB der Tabelle fixiert,
+damit sie bei wechselnder Tabellengrösse nicht springen.
+```
+
+### 4.2 Komponenten
+
+**FolderTree (neue Shared-Komponente)**
+- Rekursiv verschachtelbarer Baum
+- Lazy-Loading der Unterordner (Referenz: `DataSourcePanel.tsx` für Tree-Pattern)
+- **Inline-Icons pro Folder-Zeile:** Delete (🗑), Add Subfolder (+), Rename (✏) -- direkt sichtbar, kein Kontextmenü nötig
+- **Drag-and-Drop:** Folders und Files können per Drag auf andere Folders verschoben werden
+- Visuelles Feedback: Drop-Target-Highlighting beim Hover über gültiges Ziel
+- Wiederverwendbar in der Dateien-Seite UND im Workspace-Chat (gleiche Komponente)
+
+**FileTable (bestehend, erweitert)**
+- Zeigt Dateien des aktuell markierten Folders
+- Neue Spalte "Quelle" (zeigt `featureInstanceLabel` als Information, nicht als Gruppierung)
+- **Drag-and-Drop:** Dateien aus der Tabelle in den Folder-Tree ziehen zum Verschieben
+- Kontextmenü pro Datei: Umbenennen, Löschen, Kopieren, Verschieben, Download
+
+**SplitView / Divider**
+- Verschiebbar per Maus-Drag
+- Default-Position: 25% links / 75% rechts
+- Minimum-Breite pro Seite: 200px
+- Position persistent im LocalStorage
+
+### 4.3 Workspace-Integration
+
+Im Workspace-Chat (`WorkspacePage.tsx`) wird der bestehende `FileBrowser` umgebaut:
+- Statt Gruppierung nach `featureInstanceId`: der gleiche `FolderTree` wie auf der Dateien-Seite
+- Kompaktere Darstellung (kein Split-View nötig, Tree + Dateien untereinander)
+- Gleiche Drag-and-Drop-Fähigkeiten
+
+---
+
+## 5. Kritische Bewertung
+
+### 5.1 executeCode -- Sicherheitskonzept
+
+**Anforderung:** "Hier einfach nur für Analysen und Berechnungen zulassen, nicht für Codeelemente ausserhalb der Umgebung oder Installationen."
+
+**Bewertung:** Sinnvoll und notwendig für Datenanalyse. Die Einschränkung auf Analysen/Berechnungen ist korrekt.
+
+**Empfohlene Absicherung:**
+- **Isolierte Ausführung:** RestrictedPython (Python) oder vm2/isolated-vm (JS) -- keine eigene Docker-Sandbox nötig für reine Berechnungen
+- **Kein Dateisystem-Zugriff:** `open()`, `os`, `subprocess`, `sys` blockieren
+- **Kein Netzwerk-Zugriff:** `socket`, `urllib`, `requests` blockieren
+- **Keine Installationen:** `pip`, `import` nur für Whitelist (math, statistics, json, csv, re, datetime, collections, itertools, functools, decimal, fractions)
+- **Timeout:** Max. 30 Sekunden Ausführungszeit
+- **Memory-Limit:** Max. 256 MB
+- **Output-Limit:** Max. 50.000 Zeichen Rückgabe
+
+**Risiko-Bewertung:** Mittel. RestrictedPython ist gut getestet, aber kein perfekter Sandbox. Für den Use Case (Berechnungen, Datenanalyse) ist das Risiko vertretbar, solange die Whitelist strikt eingehalten wird.
+
+### 5.2 editFile -- Logik aus Codeeditor übernehmen
+
+**Anforderung:** "Bestehende Datei-Inhalte aktualisieren."
+
+**Bewertung:** Das Codeeditor-Feature hat eine bewährte File-Edit-Pipeline (`applyEdit`-Route, `createFileData(fileId, contentBytes)`). Da der Codeeditor später entfernt wird, muss die benötigte Logik in den Workspace-Agent **kopiert** werden -- keine Referenzen auf Codeeditor-Module.
+
+**Zu übernehmende Logik (aus Codeeditor extrahieren):**
+- File-Write: `createFileData(fileId, contentBytes)` zum Überschreiben bestehender Dateien
+- Falls `createFileData` bei existierenden Daten überspringt: `deleteFileData` → `createFileData` als Fallback
+- MIME-Type-Erkennung: `_guessMimeType(fileName)` Logik kopieren
+- FileItem-Metadaten (fileSize, fileHash) via `updateFile` aktualisieren
+- MIME-Type-Check: Nur Text-basierte Dateien erlauben (text/\*, application/json, etc.)
+- Knowledge-Store re-indexieren nach Edit (bestehende Pipeline nutzen)
+
+**Zu übernehmende UX-Logik (aus Codeeditor extrahieren):**
+- `FileEditProposal`-Datenmodell (`EditStatusEnum`: PENDING → ACCEPTED / REJECTED) in Agent-Datenmodelle kopieren
+- `file_edit_proposal` SSE-Event-Pattern in den Workspace-Agent übernehmen
+- Diff-View-Komponente (`DiffPreviewPanel.tsx`) als eigenständige Shared-Komponente extrahieren
+
+**Optionaler Approve/Reject-Mode:**
+- Agent emittiert `file_edit_proposal` SSE-Event mit Old/New-Content
+- Frontend zeigt Diff-View (extrahierte Shared-Komponente)
+- User akzeptiert oder lehnt ab
+- Konfigurierbar: Auto-Apply für kleine Edits, Approve für Dateien > 1 KB
+
+**Race-Condition-Risiko:** Gering. Benutzerbezogen, sequenzieller Agent-Zugriff.
+
+### 5.3 copyFile -- Vollständige Kopie
+
+**Anforderung:** "Datei im Workspace duplizieren."
+
+**Bewertung:** Korrekt. Nur File-Level-Kopie sinnvoll. FileData **muss** vollständig dupliziert werden, damit die Kopie unabhängig bearbeitet werden kann (z.B. via `editFile`).
+
+**Empfehlung:**
+- Nur einzelne Dateien kopieren, keine ganzen Ordner
+- **Vollständige Duplikation:** FileItem UND FileData werden als eigenständige Einträge mit neuer ID erstellt. Keine Shared-Referenz auf dieselben Daten, da die Kopie editierbar sein muss.
+- Ordner-Kopie als eigenes Feature für später (komplexer wegen Rekursion und Namenskonflikten)
+
+### 5.4 createCalendarEvent -- Nicht implementierbar
+
+**Anforderung:** "Kalender-Eintrag erstellen."
+
+**Bewertung: Noch nicht umsetzbar.** Calendar-API (Microsoft Graph `/calendar/events`) ist nicht gebaut.
+
+**Empfehlung:** Aus dem aktuellen Scope entfernen. Als separates Feature planen:
+1. Microsoft Graph Calendar-Endpoints in `connectorMsft.py` implementieren
+2. Workflow-Actions für Calendar erstellen (`outlook.createEvent`, `outlook.listEvents`)
+3. Dann als Agent-Tool exponieren
+
+**Bestandsaufnahme Mail-Operationen (für Kontext):**
+
+| Operation | Microsoft Outlook | Google Gmail |
+|-----------|-------------------|--------------|
+| Mail-Ordner auflisten | `OutlookAdapter.browse(me/mailFolders)` | `GmailAdapter.browse(labels)` |
+| Mails in Ordner lesen | `OutlookAdapter.browse(me/mailFolders/{id}/messages)` | `GmailAdapter.browse(messages?labelIds=)` |
+| Mail herunterladen | `OutlookAdapter.download(me/messages/{id})` | **Stub** (gibt `b""` zurück) |
+| Mails suchen | `OutlookAdapter.search(me/messages?$search=)` | `GmailAdapter.search(users/me/messages?q=)` |
+| Mail senden | `OutlookAdapter.sendMail(me/sendMail)` | **Nicht implementiert** |
+| Draft erstellen | `composeAndDraftEmailWithContext` (Workflow) | **Nicht implementiert** |
+| Draft senden | `sendDraftEmail` (Workflow) | **Nicht implementiert** |
+| Agent-Tool `sendMail` | Implementiert (nur Outlook trotz Doku "Outlook, Gmail") | **Nicht unterstützt** |
+
+**Fazit Mail:** Microsoft Outlook ist vollständig integriert (lesen, suchen, senden, drafts). Google Gmail hat nur Lesen/Suchen (readonly Scopes). Gmail-Senden, Drafts und Download sind nicht implementiert -- bei Bedarf separates Feature.
+
+### 5.5 featureInstanceId-Entfernung
+
+**Anforderung:** "Die Strukturierung der Daten nach deren Creation Feature Instanz entfernen wir. Dazu eine zusätzliche Spalte ergänzen."
+
+**Bewertung:** Guter Ansatz. Die Ordnerstruktur ersetzt die Feature-Instanz-Gruppierung.
+
+**Empfehlung:**
+- `featureInstanceId` auf `FileItem` beibehalten (nicht aus dem Modell entfernen)
+- Nicht mehr für Gruppierung nutzen, nur als informatives Metadatum
+- Neue Tabellenspalte "Quelle" zeigt den Feature-Instanz-Namen
+- Kein Datenmigrations-Aufwand nötig -- bestehende Dateien bleiben im Root und können manuell in Ordner verschoben werden
+- Die Zuordnung `featureInstanceId` bei neuen Dateien weiterhin setzen (Audit-Trail)
+
+### 5.6 Root "(Global)" -- Multi-Mandanten-Implikation
+
+**Anforderung:** "Der Root ist der Folder '(Global)'."
+
+**Bewertung:** Konsistent mit dem bestehenden System. Dateien sind bereits benutzerbezogen (`_createdBy` Filter). Der Root-Scope ist pro User, nicht systemweit.
+
+**Empfehlung:**
+- Root ist virtuell (kein DB-Eintrag), repräsentiert `folderId=None`
+- Dateien sind weiterhin pro User isoliert (bestehende RBAC-Filter)
+- Label "(Global)" im UI zeigt dem User, dass dies sein persönlicher Root ist
+- Für Multi-Mandanten: Dateien haben `mandateId`, Filter bleibt bestehen
+
+### 5.7 searchImages -- Abhängigkeit und Integration
+
+**Anforderung:** Aus `doc_enhancement_web_image_actions_pending.md` ein Tool ableiten.
+
+**Bewertung:** Das bestehende Konzept ist solide (Google Custom Search API, `WEB_SEARCH_MEDIA` Operation Type). Für den Agent-Tool-Kontext reicht eine vereinfachte Version.
+
+**Empfehlung:**
+- Das Agent-Tool `searchImages` als Wrapper um die neue `WEB_SEARCH_MEDIA`-Operation implementieren
+- Ergebnisse als Liste von URLs zurückgeben (der Agent kann dann `downloadFromDataSource` oder einen neuen Mechanismus nutzen, um Bilder herunterzuladen)
+- Google API Key als Voraussetzung klar dokumentieren
+- Fallback: Wenn kein Google API Key konfiguriert, Tool nicht registrieren
+
+### 5.8 Dokument-Rendering mit Bildern
+
+**Anforderung:** Können professionelle Berichte (DOCX, XLSX, PPTX, PDF, HTML) mit eingebetteten Bildern generiert werden?
+
+**Bewertung: Ja, alle 5 Renderer unterstützen Bilder bereits vollständig.**
+
+| Renderer | Pfad | Bild-Methode | Bibliothek |
+|----------|------|-------------|------------|
+| DOCX | `renderers/rendererDocx.py` | `_renderJsonImage()` → `doc.add_picture()` | python-docx |
+| XLSX | `renderers/rendererXlsx.py` | `_addImageToExcel()` → `sheet.add_image()` | openpyxl |
+| PPTX | `renderers/rendererPptx.py` | `_addImagesToSlide()` → `slide.shapes.add_picture()` | python-pptx |
+| PDF | `renderers/rendererPdf.py` | `_renderJsonImage()` → `ReportLabImage` | reportlab |
+| HTML | `renderers/rendererHtml.py` | `_renderJsonImage()` → `
` | native |
+
+**Gemeinsames Bild-Format (alle Renderer):**
+```json
+{
+ "type": "image",
+ "content": {
+ "base64Data": "",
+ "altText": "Bildbeschreibung",
+ "caption": "Optionale Bildunterschrift"
+ }
+}
+```
+
+**Basis-Klasse** (`documentRendererBaseTemplate.py`): Bietet `_validateImageData()`, `_getImageDimensions()`, `_resizeImageIfNeeded()` als Shared-Funktionen.
+
+**Fazit:** Kein Handlungsbedarf für Bild-Rendering. Der Agent kann Bilder (z.B. via `generateImage` oder `searchImages` + Download) als Base64-Daten in das Dokument-Schema einbetten, und alle Renderer verarbeiten diese korrekt.
+
+### 5.9 neutralizeData -- Scope
+
+**Anforderung:** "Text/Datei anonymisieren."
+
+**Bewertung:** `serviceNeutralization` existiert, ist aber Feature-spezifisch (Neutralization Feature). Als Agent-Tool muss es Feature-unabhängig aufrufbar sein.
+
+**Empfehlung:**
+- Read-Only: Gibt anonymisierten Text zurück, ändert das Original nicht
+- Für Dateien: Temporär extrahieren, neutralisieren, Ergebnis als neuen Text zurückgeben
+- Feature-Abhängigkeit prüfen: Wenn Neutralization-Feature nicht lizenziert, Tool nicht registrieren
+
+---
+
+## 6. Zusammenfassung und Priorisierung
+
+### Tool-Übersicht nach Aufwand und Nutzen
+
+| Tool | Aufwand | Nutzen | Priorität | Phase |
+|------|---------|--------|-----------|-------|
+| `deleteFile` | Niedrig | Hoch | **Must** | 1 |
+| `renameFile` | Niedrig | Hoch | **Must** | 1 |
+| `readUrl` | Niedrig | Hoch | **Must** | 1 |
+| `translateText` | Niedrig | Mittel | **Should** | 1 |
+| `deleteFolder` | Mittel | Hoch | **Must** | 2 |
+| `renameFolder` | Niedrig | Mittel | **Should** | 2 |
+| `moveFolder` | Mittel | Mittel | **Should** | 2 |
+| `copyFile` | Niedrig | Mittel | **Should** | 2 |
+| `editFile` | Niedrig (Codeeditor-Logik vorhanden) | Hoch | **Must** | 2 |
+| `speechToText` | Niedrig | Mittel | **Should** | 3 |
+| `detectLanguage` | Niedrig | Niedrig | **Could** | 3 |
+| `searchImages` | Hoch | Mittel | **Should** | 3 |
+| `neutralizeData` | Mittel | Niedrig | **Could** | 3 |
+| `executeCode` | Hoch | Hoch | **Should** | 3 |
+| `createCalendarEvent` | Hoch | Mittel | **Won't** (vorerst) | - |
+
+### Geschätzter Gesamtaufwand
+
+| Bereich | Aufwand |
+|---------|---------|
+| Phase 1: Quick-Win Tools | 2-3 Tage |
+| Phase 2: Dateisystem-Backend + Tools | 4-5 Tage |
+| Phase 3: Erweiterte Tools | 5-7 Tage |
+| UI: Dateien-Seite Split-View | 3-4 Tage |
+| UI: Folder-Tree Komponente | 2-3 Tage |
+| UI: Workspace-Integration | 1-2 Tage |
+| **Gesamt** | **17-24 Tage** |
+
+---
+
+## 7. Referenzen
+
+| Dokument | Pfad |
+|----------|------|
+| AI Agent Architecture | `wiki/concepts/AI-Agent-Architecture-Konzept.md` |
+| Web Image Search Konzept | `local/pending/doc_enhancement_web_image_actions_pending.md` |
+| Codeeditor (Logik extrahieren, Feature wird entfernt) | `gateway/modules/features/codeeditor/routeFeatureCodeeditor.py` |
+| Codeeditor Datenmodell (extrahieren) | `gateway/modules/features/codeeditor/datamodelCodeeditor.py` |
+| Codeeditor Response Parser (extrahieren) | `gateway/modules/features/codeeditor/responseParser.py` |
+| Codeeditor DiffPreview (als Shared-Komponente extrahieren) | `frontend_nyla/src/pages/views/codeeditor/DiffPreviewPanel.tsx` |
+| Dokument-Renderer (Bild-Support) | `gateway/modules/serviceCenter/services/serviceGeneration/renderers/` |
+| FileItem Datenmodell | `gateway/modules/datamodels/datamodelFiles.py` |
+| FileFolder Datenmodell | `gateway/modules/datamodels/datamodelFileFolder.py` |
+| DB-Operationen | `gateway/modules/interfaces/interfaceDbManagement.py` |
+| Agent Tools | `gateway/modules/serviceCenter/services/serviceAgent/mainServiceAgent.py` |
+| Tool Registry | `gateway/modules/serviceCenter/services/serviceAgent/toolRegistry.py` |
+| Frontend FileBrowser | `frontend_nyla/src/pages/views/workspace/FileBrowser.tsx` |
+| Frontend FilesPage | `frontend_nyla/src/pages/basedata/FilesPage.tsx` |
+| DataSourcePanel (Tree-Referenz) | `frontend_nyla/src/pages/views/workspace/DataSourcePanel.tsx` |
diff --git a/concepts/Commcoach-Voice-Recording-Streaming-Konzept.md b/concepts/Commcoach-Voice-Recording-Streaming-Konzept.md
new file mode 100644
index 0000000..df939f7
--- /dev/null
+++ b/concepts/Commcoach-Voice-Recording-Streaming-Konzept.md
@@ -0,0 +1,334 @@
+# CommCoach Voice Recording Streaming Konzept
+
+## Ausgangslage
+
+Die aktuelle CommCoach-Spracheingabe basiert auf Browser `SpeechRecognition` (`webkitSpeechRecognition`).
+Auf Mobile-Browsern wird die Aufnahme typischerweise alle ca. 5-7 Sekunden automatisch beendet und neu gestartet.
+Das ist kein sauber behebbarer App-Bug, sondern eine Browser/API-Limitation.
+
+## Zielbild
+
+Die Spracheingabe soll auf Mobile und Desktop stabil laufen, ohne erzwungene 5-Sekunden-Resets, ohne Textverlust zwischen Restart-Gaps und ohne MSFT-Abhängigkeiten.
+
+## Antwort auf die Kernfragen
+
+### 1) Ist Option 3 ein genereller Umbau oder nur Mobile?
+
+Beides ist möglich:
+
+- **Variante A (Mobile-only Umbau):**
+ Neue Audio-Streaming-Pipeline nur auf Mobile aktivieren, Desktop bleibt vorerst bei Browser STT.
+- **Variante B (Genereller Umbau):**
+ Neue Pipeline für alle Clients (Mobile + Desktop), einheitliches Verhalten und weniger Wartung.
+
+Empfehlung: **Variante B**, da nur ein Stack gepflegt werden muss und CommCoach-Logik konsistent bleibt.
+
+### 2) Kann das komplett in unserer Plattform umgesetzt werden?
+
+Ja. Vollständig in eigener Plattform ist möglich.
+
+Notwendig sind:
+
+- Eigene UI- und Gateway-Implementierung (WebSocket Streaming)
+- Eigener STT-Service (self-hosted), z. B. `faster-whisper`
+- Optional eigene VAD (Voice Activity Detection), z. B. `webrtcvad`
+
+Nicht zwingend notwendig:
+
+- Externe SaaS-STT Provider
+- MSFT/Azure Speech
+
+## Zielarchitektur (MSFT-frei)
+
+### UI (Frontend CommCoach)
+
+- Mikrofonaufnahme via `MediaStream` + `AudioWorklet` oder `MediaRecorder`
+- Chunking (z. B. 100-250 ms)
+- Streaming per WebSocket an Gateway
+- Lokaler Live-Preview-Text kommt nicht mehr aus Browser-STT, sondern aus Server-Interims
+- Bestehende State-Machine bleibt als Steuerlogik erhalten (`idle`, `listening`, `botSpeaking`, `interrupted`, `muted` orthogonal)
+
+### Gateway
+
+- Neuer Endpoint, z. B. `ws /api/feature/commcoach/{instanceId}/stt/stream`
+- Sessiongebundene Stream-Verarbeitung
+- Weiterleitung der Audio-Chunks an STT-Worker
+- Rückkanal von Interims + Final-Text zum UI
+- Serverseitige Segmentierung (Silence / VAD) statt Browser `onend`
+
+### STT-Service (self-hosted)
+
+- `faster-whisper` als Runtime (GPU empfohlen, CPU möglich)
+- Sprachen konfigurierbar (z. B. `de`, `en`)
+- Ausgabe:
+ - `interim` Events (optional, je nach Latenzbudget)
+ - `final` Segmente
+ - Zeitmarken für Debug und Nachvollziehbarkeit
+
+### Optional: VAD-Service
+
+- Entweder im Gateway oder STT-Worker
+- Trigger für Segment-Ende statt fester 1s-Timer
+- Stabiler als browserseitige `onspeechstart/onspeechend`
+
+## Ist-Analyse der bestehenden Codebase (UI + Gateway)
+
+### Frontend (`frontend_nyla`)
+
+- `src/pages/views/commcoach/useVoiceController.ts`
+ - Nutzt Browser `SpeechRecognition` direkt.
+ - Auto-Restart bei `onend` mit `REC_AUTORESTART_DELAY_MS = 300`.
+ - `SILENCE_TIMEOUT_MS = 1000` finalisiert User-Text.
+ - Mobile-Problem entsteht hier durch Browser-`onend`-Zwang (alle ~5-7s).
+
+- `src/pages/views/commcoach/CommcoachDossierView.tsx`
+ - Bindet Voice-State-Machine an UI/TTS.
+ - `voice.liveTranscript` wird als Live-User-Bubble angezeigt.
+ - Debuglog existiert bereits via `window.__dlog`.
+
+- `src/hooks/useCommcoach.ts`
+ - Text läuft über `sendMessageStreamApi` (SSE).
+ - Audio läuft derzeit nur als One-Shot Blob über `sendAudioStreamApi` (SSE).
+ - Kein echtes bidirektionales Low-Latency Audio-Streaming.
+
+- `src/api/commcoachApi.ts`
+ - Bestehende Endpunkte sind HTTP + SSE.
+ - Es gibt noch keinen WebSocket-Endpunkt für chunkweises Audio.
+
+### Gateway (`gateway`)
+
+- `modules/features/commcoach/routeFeatureCommcoach.py`
+ - `POST .../message/stream` (SSE) für Text.
+ - `POST .../audio/stream` (SSE) für Audio-One-Shot.
+ - Audio-Endpoint liest `await request.body()` komplett und startet danach STT.
+
+- `modules/features/commcoach/serviceCommcoach.py`
+ - `processAudioMessage(...)`: STT auf gesamtem Blob, danach `processMessage(...)`.
+ - Keine Segment-/Chunk-Logik für laufende Erkennung.
+
+- `modules/interfaces/interfaceVoiceObjects.py`
+ - Kapselt STT/TTS aktuell über Google Connector.
+ - API ist bereits zentralisiert und damit gut erweiterbar.
+
+- `modules/connectors/connectorVoiceGoogle.py`
+ - Verwendet `recognize` auf Einzeldateien (nicht Streaming).
+ - Damit strukturell ungeeignet für kontinuierliche Mobile-Spracheingabe.
+
+## Implementierung im bestehenden System (konkret)
+
+### Ziel: bestehende CommCoach-Pipeline beibehalten, nur STT-Eingang ersetzen
+
+Unverändert bleiben:
+- Session- und Message-Processing in `serviceCommcoach.py` (`processMessage`, Events, TTS).
+- UI-State-Machine-Fachlogik (`idle`, `listening`, `botSpeaking`, `interrupted`, `muted`).
+
+Ersetzt wird:
+- Browser-STT (`SpeechRecognition`) durch Streaming-STT Provider.
+- One-Shot Audio-Upload durch WebSocket-Audio-Stream.
+
+### A) Frontend Änderungen
+
+1. Neue API-Funktion in `src/api/commcoachApi.ts`
+ - `openSttStreamApi(instanceId, sessionId, handlers, options)` via WebSocket.
+ - Handler: `onStatus`, `onInterim`, `onFinal`, `onError`, `onClose`.
+
+2. Neues Hook `src/hooks/useAudioStreamTranscription.ts`
+ - Mikrofon aufnehmen (`MediaStream` + `AudioWorklet` oder `MediaRecorder`).
+ - Audio in 100-250ms Chunks an WS senden.
+ - Event-Rückkanal verarbeiten (interim/final/status/error).
+ - Reconnect-Mechanismus für Mobile-Netzwechsel.
+
+3. `src/pages/views/commcoach/useVoiceController.ts` refactor
+ - Provider-Interface einführen:
+ - `browserSpeech` (legacy)
+ - `streamedStt` (neu)
+ - `transcriptPartsRef` und `liveTranscript` aus Serverevents speisen.
+ - `SILENCE_TIMEOUT_MS` nur noch als optionales Guard-Rail, nicht als Kernsegmentierung.
+
+4. `src/pages/views/commcoach/CommcoachDossierView.tsx`
+ - Keine Verhaltensänderung in Tabs/State nötig.
+ - Nur Provider initialisieren und vorhandene Debuganzeige um STT-WS Events erweitern.
+
+### B) Gateway Änderungen
+
+1. Neue WS-Route in `modules/features/commcoach/routeFeatureCommcoach.py`
+ - Beispiel: `ws /api/commcoach/{instanceId}/sessions/{sessionId}/stt/stream`
+ - Ownership-/Session-Checks analog zu `message/stream`.
+ - WebSocket akzeptieren, Audio-Chunks entgegennehmen, Events zurücksenden.
+
+2. Neuer Service `modules/features/commcoach/serviceCommcoachSttStream.py`
+ - Sessiongebundene Streamverwaltung.
+ - Chunk-Buffer, VAD/Silence-Regeln, Segmentbildung.
+ - Für jedes finale Segment: `processMessage(sessionId, contextId, finalText, interface)` aufrufen.
+
+3. Anpassung `modules/features/commcoach/serviceCommcoach.py`
+ - `processAudioMessage` bleibt für Legacy/Upload kompatibel.
+ - Neue Streaming-Nutzung erfolgt über den neuen STT-Stream-Service.
+
+4. Erweiterung `modules/interfaces/interfaceVoiceObjects.py`
+ - Neue Methoden ergänzen:
+ - `startSttStream(...)`
+ - `pushSttAudioChunk(...)`
+ - `stopSttStream(...)`
+ - Bestehende Methoden (`speechToText`, `textToSpeech`) unverändert lassen.
+
+### C) STT Worker (self-hosted)
+
+1. Neuer Connector (z. B.) `modules/connectors/connectorVoiceWhisper.py`
+ - `faster-whisper` Integration.
+ - Modell, Sprache, VAD-Parameter konfigurierbar.
+
+2. Optional separater Worker-Prozess
+ - Entkoppelt Gateway-Latenz von STT-Rechenlast.
+ - Kommunikation intern über Queue/IPC oder internen WS.
+
+## WS Event Contract (verbindlich)
+
+### Client -> Server
+
+- `open`
+ - `{ "type": "open", "sessionId": "...", "language": "de-DE", "codec": "pcm16" }`
+- `audio`
+ - `{ "type": "audio", "seq": 123, "chunk": "" }`
+- `commit`
+ - `{ "type": "commit", "reason": "silence|manual|stateChange" }`
+- `close`
+ - `{ "type": "close" }`
+
+### Server -> Client
+
+- `status`
+ - `{ "type": "status", "label": "Sprache wird erkannt..." }`
+- `interim`
+ - `{ "type": "interim", "text": "...", "segmentId": "..." }`
+- `final`
+ - `{ "type": "final", "text": "...", "segmentId": "...", "confidence": 0.0 }`
+- `ack`
+ - `{ "type": "ack", "seq": 123 }`
+- `error`
+ - `{ "type": "error", "code": "stt_failed", "message": "..." }`
+- `closed`
+ - `{ "type": "closed", "reason": "client|server|timeout" }`
+
+## Migrationsstrategie (ohne doppelte Logikfalle)
+
+1. Feature-Flag `commcoachVoiceProvider` auf Instanzebene
+ - Werte: `browserSpeech` | `streamedStt`.
+
+2. Rolloutpfad
+ - Schritt 1: intern auf Mobile aktivieren.
+ - Schritt 2: nach Stabilisierung global aktivieren.
+ - Schritt 3: Browser-STT-Code entfernen.
+
+3. Expliziter Fehlerpfad
+ - Kein stiller Fallback auf Browser-STT, wenn `streamedStt` aktiv ist.
+ - Fehler wird sichtbar im UI (Banner + Debuglog).
+
+## Konkrete Taskliste pro Repository
+
+### Repo `frontend_nyla`
+
+1. `commcoachApi.ts`: `openSttStreamApi` ergänzen.
+2. Neues Hook `useAudioStreamTranscription.ts` implementieren.
+3. `useVoiceController.ts`: Provider-Abstraktion + streamed Provider integrieren.
+4. `CommcoachDossierView.tsx`: Provider-Init + Debug-Events anzeigen.
+5. Tests:
+ - unit: Segment-Assembler
+ - integration: state transitions bei interim/final/error
+
+### Repo `gateway`
+
+1. WS-Route für STT-Stream in `routeFeatureCommcoach.py`.
+2. Neuer Stream-Service `serviceCommcoachSttStream.py`.
+3. `interfaceVoiceObjects.py` um Streaming-Methoden erweitern.
+4. Neuer Whisper-Connector `connectorVoiceWhisper.py`.
+5. Telemetrie + Logs:
+ - stream open/close
+ - first interim latency
+ - first final latency
+ - reconnect count
+
+## Variante A vs. Variante B
+
+| Kriterium | Variante A: Nur Mobile | Variante B: Alle Plattformen |
+|---|---|---|
+| Time-to-first-fix | schneller | mittel |
+| Komplexität gesamt | höher langfristig (2 Stacks) | niedriger langfristig (1 Stack) |
+| UX-Konsistenz | unterschiedlich je Device | einheitlich |
+| Wartung | doppelt | einfach |
+| Risiko | mittelhoch (Divergenz) | mittel (einmaliger Umbau) |
+
+**Empfehlung:** Variante B.
+
+## Implementierungsplan
+
+### Phase 1: Infrastruktur und Prototyp
+
+1. STT-Worker als eigener Service im Gateway-Umfeld bereitstellen
+2. Streaming-Protokoll definieren (Events: `audio`, `interim`, `final`, `status`, `error`, `close`)
+3. WS-Route im Gateway für CommCoach implementieren
+4. Test-Client mit Beispielaudio aufbauen (ohne UI) zur Last-/Latenzprüfung
+
+### Phase 2: Frontend Integration
+
+1. Neues Modul `useAudioStreamTranscription` einführen
+2. `useVoiceController` auf Provider-Abstraktion umstellen:
+ - Provider `browserSpeech` (bestehend)
+ - Provider `streamedStt` (neu)
+3. Transcript-Handling auf Serverevents umstellen:
+ - Interim in `liveTranscript`
+ - Final in `transcriptParts`
+4. State-Transitions unverändert belassen, nur STT-Quelle ersetzen
+
+### Phase 3: Segmentierung und Qualität
+
+1. Serverseitige VAD aktivieren
+2. Segment-Ende sauber in `onMessage` überführen
+3. Doppelungen/Fragmentverluste mit Session-IDs und Segment-Counter verhindern
+4. Mobile Netzwechsel/WS-Reconnect robust behandeln
+
+### Phase 4: Rollout
+
+1. Feature-Flag: zuerst intern, dann Pilotmandanten
+2. Optionale Stufen:
+ - Stufe 1: Nur Mobile
+ - Stufe 2: Alle Plattformen
+3. Browser-STT nach Stabilitätsphase vollständig entfernen
+
+## Technische Leitplanken
+
+- Keine stillen Fallbacks, die Fehler verdecken
+- Explizite Fehlerzustände im UI (Mikrofon, Netzwerk, STT nicht verfügbar)
+- Klares Telemetrie-Set:
+ - Time to first interim
+ - Time to first final
+ - Segment-Länge
+ - Abbruchgründe
+ - WS-Reconnect-Rate
+
+## Drittkomponenten
+
+### Minimal erforderlich
+
+- Python Packages (self-hosted):
+ - `faster-whisper`
+ - optional `webrtcvad`
+ - Audio-Decode (`ffmpeg` Laufzeit)
+
+### Nicht erforderlich
+
+- MSFT/Azure Speech
+- Externe STT-SaaS
+
+## Risiken und Gegenmassnahmen
+
+- **GPU-Kapazität fehlt:** zunächst kleines Modell und Queueing, später GPU-Skalierung
+- **Latenz zu hoch:** Chunk-Grösse reduzieren, VAD feinjustieren, Modellwahl anpassen
+- **Mobile Netz instabil:** robustes Reconnect-Handling + Segment-ACKs
+- **Drift zwischen UI und Backend:** eindeutige Stream-/Segment-IDs und Idempotenzregeln
+
+## Fazit
+
+Der Umbau ist vollständig in der eigenen Plattform machbar und löst das Mobile-5-Sekunden-Problem an der Wurzel.
+Für Wartbarkeit und konsistentes Verhalten ist ein **genereller Umbau (Variante B)** sinnvoll.
+Ein stufenweiser Rollout mit Feature-Flag minimiert Risiko.
diff --git a/deployment/poweron_sec.kdbx b/deployment/poweron_sec.kdbx
index c1d6c6a45680f1169060323219c89f1005bef301..ea65af58a4fb0ab2e6f439ade9519f9b8fa18df4 100644
GIT binary patch
literal 18878
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⪻?YyDu@XWgQpgucJ4B<`3`%sdU@N_x~YoF;yz
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