# Plan: Eingehende Edits verarbeiten (feat/edit-dispatch) Referenz: `gateway/platforms/base.py` aus dem Hermes-Checkout; chattolib 0.4.20.post1 (`MessageEditedPayload`, realtime protocol v1). Ein `message_edited`-Event wird je nach Zustand der Original-Nachricht zu einer Korrektur, einem Nachtrag oder nichts. Drei Pfade: 1. **Mid-run-Korrektur** – Nachricht wird gerade verarbeitet: Lauf canceln (Muster `/stop`, `cancel_session_processing`), mit korrigiertem Text neu dispatchen. 🚫 entsteht über den CANCELLED-Outcome-Hook (base.py:6345), 👀 wieder über den Neustart. 2. **Nachtrag** – Nachricht wurde nie dispatcht (z. B. Mention nachgetragen): Admission-Gates gegen den neuen Body, dann normaler Turn. 3. **Schon beantwortet** – ignorieren. `_dispatched_ids` ist die Sperre gegen die Retrospektive-Maschine. Konsistenzregel: Gates gelten in beide Richtungen — wer die Mention beim Berichtigen herauseditiert, fällt durchs Gate und wird ignoriert. --- ## Konfiguration (Drei-Namen-Schema) - `CHATTO_EDIT_DISPATCH` (bool, **default true**) — Master-Schalter. Default an: das Verhalten ist Teil des README-Versprechens. - `CHATTO_EDIT_WINDOW` (int Sekunden, default 300) — max. Alter eines Edits via `Message.updated_at − created_at` (types.py:673/678). Neuer "int"-Kind in der ConfigField-Maschinerie (`_get_env_or_extra_int`); unparsebare Werte fallen mit Warnung aufs Default. - plugin.yaml: beide als optional_env mit `(default: …)`-Angabe. ## Adapter - `_dispatched_ids`: Ring-Puffer (SEEN_CAP-Muster) — gefüllt vor jedem `handle_message`, both paths. Retro-Sperre. - `_processing: dict[session_key, message_id]` — geschrieben in `on_processing_start`, geräumt in `on_processing_complete` (feuert bei Cancellation mit CANCELLED, base.py:6345-6350). Liefert Pfadwahl UND den Cancel-Schlüssel. - `_session_key_for(source)`: ruft `gateway.session.build_session_key` mit exakt denselben Extras wie `handle_message` (base.py:5578) — ohne das trifft `cancel_session_processing` die falsche Session. - `_admit_and_build(...)`: die Admission-Pipeline aus `_dispatch_message_posted` ausgezogen (User-Cache → Auth → Room-Kind → DM-Commands → Mention-Gates → 🫥-Ack → Thread-Anker → Source/Event → Media). Beide Pfade teilen sie; Flag `allow_dm_commands=False` für Edits — ein editiertes `/join` läuft weder doppelt noch zum Agenten durch. - `_dispatch_message_edited(payload)`: Echo-Filter (actor == me, im Event-Zweig) → Respond-Room-Gate → Hydration (`deleted_at` skip) → Fenster → **billige Retro-Triage vor der Pipeline** (schon erledigt ⇒ keine API-Kosten, kein zweites 🫥) → `_admit_and_build` → Pfadentscheidung: - inflight ⇒ `cancel_session_processing(discard_pending=False)` — geparkte Folgenachrichten überleben; der neue Turn drainiert sie am Ende selbst. - queued (`_pending_messages[session_key].message_id == id`) ⇒ Text in place umschreiben, kein zweiter Dispatch. - sonst nie dispatcht ⇒ frischer Turn. - Einbau in `_handle_realtime_event` als eigener Zweig; `message_edited` fliegt aus der Known-Kinds-Liste. ## Tests `test_adapter.py::TestEditDispatch` (14 Fälle): Default on + Fenster 300 · Echo ignoriert · Feature aus · Read-only-Room ohne API-Call · Mention nachgetragen ⇒ Turn · weiter ohne Mention ⇒ still · schon beantwortet ⇒ ignoriert · gelöscht ⇒ ignoriert · Edit außerhalb des Fensters ⇒ ignoriert · frischer Edit in DM ⇒ Turn · Mid-run ⇒ Cancel + Redispatch mit neuem Text · Completion-Hook räumt `_processing` ab (Re-Arming) · queued Follow-up wird in place korrigiert (kein Cancel, kein zweiter Dispatch) · DM-Command-Edit läuft weder Command noch Agent. `test_platform_config.py`: `_get_env_or_extra_int` (Env-Präferenz, Extra int und str, Junk ⇒ Default, YAML-bool ⇒ Default, beides fehlt ⇒ Default). Lauf: `PYTHONPATH=/path/to/hermes-agent uv run --with pyyaml pytest -q` ## Doku README.md: Verhaltenstabelle (neue Zeile „Editing your messages"), Capability-Matrix („inbound edit processing"), Env-Beispiele, Env-Tabelle, config.yaml-Beispiel, Usage Note „Editing Your Messages" mit Pflichtenheft- Tabelle. AGENTS.md: Behavioural-Contract-Bullet. after-install.md: Satz im Test-Schritt (First-run-Verhalten). ## Nicht in Scope Anhänge im Edit berücksichtigen (queued rewrite toucht nur Text; Media bleibt vom Original). Mehrere schnelle Edits debouncen — `fetch_message` liefert ohnehin den neuesten Stand. Retraction (`message_retracted`) verarbeiten. Edits an Nachrichten in anderen Sessions gezielt in deren Queues korrigieren (der Pending-Match läuft über die Session-Key-Gleichheit).