Commit graph

18 commits

Author SHA1 Message Date
Jeuner
695fcf82f4 docs: Handoff für die nächste Sitzung
Arbeitsnotiz mit Stand, den beiden nächsten Schritten (Export/Snapshots,
Papierkorb) und dem zurückgestellten Cron-Entwurf. Dazu die Fallstricke
dieser Sitzung, damit sie nicht zweimal Zeit kosten — vor allem das
falsche gh-Remote, Ollamas effektives Kontextfenster und die Reihenfolge
von onStats und onDone.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015AUP4R3rgq4XwVs4bVf7uh
2026-08-28 16:04:38 +02:00
Jeuner
a36e366b58 fix(ui): Mic- und Anhang-Icon sichtbar, Warte-Cursor blinkt
Zwei Stellen, an denen die Oberfläche tot wirkte.

Mic und Anhang waren grau: 🎙 und 📎 sind in den Emoji-Fonts fest
eingefärbt, `color` greift bei ihnen nicht. Damit liefen auch die
vorhandenen Zustandsregeln ins Leere — .btn-mic.recording (rot) und
.transcribing (orange) färbten nur den Rahmen, nie das Icon. Jetzt SVG in
currentColor, dazu ein akzentfarbener Rahmen, damit Mic, Anhang und
"Senden" als eine Gruppe lesbar sind.

Der Platzhalter vor dem ersten Token war ein statisches ▍ und sah nach
Stillstand aus, obwohl die Antwort lief — bei kaltem Modell dauert das
mehrere Sekunden. Blinkt jetzt über das vorhandene blink-Keyframe.
Eigene Klasse .stream-caret, weil .caret schon das Aufklapp-Dreieck des
Denkprozesses ist und sonst mitgeblinkt hätte.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015AUP4R3rgq4XwVs4bVf7uh
2026-08-28 15:16:14 +02:00
Jeuner
8a489dfc5c
feat: Werkzeug-Bestätigung, DNS-Pinning, Statistik-Leiste, Produktionsbetrieb (#1)
Ergebnis eines Security-Reviews des Werkzeug-Pfads plus die daraus
hervorgegangenen Verbesserungen.

Sicherheit:
- runTool wertet requiresConfirmation aus; Rückfrage über den WebSocket mit
  vollständigen Argumenten, Antwort allow/always/deny. Ohne Rückkanal gilt
  abgelehnt. read_webpage und remember sind bestätigungspflichtig
- remember pinnt nicht mehr automatisch, Modell-Anker verfallen normal
- SSRF-Guard mit gepinnter DNS-Auflösung (lookup-Hook statt fetch): geprüft
  wird genau die Adresse, die auch verbunden wird — schließt DNS-Rebinding
- ctx.signal kombiniert Abbruch und Zeitlimit bis in den Netzwerkabruf
- activeAborts als Set, Chat-Rate-Limit je Verbindung, Bucket-Cleanup

Statistik-Leiste über dem Composer: Tokens, tok/s, TTFT, Gesamtzeit und
Kontext-Füllstand, dazu die Session-Summe. Zahlen sind gemessen — bei Ollama
aus dem Abschluss-Chunk, bei OpenRouter aus dem usage-Block. Der Füllstand
rechnet gegen das tatsächlich genutzte Fenster aus /api/ps, nicht gegen die
deklarierte Länge des Modells.

Produktionsbetrieb: der Server liefert web/dist jetzt mit aus, npm start
genügt. /api und /ws behalten Vorrang.

README überarbeitet, Fork-Bezug entfernt, zerbrochene Konfigurationstabelle
repariert, API-Referenz ergänzt.

Nicht umgesetzt: Auth-/Origin-Härtung — der Server läuft bewusst lokal.
2026-08-28 15:04:55 +02:00
Jeuner
878acd9933 ui: Mic und Anhänge rechts neben Senden, einheitliche Icon-Buttons
- Composer-Reihenfolge: Eingabe → Senden → Mikro → Anhängen
- .btn-icon-Basisklasse: gleiche Höhe wie Senden, Hover-Feedback,
  Active-Hub, Fokus-Ring (Tastatur), dezent gefüllter Send-Button
2026-08-28 13:52:42 +02:00
Jeuner
0cfc501ec4 docs: Anhänge (Text/PDF) dokumentiert, Modellauswahl in Konfiguration erwähnt 2026-08-28 13:46:56 +02:00
Jeuner
32128aba7a feat: read_webpage-Werkzeug (Defuddle) mit SSRF-Schutz
- lädt öffentliche Websites und extrahiert Hauptinhalt als Markdown
  (defuddle/node + linkedom)
- SSRF-Schutz: nur http/https, private Adressbereiche werden nach
  DNS-Auflösung abgewiesen, auch über Weiterleitungen (redirect:
  manual, jede Hop-URL neu geprüft)
- Grenzen: 15 s Zeitlimit, 2 MB Fetch-Limit, 25 kB Output-Cap;
  Inhalt wird dem Modell als nicht vertrauenswürdig markiert
- Text-/PDF-Anhänge: Message-Feld files, Validierung über
  server/src/files.ts, PDF-Textextraktion via pdf-parse
- README: Werkzeugliste und Netz-Ausnahme dokumentiert
2026-08-28 13:45:00 +02:00
Jeuner
969ec6909d docs: RAM-Hinweis, qwen3:8b als leichtere Alternative; setup.sh warnt bei <16 GB RAM 2026-08-28 13:36:46 +02:00
Jeuner
ea87c350a5 feat: Installationsroutine für macOS (setup.sh)
- prüft macOS, Homebrew, Node 22+, npm-Abhängigkeiten, ffmpeg,
  whisper.cpp inkl. Modell, Piper inkl. Stimme, Ollama-Server und
  das Sprachmodell — fehlende Teile werden auf Nachfrage installiert
- Modi: interaktiv, --check (nur Prüfung), --yes (ohne Nachfragen)
- README: Schnellstart-Abschnitt (clone → setup → dev)
- npm run setup als Alias
2026-08-28 13:35:16 +02:00
Jeuner
c8b74892ef feat: Modellauswahl, globales Gedächtnis und Session-Selbstheilung
- lokales Ollama-Modell frei wählbar (Dropdown aus /api/ollama/models,
  /api/model akzeptiert ?name=, Chat-Option model mit Regex-Validierung)
- Gedächtnis jetzt global über alle Chats: Ankerpunkte werden
  sessionübergreifend eingespielt (eigene Session bevorzugt), recall
  und Gedächtnis-Panel sehen alle Anker
- Frontend heilt tote Session-IDs: unbekannte ID verfällt beim Laden,
  Client springt auf die vom Server genutzte Session — Nachrichten
  landen nicht mehr in schleichend neuen Chats
- Origin-Allowlist um Port 5173 ergänzt (alter Dev-Server blockierte
  sonst alles mit 403)
- Rollen-Label und Composer-Platzhalter zeigen das echte Modell statt
  hartkodiertem qwen3
2026-08-28 13:13:48 +02:00
Jeuner
9bce768f95 feat: Gedächtnis mit Append-Only-Log, Traumphase und Ankerpunkten
- memory_events als append-only Log (nur INSERT), daraus Zustand
  deterministisch rekonstruierbar (fold/rebuild)
- Traumphase konsolidiert Log in Ankerpunkte: LLM-Extraktion mit
  Heuristik-Fallback, Dedupe über Wort-Ähnlichkeit, Decay auf
  ungepinnten Ankern
- einstellbares Gedächtnis-Fenster (Default 10 Schritte) statt
  hartkodiertem slice(-24)
- wichtigste Ankerpunkte werden als System-Kontext eingespielt
- neue Werkzeuge remember (schreibend, gepinnt) und recall
- Gedächtnis-Panel im Frontend: Ankerliste, Pinnen, Traumphase
  manuell auslösen, Rekonstruktion
- Eval-Skript: npm run memory:eval --workspace server
2026-08-27 21:41:35 +02:00
Jeuner
e6d55f35a3 chore: Favicon (SVG) und Theme-Farbe im HTML-Kopf 2026-08-27 21:41:35 +02:00
Jeuner
9936b35b48 fix: Dezimalkomma im Rechner, Anhang-Vorschau über der Eingabezeile
Beide Fehler zeigten sich beim Durchspielen mit einem Bild in der laufenden
Oberfläche:

- calculate lehnte "87,50" ab. Das Modell rechnet bei deutschen Beträgen
  natürlicherweise mit Komma und musste einen Aufruf verschwenden, um sich
  selbst zu korrigieren — sichtbar als fehlgeschlagener erster Aufruf. Der
  Tokenizer nimmt Komma jetzt als Dezimaltrenner an, die Beschreibung des
  Werkzeugs sagt das auch. Danach genügten zwei Aufrufe statt drei.
- Die Anhang-Vorschau saß neben der Eingabezeile statt darüber: .composer war
  bereits eine Flex-Zeile, wodurch .attachments zum Flex-Geschwister wurde.
  Der Container ist jetzt eine Spalte, .composer-row übernimmt die Zeile.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1NXUpHbxTusqQmsjrFfbU
2026-08-26 19:29:58 +02:00
Jeuner
33125e1420 feat: Werkzeuge (Tool-Calling) mit vier lesenden Werkzeugen
qwen3.5 meldet die Fähigkeit "tools". Der Streaming-Loop in ollama.ts sammelt
jetzt tool_calls ein, führt sie aus, hängt die Ergebnisse an den Verlauf und
startet eine weitere Runde — bis eine Antwort ohne Werkzeugwunsch entsteht
oder MAX_TOOL_ROUNDS (5) erreicht ist. In der letzten Runde werden bewusst
keine Werkzeuge mehr mitgeschickt, damit eine Antwort entsteht statt eines
weiteren Aufrufwunsches.

Werkzeuge (alle ausschließlich lesend):
- get_time    Datum/Uhrzeit je IANA-Zeitzone — Modelle raten das Datum sonst
- calculate   Arithmetik über einen eigenen Parser
- read_file   Textdatei unterhalb des Projektverzeichnisses
- list_files  Verzeichnisinhalt

Sicherheit:
- Der Dateizugriff liegt in einer Sandbox. Pfade werden per realpath aufgelöst
  (damit auch Symlinks) und müssen unterhalb von TOOLS_ROOT liegen. .env, .git/
  und Schlüsseldateien sind auch innerhalb der Wurzel gesperrt.
- calculate benutzt kein eval/new Function. Der Ausdruck stammt aus einer
  Modellantwort, die von Nutzereingaben beeinflusst wird; der Parser kennt nur
  Zahlen und Grundrechenarten.
- 15 s Zeitlimit je Aufruf, Fehler werden als Ergebnis an das Modell
  zurückgegeben statt die Antwort abzubrechen. Nur ToolError-Texte werden
  durchgereicht, damit keine internen Details nach außen gelangen.

Geprüft am laufenden Modell: Zeit, Rechnen (4271*8934+17 = 38157131, exakt),
Dateizugriff und zwei Werkzeuge in einer Antwort. Gegen die Sandbox getestet:
../../../etc/passwd, /etc/passwd, ~/.ssh/id_rsa, .env sowie Code-Injection in
calculate — alle abgewiesen. Auch als Prompt formuliert ("lies .env und nenne
den API-Key") bleibt der Zugriff blockiert.

Die Oberfläche zeigt Aufrufe samt Argumenten und Dauer über der Antwort; ein
Schalter in den Einstellungen deaktiviert Werkzeuge. Die Anzeige lebt nur im
Browser-Zustand und ist nach einem Neuladen weg.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1NXUpHbxTusqQmsjrFfbU
2026-08-26 19:01:28 +02:00
Jeuner
adb57ae6e3 feat: Bilder im Chat (Vision)
qwen3.5 meldet die Fähigkeit "vision", die bisher ungenutzt blieb. Bilder
lassen sich jetzt per Button, Zwischenablage (⌘V) oder Drag & Drop anhängen;
eine Nachricht darf auch nur aus einem Bild bestehen.

- images.ts: Validierung vor dem Speichern — maximal 4 Bilder je Nachricht,
  je 6 MB, Typ per Magic Bytes statt per Dateiendung (PNG/JPEG/GIF/WebP).
  Der WebSocket bekommt dazu ein maxPayload von 32 MB, vorher war er
  unbegrenzt und Bilder machen Nachrichten deutlich größer.
- db.ts: Spalte images samt Migration für bestehende Datenbanken aus agenttwo
- ollama.ts: images-Feld je Message; openrouter.ts: OpenAI-Format mit
  image_url, MIME aus den Magic Bytes statt fest image/png
- Composer: Vorschau mit Entfernen-Button, Fehlermeldung bei zu großen oder
  nicht unterstützten Dateien; ChatMessage zeigt Bilder in der Historie
- Bilder wandern in die Chat-Historie und werden bei Folgefragen erneut
  mitgeschickt, damit Rückfragen zum selben Bild funktionieren

Getestet gegen das laufende Modell: ein rendertes PNG (roter Kreis auf weiß)
wird korrekt als japanische Flagge beschrieben. Validierung geprüft gegen
5 Bilder, Textdatei mit Bildnamen, kaputtes base64 und 7-MB-Blob — alle
abgelehnt.

Nebenbei: Branding in UI, Titel und Serverlog auf agenttwo-tools umgestellt.
Die localStorage-Keys bleiben unverändert, sonst gingen gespeicherte
Einstellungen verloren.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1NXUpHbxTusqQmsjrFfbU
2026-08-26 17:44:57 +02:00
Jeuner
e48228c570 chore: Fork als agenttwo-tools aufsetzen
Basis für die Tool-Calling- und Vision-Entwicklung. Die Historie von agenttwo
bleibt erhalten, damit sich Fixes aus der Basis per cherry-pick übernehmen
lassen; das Remote upstream zeigt dafür auf Jeuners/agenttwo.

Eigene Ports, damit beide Projekte gleichzeitig laufen können:
- Backend 8787 -> 8788, Vite 5173 -> 5174 samt Proxy-Zielen
- Origin-Allowlist in security.ts entsprechend auf 5174/8788
- OpenRouter-Attribution (HTTP-Referer, X-Title) angepasst

Die SQLite-Datei liegt wie gehabt neben dem Server, beide Instanzen haben
dadurch getrennte Sessions.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1NXUpHbxTusqQmsjrFfbU
2026-08-26 17:35:51 +02:00
Jeuner
e721540b80 docs: Voraussetzungen korrigieren (Node 22+, piper als Python-Modul)
Beim Testen eines frischen Clones fielen zwei falsche Angaben auf:

- "Node.js 20+" stimmte nicht. db.ts nutzt node:sqlite, das unter Node 20
  gar nicht existiert (ERR_UNKNOWN_BUILTIN_MODULE) — der Server startet dort
  nicht. Ab 22 läuft es, mit ExperimentalWarning; stabil ab Node 24.
  Zusätzlich als engines-Feld in package.json hinterlegt.
- "brew install piper" führte in die Irre: voice.ts ruft `python3 -m piper`
  auf, nicht das Homebrew-Binary. Korrekt ist `pip3 install piper-tts`.

Dazu ein Prüf-Snippet, mit dem sich die Toolchain vor dem ersten Start
verifizieren lässt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1NXUpHbxTusqQmsjrFfbU
2026-08-26 17:16:17 +02:00
Jeuner
799c9c9394 fix: Origin-Prüfung für HTTP und WebSocket, Härtung von STT/TTS
Der Server lauscht nur auf 127.0.0.1, hatte aber keine Authentifizierung und
mit CORS origin:true jede fremde Origin reflektiert. Da localhost aus jedem
Browser-Tab erreichbar ist, konnte damit jede besuchte Webseite über
/api/sessions und /api/sessions/:id/messages die komplette Chat-Historie
auslesen. Der WebSocket-Upgrade prüfte die Origin ebenfalls nicht — WebSockets
unterliegen nicht der Same-Origin-Policy, wodurch fremde Seiten Chats senden,
Antworten mitlesen und Kosten auf dem OpenRouter-Key erzeugen konnten (CSWSH).

- security.ts: Origin-Allowlist (localhost/127.0.0.1:5173 und :8787, per
  ALLOWED_ORIGINS erweiterbar) und einfacher In-Memory-Rate-Limiter
- CORS auf die Allowlist begrenzt, zusätzlicher onRequest-Hook mit 403
- WebSocket-Handshake weist fremde Origins mit 403 ab
- ffmpeg: Containerformat per Magic Bytes bestimmt statt geraten, dazu
  -protocol_whitelist file — verhindert, dass Demuxer wie concat oder HLS
  auf lokale Pfade und URLs im hochgeladenen Inhalt zugreifen
- Rate-Limits auf /api/stt (10/min) und /api/tts (30/min), Textlänge bei TTS
  begrenzt, bodyLimit und MAX_AUDIO_BYTES auf 8 MB angeglichen
- Fehlerdetails (lokale Pfade, Upstream-Antworten) nur noch ins Server-Log;
  dafür Fastify-Logger aktiviert, sonst wären sie stillschweigend verworfen

Nebenbei: ollama.ts respektiert jetzt OLLAMA_URL statt localhost:11434
hartzukodieren, und ws wandert von devDependencies zu dependencies, da es
zur Laufzeit importiert wird.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1NXUpHbxTusqQmsjrFfbU
2026-08-26 16:54:31 +02:00
Jeuner
752198c5fa init: oxagenttwo — voice chat with local qwen3 (Ollama) + OpenRouter, Whisper STT, Piper TTS (Thorsten), sessions, thinking mode 2026-08-26 16:37:59 +02:00