PLAN.md 4.6 KB

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