agenttwo-tools/HANDOFF.md
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

115 lines
5.6 KiB
Markdown

# Handoff
Stand: 28.08.2026. Arbeitsnotiz für die nächste Sitzung — kein Teil der
Projektdokumentation, die steht im [README](README.md).
## Wo wir stehen
Auf `main`:
| Commit | Inhalt |
|-----------|--------------------------------------------------------------------------------------------|
| `8a489df` | Werkzeug-Bestätigung, DNS-Pinning, Statistik-Leiste, Produktionsbetrieb (Squash aus PR #1) |
| `a36e366` | Mic-/Anhang-Icons als SVG, blinkender Warte-Cursor |
Arbeitsverzeichnis sauber, `npm run typecheck` und `vite build` grün.
## Als Nächstes
### 1. Export und Snapshots
Anlass: Am 28.08. waren die vier Chats des Nutzers plötzlich weg. Rettbar
waren sie nur, weil die Löschungen noch im SQLite-WAL standen und der
Hauptfile den Stand des letzten Checkpoints hielt. Das war Glück, keine
Vorkehrung.
- **Export** eines Chats als Markdown (Nachrichten, Anhänge als Dateinamen,
Werkzeugaufrufe optional). Naheliegender Ort: Button im Sidebar-Kontext
oder `GET /api/sessions/:id/export`.
- **Snapshot** der Datenbank: beim Serverstart und danach täglich eine Kopie
nach `server/backups/data-JJJJ-MM-TT.sqlite`, die letzten ~7 behalten.
Wichtig: `VACUUM INTO` statt `cp` — das schreibt einen konsistenten Stand
ohne WAL-Abhängigkeit. Verzeichnis in `.gitignore` aufnehmen.
### 2. Papierkorb statt Sofortlöschung
Der ✕-Button in `web/src/components/Sidebar.tsx:38` ruft `onDelete` ohne
jede Rückfrage. Am 28.08. sind so vier Chats mit 34 Nachrichten in 1,1
Sekunden verschwunden (vier Klicks, je ~250 ms auseinander, jeweils auf den
obersten Eintrag, der nach dem Löschen nachrückt).
- Spalte `deleted_at` in `sessions`; ✕ setzt sie, statt zu löschen
- Listenabfragen filtern `deleted_at IS NULL`
- Endgültiges Löschen nach 30 Tagen oder manuell im Papierkorb
- Optional zusätzlich: kurzes „Rückgängig" nach dem Klick
Eine reine Bestätigungsabfrage wäre billiger, aber schwächer — sie schützt
nicht vor dem schnellen Durchklicken, das hier genau passiert ist.
### 3. Datenbankpfad konfigurierbar machen (klein, aber Voraussetzung)
`server/src/db.ts:5` hat den Pfad fest verdrahtet. Jeder Testlauf schreibt
damit in die echte Datenbank des Nutzers — genau das hat am 28.08. Testchats
in seiner Historie hinterlassen. Eine Variable `DB_PATH` (Default wie
bisher) erlaubt Tests gegen eine Wegwerf-Datei und gehört vor allem, was
automatisiert testet.
## Später: geplante Aufgaben (Cron)
Idee des Nutzers, bewusst zurückgestellt. Passt gut — Werkzeug-Loop,
Gedächtnis und TTS liegen bereit, und mit `dreamTimers` (`index.ts:71`)
existiert schon ein kleiner Scheduler. Zwei Punkte entscheiden das Design:
**Bestätigungspflicht.** `read_webpage` und `remember` laufen nur nach
Freigabe, und ohne Rückkanal gilt „abgelehnt" (`tools/index.ts:107`). Ein
Job um 8 Uhr hat niemanden zum Fragen — ausgerechnet der nützlichste Job
würde zuverlässig scheitern. Auflösung: Freigabe wandert vom Ausführungs-
zum **Anlegezeitpunkt**. Beim Erstellen hakt der Nutzer die erlaubten
Werkzeuge an und sieht sie zusammen mit dem Prompt; der Job trägt diese
Allowlist als `ToolContext.confirm`. Das ist Einwilligung, nicht ihre
Umgehung.
Dabei eng bleiben: `remember` **nicht** in die Standard-Allowlist. Ein
unbeaufsichtigter Job, der Fremdinhalt liest und daraus dauerhafte „Fakten
über den Nutzer" ableitet, ist genau die Kette, die der Security-Review
geschlossen hat. Jeder Job schreibt in einen eigenen Chat.
**Laufzeit.** Cron feuert nur, solange der Server läuft; gestartet wird er
heute von Hand. Statt launchd lieber Verpasstes beim nächsten Start
nachholen — deutlich weniger Systemeingriff.
## Offen beim Nutzer
- Die vier wiederhergestellten Chats: unklar, ob die Löschung am 28.08. um
15:02 Absicht war. Falls ja, gehören sie wieder raus. Sicherungen liegen
in `~/agenttwo-backups/` (`vor-wal.sqlite` = 8 Sessions inkl. vier
Testchats, `geloeschter-stand.sqlite*` = leerer Stand).
- Static-Serving im Entwicklungsbetrieb: seit `8a489df` liefert `:8788` auch
`web/dist` aus, sobald ein Build existiert — ein Standbild neben dem
lebenden Vite auf `:5174`. Abschaltbar über `NODE_ENV` im `dev`-Script,
bisher nicht gemacht.
## Fallstricke, teuer gelernt
- **`gh` zielt auf das falsche Repo.** Es löst auf `Jeuners/agenttwo` (das
`upstream`-Remote) auf, nicht auf `agenttwo-tools`. Ohne
`--repo Jeuners/agenttwo-tools` scheitert `gh pr create` mit einem
irreführenden „No commits between". Dauerhaft:
`gh repo set-default Jeuners/agenttwo-tools`.
- **Ollamas Kontextfenster ist nicht das deklarierte.** qwen3.5 meldet
262144, geladen läuft es mit 4096. Der wahre Wert steht in
`GET /api/ps`, nicht in `/api/show`.
- **Emoji lassen sich nicht einfärben.** `🎙` und `📎` sind in den Fonts fest
grau. Zustandsfarben in CSS verpuffen an ihnen — deshalb jetzt SVG mit
`currentColor`.
- **`.caret` ist belegt** (Aufklapp-Dreieck des Denkprozesses). Der
Warte-Cursor heißt `.stream-caret`.
- **`onStats` muss vor `onDone` abgewartet werden.** Der `/api/ps`-Lookup ist
asynchron; ohne `await` geht die Stats-Nachricht hinter dem `done` raus und
der Client verwirft sie. Fiel nur auf, weil der erste (ungecachte) Lauf
leer blieb und der zweite nicht.
- **OpenRouters `usage` kommt nach `finish_reason`.** Ein früher Return
verschluckt die Tokenzahlen. Es braucht `stream_options.include_usage`.
- **Werkzeuge ohne Rückkanal werden abgelehnt.** Skripte und Tests, die
`runTool` direkt aufrufen, bekommen bei bestätigungspflichtigen Werkzeugen
„abgelehnt". Das ist Absicht — beim Testen ein `confirm` mitgeben.