From 3a2d004558fbad67e6fb6bc597c098b8a67991d5 Mon Sep 17 00:00:00 2001 From: Sven Carstensen Date: Mon, 2 Nov 2026 16:30:45 +0100 Subject: [PATCH] Agenten-Leitfaden hinzufuegen --- AGENTS.md | 205 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 205 insertions(+) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..59486c4 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,205 @@ +# SwyxTray – Leitfaden für Coding-Agenten + +## Projektüberblick + +SwyxTray ist eine Windows-Desktop-Anwendung ohne Hauptfenster. Sie läuft als normale Benutzeranwendung in derselben interaktiven Windows-Sitzung wie SwyxIt!, verbindet sich über das Swyx Client SDK mit dem bereits laufenden Client Line Manager (`CLMgr.exe`) und zeigt den Telefoniezustand als farbiges Symbol im Infobereich an. + +Zusätzlich stellt die Anwendung einen JSON-WebSocket-Zugang für Browser bereit. Darüber lassen sich unter anderem Anrufe steuern, Zustände und Anrufereignisse empfangen, Kontakte abfragen, Firefox-Tabs über ein externes Plugin verwalten sowie Gesprächsaufnahmen starten, stoppen und als WAV übertragen. Das aktuelle WebSocket-Protokoll ist Version 11. + +SwyxIt!, das Firefox-Plugin SwyxFFPlugin, SwyxWeb und SwyxTrans sind nicht Teil dieses Repositories. Die ausführliche fachliche und Protokolldokumentation steht in `README.md`. + +## Technologie und zentrale Konfiguration + +- C# mit .NET 10; die Anwendung zielt auf `net10.0-windows`, die Checks auf `net10.0`. +- Windows Forms mit `OutputType=WinExe`; sichtbar sind primär `NotifyIcon`, Kontextmenü, Sprechblasen und ein kleiner Einstellungsdialog. +- Einzige externe NuGet-Abhängigkeit in beiden Projekten: `Swyx.Client.ClmgrAPI` 14.21.0. Diese Version ist auf die verwendete SwyxIt!-Version 14.21 abgestimmt und sollte bei einem Client-Update mitgeführt werden. +- Das SDK stellt die COM-Interop-Typen aus `IpPbx.CLMgrLib` bereit. Es wählt die Interop-Assembly anhand von `$(Platform)` aus; `AnyCPU` ist daher nicht zulässig. +- Die Anwendung ist standardmäßig auf `x64`, Runtime Identifier `win-x64` und framework-abhängige Ausführung eingestellt. `x86` ist in `Platforms` aufgeführt, der vorhandene Runtime Identifier bleibt jedoch `win-x64`; x64 ist der dokumentierte und geprüfte Weg. +- Nullable Reference Types und Implicit Usings sind aktiviert. Globalisierung ist invariant. +- Es gibt keine `.sln`, kein `global.json`, kein `.editorconfig`, kein Paket-Lockfile und keine CI-Konfiguration im Repository. + +Wichtige Dateien: + +| Pfad | Aufgabe | +|---|---| +| `src/SwyxTray/SwyxTray.csproj` | Zielframework, WinForms, Plattform, Runtime und SDK-Paket | +| `src/SwyxTray/app.manifest` | `asInvoker`, Windows-10/11-Kompatibilität, PerMonitorV2-DPI und Long-Path-Unterstützung | +| `tests/SwyxTray.RecordingChecks/SwyxTray.RecordingChecks.csproj` | Eigenständiges Konsolenprogramm; bindet ausgewählte Produktionsdateien direkt ein | +| `.vscode/tasks.json` | Debug-/Release-Builds und Start in der Desktop-Sitzung | +| `.vscode/launch.json` | Direkter Start beziehungsweise Anhängen des Debuggers | +| `.vscode/start-desktop.ps1` | Temporäre geplante Aufgabe für den Start in der interaktiven Sitzung | +| `README.md` | Verhalten, WebSocket-Protokoll, Sicherheit, Betrieb und bisherige Prüfungen gegen den realen Client | + +## Codeorganisation und Laufzeitarchitektur + +### Anwendung und Oberfläche + +- `src/SwyxTray/Program.cs`: STA-Einstiegspunkt, sitzungslokale Einzelinstanz-Sperre (`Local\SwyxTray.SingleInstance`), `--verbose`/`-v`, COM-Sicherheitsinitialisierung und globale Fehlerprotokollierung. +- `src/SwyxTray/TrayApplicationContext.cs`: Anwendungsrumpf, Tray-Menü, Timer, Verbindungsaufbau, Einstellungen und Weitergabe von Zustandsänderungen an Oberfläche und WebSocket-Server. +- `src/SwyxTray/TrayIcons.cs`: Erzeugt und cached die farbigen Symbole zur Laufzeit; es gibt keine ICO-Ressourcen. +- `src/SwyxTray/SettingsDialog.cs`: Einstellung der Verzögerung, mit der Firefox bei eingehenden Rufen in den Vordergrund geholt wird. +- `src/SwyxTray/FirefoxWindow.cs`: Native Windows-Aufrufe zum Aktivieren des Firefox-Fensters nach der Tab-Aktivierung durch das Plugin. +- `src/SwyxTray/NativeMethods.cs`: COM-Sicherheit und Freigabe nativer Icon-Handles. +- `src/SwyxTray/Log.cs`: Schlankes, threadübergreifend gesperrtes Dateiprotokoll ohne Logging-Framework. + +### Swyx-/COM-Schicht (`src/SwyxTray/Swyx/`) + +- `SwyxClient.cs` kapselt CLMgr-Verbindung, Ereignissenke, Snapshots, Telefoniebefehle, Kontaktsuche, Telefonbuchcache, Aufnahmebefehle und COM-Lebenszyklus. +- `SwyxModels.cs` enthält `SwyxSnapshot`, `SwyxLineInfo`, `SwyxContact` und den aggregierten Zustand `SwyxOverallState`. +- `LineStateExtensions.cs` klassifiziert die SDK-Leitungszustände und liefert deutsche Anzeigetexte. +- `PubCLMgrMessages.cs` bildet die benötigten Werte aus dem C-Header `CLMgrPubTypes.h` nach. +- `CallRecordingTracker.cs` verfolgt erfolgreich an das SDK übergebene Aufnahmewünsche pro Leitung und `callId`. Dies ist ausdrücklich kein Nachweis, dass tatsächlich eine Audiodatei geschrieben wurde. + +Zentrale Invarianten: + +1. **Alle COM-Zugriffe bleiben im WinForms-STA-Thread.** Netzwerk- und Hintergrund-Threads dürfen CLMgr nicht direkt aufrufen. `UiDispatcher` und `CommandExecutor` schleusen Befehle mit einer Zeitgrenze von zehn Sekunden in den UI-Thread. +2. `NativeMethods.InitializeComSecurity()` muss vor dem ersten COM-Aufruf erfolgen, damit CLMgr Ereignisse in den Prozess zurückrufen kann. +3. Zustandsänderungen sind primär ereignisgesteuert. Bei jeder CLMgr-Meldung wird der vollständige Zustand neu gelesen. Ein Sicherheitszyklus läuft alle fünf Sekunden, bei fehlendem Ereignisrückkanal jede Sekunde. +4. COM-Objekte werden an den vorhandenen Stellen mit `Marshal.FinalReleaseComObject` freigegeben. Neue COM-Pfade müssen Ereignisabmeldung, Fehlerfälle und Freigabe ebenso behandeln. +5. CLMgr-Leitungen sind intern 0-basiert, im Tray-Menü und WebSocket-Protokoll dagegen 1-basiert. +6. Telefonbücher kommen als reine IDispatch-Objekte an und werden über `IEnumerable` sowie `dynamic`/Late Binding gelesen, nicht über die typisierten Telefonbuch-Interop-Schnittstellen. +7. Der Adresscache wird beim Verbinden und anschließend alle 60 Minuten neu gelesen. Bei Lesefehlern bleibt der letzte erfolgreiche Stand erhalten. + +### WebSocket-Schicht (`src/SwyxTray/Web/`) + +- `LocalWebSocketServer.cs`: Mehrere `TcpListener`, Verbindungsverwaltung und Broadcasts. Der Hauptport bedient Browser; ein zweiter Port ist ausschließlich für das Firefox-Plugin vorgesehen. +- `WebSocketHandshake.cs`: Manueller HTTP-Upgrade nach RFC 6455, einschließlich Token-, Origin- und Host-Prüfung. WebSocket-Frames, Maskierung, Ping/Pong und Close übernimmt `System.Net.WebSockets`. +- `WebSocketSession.cs`: Empfang und serialisiertes Senden einer Verbindung. Eingehende Textnachrichten sind auf 64 KiB begrenzt; Sendevorgänge sind durch eine Sperre serialisiert. Normale Kommandos werden pro Sitzung nacheinander verarbeitet. +- `CommandExecutor.cs`: Übersetzt Kommandos in `SwyxClient`- beziehungsweise Plugin-Aufrufe und validiert unter anderem Rufnummern und absolute HTTP(S)-URLs. +- `UiDispatcher.cs`: Übergabe von Arbeit an den UI-`SynchronizationContext`. +- `PluginTabChannel.cs`: Anfrage-/Antwortzuordnung für `list`, `open`, `close` und `activate` mit fünf Sekunden Antwortzeit. Der Plugin-Port erlaubt nur `tabresult` und `ping`. +- `Protocol.cs`: JSON-DTOs, Snapshot-Abbildung und Ableitung der Anrufereignisse. JSON verwendet Web-Defaults (camelCase) und lässt Nullwerte weg. +- `WebSocketConfig.cs`: Protokollversion, Konfiguration, Token-Erzeugung und Sicherheitsschalter. + +Bei Broadcasts wird zuerst der Snapshot und unmittelbar danach die daraus abgeleiteten `call`-Ereignisse gesendet. Ein Verbindungs- oder Serverausfall allein gilt nicht als nachgewiesenes Gesprächsende. + +### Aufnahme-Warteschlange (`src/SwyxTray/Recording/RecordingOutbox.cs`) + +`RecordingOutbox` verbindet SDK-Aufnahmeabschnitte mit neu erzeugten WAV-Dateien und hält deren Zustand persistent. Wichtige Regeln: + +- Manifeste liegen als UUID-benannte JSON-Dateien in der Warteschlange; Schreibvorgänge erfolgen über temporäre Dateien und anschließendes Ersetzen. +- Nur eindeutig zuordenbare, neue, geschlossene WAVs mit stabiler Länge und gültiger RIFF/WAVE-Struktur werden übernommen. Die maximale Größe beträgt 500 MB. +- Die ursprüngliche SwyxIt!-Aufnahme wird weder verändert noch gelöscht; übertragen wird eine temporäre Kopie. +- Ein Aufnahmestopp allein gibt die Datei nicht frei. Halten oder ein COM-/Serverausfall gelten nicht als Gesprächsende. Erst ein gesunder Snapshot mit beendeter oder ersetzter Verbindung bestätigt das Ende. +- Offene Aufnahmen überstehen einen Neustart. Erst nach einer dauerhaft gespeicherten `recording.ack`-Bestätigung mit 32-stelliger hexadezimaler `jobId` wird die temporäre Kopie entfernt. +- `recording.download` überträgt binäre Blöcke mit `SWYX`-Magic, Kommando-ID und Offset (jeweils Int32 Little Endian) sowie höchstens 64 KiB Audiodaten. Die normale Empfangsschleife bleibt für Steuerkommandos verfügbar. + +## Bauen, Starten und Prüfen + +Alle Befehle werden aus dem Repository-Stamm ausgeführt. Vorausgesetzt werden Windows, ein .NET-10-SDK und für den realen Betrieb ein laufendes SwyxIt! in derselben Benutzersitzung. + +### Build + +```powershell +dotnet build src/SwyxTray/SwyxTray.csproj -p:Platform=x64 +dotnet build src/SwyxTray/SwyxTray.csproj -c Release -p:Platform=x64 +``` + +Die Debug-Ausgabe liegt unter: + +```text +src/SwyxTray/bin/x64/Debug/net10.0-windows/win-x64/SwyxTray.exe +``` + +`bin/` und `obj/` sind ignorierte Build-Artefakte und gehören nicht in Änderungen. + +### Automatische Checks + +```powershell +dotnet run --project tests/SwyxTray.RecordingChecks -p:Platform=x64 +``` + +Dies ist kein MSTest-/xUnit-/NUnit-Projekt und nicht mit `dotnet test` gleichzusetzen. `tests/SwyxTray.RecordingChecks/Program.cs` ist ein ausführbares Prüfprogramm mit eigenen `Check`-/`Reject`-Hilfen; jede nicht bestandene Bedingung wirft eine Ausnahme. Erfolg wird auf der Konsole mit der Anzahl bestandener Checks gemeldet. + +Die Checks umfassen derzeit: + +- Aufnahmezustand, idempotentes Starten, Fehlerfälle und veraltete `callId`-Werte; +- Snapshot-/Ereignisfelder und Unterscheidung von Gesprächsende, Halten und Verbindungsverlust; +- WAV-Erkennung, Dateisperren, unvollständige Dateien, Mehrdeutigkeit, Gesprächsende ohne lokalen Auflegebefehl und persistente Bestätigungen; +- echten Loopback-WebSocket-Binärtransport einschließlich Übertragungsabbruch, Wiederholung und parallelem `ping`. + +Das Testprojekt kompiliert Produktionsdateien direkt ein und ersetzt nur COM-Executor und Plugin-Kanal durch `TransportStubs.cs`. Temporäre WAVs und Warteschlangen liegen unter dem System-Tempverzeichnis und werden aufgeräumt. Es finden keine COM-Aufrufe oder Telefonate statt. + +Für einen manuellen Browser-Fixture-Server existiert zusätzlich: + +```powershell +dotnet run --project tests/SwyxTray.RecordingChecks -p:Platform=x64 -- --serve +``` + +Dieser Modus läuft dauerhaft auf Loopback-Port 17658 und stellt eine synthetische WAV bereit; er ist kein Bestandteil des normalen Prüflaufs. + +### Anwendung starten + +```powershell +dotnet build src/SwyxTray/SwyxTray.csproj -c Release -p:Platform=x64 +.\src\SwyxTray\bin\x64\Release\net10.0-windows\win-x64\SwyxTray.exe --verbose +``` + +`--verbose` protokolliert zusätzlich jede CLMgr-Meldung. Eine zweite Instanz in derselben Sitzung beendet sich wegen der Einzelinstanz-Sperre. + +In VS Code ist **„SwyxTray debuggen (Desktop-Sitzung)"** der geeignete Weg, besonders bei Remote-VS-Code. Die Aufgabe beendet zunächst eine laufende Instanz, baut Debug und startet die Anwendung über eine geplante Aufgabe im interaktiven Desktop. Der direkte Start kann bei einer Remote-Sitzung in Sitzung 0 landen; dort läuft der Prozess ohne sichtbares Tray-Symbol. + +### Verifizierungsumfang und manuelle Restprüfungen + +Bei Erstellung dieses Leitfadens wurden der Debug-Build und der normale Prüflauf ausgeführt: Build erfolgreich mit 0 Warnungen und 0 Fehlern, **204 recording/protocol checks passed (no COM calls)**. + +Die automatischen Checks ersetzen keine Prüfung mit realem SwyxIt!. COM-Aktivierung, Ereignisrückkanal, echte Zustandswechsel, Telefoniebefehle, tatsächliche Swyx-Aufnahmen, Tray-Darstellung und Firefox-Fokusverhalten erfordern eine interaktive Windows-Sitzung und gegebenenfalls kontrollierte Testanrufe. Keine realen Anrufe allein zur Verifikation auslösen. + +## Entwicklungskonventionen + +- Sprache der Dokumentation, UI und Protokollmeldungen ist Deutsch. Einige neuere Aufnahme- und Testdateien verwenden englische Kommentare; an der jeweiligen Datei orientieren. +- Vier Leerzeichen Einrückung, dateibezogene Namespaces, geschweifte Klammern und die vorhandenen Benennungsmuster beibehalten. Es gibt keinen separaten Formatter- oder Analyzer-Workflow. +- Asynchroner Netzwerkcode verwendet durchgehend `ConfigureAwait(false)` außerhalb des UI-Kontexts. Fortsetzungen werden bei bedarfsgesteuerten `TaskCompletionSource`-Instanzen asynchron ausgeführt. +- Ressourcen und Abonnements werden explizit aufgeräumt: `IDisposable`, CancellationTokens, Sendesperren, COM-Freigaben, Tray-Icons und Event-Handler beachten. +- Die vorhandene schlanke Architektur beibehalten und keine neuen Frameworks oder Abhängigkeiten ohne konkreten Bedarf einführen. +- Protokolländerungen müssen `WebSocketConfig.ProtocolVersion`, DTOs, Verhalten, Checks und `README.md` konsistent aktualisieren. Bestehende Felder wie `id`, `line`, `callId`, `recordingId`, Snapshot/Ereignis-Reihenfolge und das Binärformat sind öffentliche Schnittstellen. +- Neue Aufnahme- oder Transportregeln gehören in das ausführbare Check-Programm. Für gemeinsam genutzte Produktionsdateien muss gegebenenfalls auch die ``-Liste des Check-Projekts angepasst werden. +- Die im README verlinkte Datei `examples/browser-client.html` ist im aktuellen Repository nicht vorhanden. Nicht annehmen, dass ein Browserbeispiel oder das externe Plugin lokal verfügbar ist. + +## Laufzeitdaten und Konfiguration + +Die von SwyxTray selbst angelegten Laufzeitdaten liegen unter `%LOCALAPPDATA%\SwyxTray` (die ursprünglichen SwyxIt!-Aufnahmen bleiben dagegen im konfigurierten Swyx-Aufnahmeordner): + +- `swyxtray.log`: Dateiprotokoll, Rotation bei 1 MiB mit einer Sicherungsdatei `swyxtray.log.1`; +- `websocket.json`: WebSocket-Konfiguration; +- `recordings/`: persistente JSON-Manifeste und temporäre WAV-Kopien der Aufnahme-Warteschlange. + +Beim ersten Start wird `websocket.json` mit zufälligem Token erzeugt. Die aktuellen Standardwerte sind: + +- Hauptport `17654`, Plugin-Port `17655` (`0` deaktiviert den Plugin-Port); +- `Enabled=true`, `MaxConnections=8`; +- `AllowedOrigins=[]` und `AllowRemoteAccess=false`; +- `IncomingCallTabTitle="SwyxWeb"` und `IncomingCallFocusDelayMs=1000`. + +Eine defekte Konfiguration führt bewusst dazu, dass der Server nicht startet, statt still ein neues Token zu erzeugen. Änderungen an der JSON-Datei erfordern grundsätzlich einen Neustart. Die Fokusverzögerung kann über den Einstellungsdialog geändert werden; dieser speichert sofort in dieselbe geladene Konfiguration. Beim Speichern gehen manuell eingefügte JSON-Kommentare verloren. + +## Sicherheitsaspekte + +**Kritischer Ist-Zustand:** `WebSocketConfig.SecurityDisabled` ist in `src/SwyxTray/Web/WebSocketConfig.cs` auf `true` gesetzt – **in jedem Build**. Token-, Origin- und Host-Prüfung sind derzeit abgeschaltet. Wer den Haupt- oder Plugin-Port erreicht, kann die angebotenen Aktionen ausführen und Daten mitlesen. Dieser Zustand ist laut README ausschließlich für die Erprobung vorgesehen und darf nicht ausgeliefert werden. + +Standardmäßig bindet die Anwendung nur an `127.0.0.1` und `::1`. Mit `AllowRemoteAccess=true` bindet sie an alle IPv4-/IPv6-Schnittstellen. In Kombination mit den abgeschalteten Prüfungen kann dann grundsätzlich jeder erreichbare Netzwerkteilnehmer telefonieren und mitlesen. Vor einer Auslieferung müssen die Prüfungen wieder aktiviert und der gewünschte Bindingscope bewusst festgelegt werden. + +Bei aktivierter Sicherheit bestehen die vorgesehenen Schranken aus: + +- zufälligem Token, zeitkonstant verglichen, per Query-Parameter oder `token.…`-Unterprotokoll; +- Origin-Allowlist, da die Same-Origin-Policy WebSockets nicht schützt; +- Host-Prüfung gegen DNS-Rebinding bei Loopback-Betrieb; +- Loopback-Bindung beziehungsweise bewusster Fernzugriff mit Firewall-Konfiguration. + +Bei Fernzugriff entfällt die Host-Prüfung auch im abgesicherten Modus. Es gibt nur `ws://`, kein `wss://`; HTTPS-Seiten können entfernte unverschlüsselte WebSockets wegen Mixed-Content-Regeln gegebenenfalls nicht verwenden. + +Die Eingabevalidierung nicht abschwächen: Rufnummern werden bereinigt und begrenzt, Browser-URLs müssen absolutes HTTP(S) verwenden, JSON- und Headergrößen sind begrenzt, WAVs werden strukturell geprüft und Downloads/Bestätigungen benötigen den passenden persistenten Zustand. Token und Aufnahmeinhalte nicht unnötig in neue Protokollmeldungen oder Commits aufnehmen. + +Die Anwendung läuft mit `asInvoker` und benötigt im Normalbetrieb keine Administratorrechte. `CoInitializeSecurity` verwendet derzeit die dokumentierte, wenig restriktive COM-Ereignissenken-Konfiguration; Änderungen daran müssen gegen den realen CLMgr-Rückruf geprüft werden. Anwendungssteuerungsrichtlinien wie WDAC/Smart App Control müssen außerdem den von Swyx benötigten COM-Proxy `CLMgrPs64.dll` zulassen. + +## Deployment und Betrieb + +Der Standardbuild ist framework-abhängig und benötigt die .NET-10-Desktop-Laufzeit. Das README dokumentiert alternativ eine eigenständige Veröffentlichung: + +```powershell +dotnet publish src/SwyxTray/SwyxTray.csproj -c Release -r win-x64 --self-contained true -p:Platform=x64 +``` + +Es gibt weder Installer noch automatisierte Release-/Deployment-Pipeline im Repository. Für Autostart wird eine Verknüpfung zur EXE in `shell:startup` beziehungsweise `%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup` abgelegt. + +SwyxTray darf nicht einfach als Windows-Dienst betrieben werden: Dienste laufen in Sitzung 0, während SwyxIt!/CLMgr zur interaktiven Benutzersitzung gehören. Anwendung und Client müssen in derselben Sitzung und mit kompatiblem Integritätslevel laufen. Bei Netzwerkzugriff kann zusätzlich eine eingehende Windows-Firewall-Regel für den Haupt- und gegebenenfalls Plugin-Port erforderlich sein.