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
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.
- 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
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
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
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
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