Both run on pinix, published only through the pinix Tailnet namespace and Traefik's web-internal entrypoint (unbound local-data + Headscale split DNS for intent./ereader.domain2). Live Intent reads its OpenAI key from the new pinix-only agenix secret. ereaderViewer is a path: flake input for now; the repo split plan moves it into its own repository on cgit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
105 lines
5.2 KiB
Markdown
105 lines
5.2 KiB
Markdown
# Live Transkript auf pinix
|
|
|
|
`intent.<domain2>` ist ein kleiner, ausschließlich im pinix-Tailnet erreichbarer
|
|
Sprachchat für Planungs-Gespräche. Er erzeugt keinen Intent, erstellt keine
|
|
Tickets und führt weder Codex-Aufträge noch andere Aktionen aus. Ein Gespräch
|
|
wird nach dem Auflegen als lokales Markdown-Transkript gespeichert; die spätere
|
|
Weiterverwendung im Codex-Abo ist bewusst ein manueller Schritt außerhalb dieser
|
|
Anwendung.
|
|
|
|
## Ablauf und Kosten
|
|
|
|
Der Browser verbindet sich per WebRTC direkt mit GPT-Live. Pinix tauscht nur das
|
|
SDP-Angebot mit `POST /v1/live/sessions` aus; der OpenAI-Schlüssel bleibt als
|
|
systemd-Credential auf dem Server. Der Browser wartet auf `session.started` und
|
|
sendet beim Beenden `session.close`. Nach `session.closed` speichert er die
|
|
empfangenen Transkriptabschnitte direkt lokal. Es gibt keinen separaten
|
|
`/api/draft`- oder Responses-Aufruf beim Abschluss und keinen Intent-Formatzwang.
|
|
Audio wird nicht aufgezeichnet.
|
|
|
|
Die Oberfläche schließt eine Session standardmäßig nach zehn Minuten. Das ist
|
|
eine Begrenzung für normale Browsernutzung, aber keine serverseitige
|
|
Abrechnungsgarantie bei manipulierten Clients. GPT-Live wird nach Dauer
|
|
abgerechnet. Die weiterhin aktivierte Responses-Delegation kann **während des
|
|
Gesprächs** separat kostenpflichtige API-Nutzung auslösen; das Speichern des
|
|
Transkripts selbst ruft kein Modell auf.
|
|
|
|
Der Live-Default ist `gpt-live-1`. `chatgpt-live-1` wurde in den überprüften
|
|
offiziellen Referenzen nicht als Live-Modell gefunden und wird deshalb nicht
|
|
still ersetzt. Beide Modelle bleiben über `services.liveIntent.liveModel` und
|
|
`services.liveIntent.backendModel` konfigurierbar. Der Default
|
|
`gpt-5.6-luna` für die in-session Responses-Delegation hat keine Tools und kann
|
|
keine Aktion ausführen.
|
|
|
|
## Speicherung und Fehlerverhalten
|
|
|
|
Neue Gespräche liegen mit restriktiven Rechten unter:
|
|
|
|
```
|
|
/var/lib/live-intent/transcripts/<YYYY-MM-DD-UUID>/transcript.md
|
|
```
|
|
|
|
Der Browser erzeugt die Gesprächs-ID beim Start und behält sie für einen
|
|
Speicherwiederholungsversuch. Der Server validiert ID und Inhalt, schreibt in
|
|
einen neuen Ordner atomar und gibt bei exakt demselben Retry dieselbe Datei
|
|
zurück, ohne eine zweite Datei anzulegen. Abweichender Inhalt für dieselbe ID
|
|
ergibt einen Konflikt statt eines Überschreibens. Der Download-Link ruft genau
|
|
diese `transcript.md` ab.
|
|
|
|
Bei normal bestätigtem `session.closed` steht `Session-Abschluss: bestätigt` in
|
|
der Datei. Endet der Datenkanal ohne diese Bestätigung oder läuft der
|
|
15-Sekunden-Abschluss-Fallback ab, versucht die UI trotzdem die bis dahin
|
|
empfangenen Abschnitte zu speichern und kennzeichnet die Datei ehrlich als
|
|
`unbestätigt`. Ohne empfangene Textabschnitte kann keine Datei erzeugt werden;
|
|
die UI zeigt dies als Fehler an. Eine vollständige Disconnect- oder
|
|
Kostenabsicherung ist nicht Teil dieses Prototyps.
|
|
|
|
Der bestehende StateDirectory bleibt erhalten. Alte Dateien unter
|
|
`/var/lib/live-intent/intents/` werden weder gelesen, verändert noch gelöscht.
|
|
|
|
## Schutzmaßnahmen und Schlüssel
|
|
|
|
Der Dienst verwendet einen `DynamicUser`, `StateDirectory`, `UMask=0077`,
|
|
begrenzte Anfragekörper, atomisches Schreiben sowie strikte ID-/Pfadvalidierung.
|
|
Schreibende Endpunkte prüfen den exakten HTTPS-Origin. Die Traefik-Route nutzt
|
|
nur `web-internal`; der Split-DNS-Eintrag verweist nur auf die private
|
|
Tailnet-Adresse. Es gibt keinen öffentlichen Router und keine zusätzliche
|
|
Firewall-Freigabe. Server-Logs enthalten weder SDP, Transkripte noch Schlüssel.
|
|
|
|
Die statische UI und die lokale Transkript-API funktionieren ohne Credential;
|
|
nur das Starten einer Live-Session antwortet dann kontrolliert mit HTTP 503.
|
|
Der OpenAI-Schlüssel gehört ausschließlich als agenix-Datei
|
|
`secrets/liveIntentOpenaiApiKey.age` in eine einzelne
|
|
`OPENAI_API_KEY=…`-Zeile. Das Modul reicht sie als systemd-
|
|
`LoadCredential` weiter; sie erscheint weder im Browser noch im Nix-Store oder
|
|
in einer HTTP-Antwort.
|
|
|
|
## Prüfung und Aktivierung
|
|
|
|
Für Quellcode-Tests:
|
|
|
|
```bash
|
|
node --test services/live-intent/tests/server.test.mjs
|
|
```
|
|
|
|
Der dokumentierte pinix-Weg nutzt einen selektiven, nicht löschenden rsync in
|
|
`/home/drone/nixos-config-build` und baut anschließend auf pinix lokal:
|
|
|
|
```bash
|
|
ssh pinix 'cd /home/drone/nixos-config-build && sudo nixos-rebuild switch --flake path:.#pinix --no-reexec'
|
|
```
|
|
|
|
Danach im Tailnet `https://intent.<domain2>` öffnen, Mikrofon freigeben, eine
|
|
Session starten, einige Transkriptzeilen erzeugen, regulär auflegen und den
|
|
Download prüfen. Ein echter Browser-/Audio-Test benötigt ein geeignetes
|
|
Mikrofon und verursacht GPT-Live-Kosten; er ist nicht durch die Offline-Tests
|
|
abgedeckt.
|
|
|
|
## Offizielle OpenAI-Referenzen
|
|
|
|
- [Getting started with GPT-Live](https://developers.openai.com/api/docs/guides/live)
|
|
- [WebRTC für GPT-Live](https://developers.openai.com/api/docs/guides/voice-webrtc?api=live)
|
|
- [Delegation und Tools](https://developers.openai.com/api/docs/guides/live-delegation)
|
|
- [Live-Sessions verwalten](https://developers.openai.com/api/docs/guides/live-conversations)
|
|
- [GPT-Live prompten](https://developers.openai.com/api/docs/guides/live-prompting)
|
|
- [TypeScript-Referenz: Live-Session erstellen](https://developers.openai.com/api/reference/typescript/resources/live/methods/create)
|