Files
mmnotify/README.md
T
elton 831c2a953f Direktnachrichten kommen doch sofort an -- das Gespraech ist Echtzeit
Andreas: "dann durchsuch noch mal die foren, das kann doch nicht sein
das das niemand da draussen hat das problem." Hatte jemand, und der
entscheidende Satz stand in einem Bugreport eines anderen Projekts:
"DMs work fine."

Nachgemessen, und es stimmt:

  Direktnachricht            -> posted kommt SOFORT
  eigener Kanalbeitrag       -> posted kommt
  Kanalbeitrag eines anderen -> kommt nie

Damit ist die Lage eine voellig andere als gedacht. Mit einem Agenten zu
reden -- der Anwendungsfall, um den es Andreas ging -- laeuft in Echtzeit
ueber eine DM. Der Boden deckt nur noch Erwaehnungen in Kanaelen ab, und
das ist der Fall, bei dem 15 Sekunden niemanden stoeren.

Dabei ausserdem einen eigenen Fehler korrigiert: Ich hatte den Test
"direkt am Container, ohne Caddy" als erledigt dargestellt, obwohl das
sed dafuer fehlgeschlagen war und gegen den Proxy gemessen wurde.
Nachgeholt: ws://192.168.1.142:8065 direkt verhaelt sich genauso. Der
Proxy ist jetzt wirklich ausgeschlossen.
2026-09-01 20:54:02 +02:00

125 lines
5.0 KiB
Markdown

# mmnotify
Hört am Mattermost-Websocket und **weckt einen Agenten**, wenn er gemeint ist.
Gegenstück zu [`goimapnotify`](https://gitlab.com/shackra/goimapnotify) für
Mail: ein kleiner Prozess, der lauscht und bei einem Ereignis einen Befehl
ausführt. Der Agent liest und antwortet danach mit seinen eigenen Werkzeugen —
`mmnotify` schreibt nie in einen Kanal, es klopft nur an.
```
mmnotify -conf /etc/mmnotify.json
```
## Konfiguration
```json
{
"url": "https://team.42i.org",
"token": "<persönliches Zugriffstoken des Agenten>",
"on_mention": "/root/run-agent.sh team",
"on_direct": "",
"debounce_seconds": 5,
"floor_seconds": 60,
"ignore_users": []
}
```
Die Datei enthält ein Token und gehört mit `0600` dem Dienstbenutzer.
| Feld | Bedeutung |
|---|---|
| `on_mention` | Befehl bei Erwähnung (`@name`) im Kanal |
| `on_direct` | Befehl bei Direktnachricht; leer = `on_mention` |
| `debounce_seconds` | Mindestabstand zwischen zwei Läufen (Standard 5) |
| `floor_seconds` | Nachsehen auch ohne Ereignis (Standard 15, `0` schaltet ab) |
| `ignore_users` | Absender, die nie wecken |
| `away_after_seconds` | Wann der grüne Punkt auf abwesend zurückfällt (Standard 120) |
Der Befehl bekommt die Ereignisdaten über die Umgebung: `MM_TRIGGER`
(`event` oder `floor`), `MM_CHANNEL`, `MM_CHANNEL_NAME`, `MM_CHANNEL_TYPE`,
`MM_SENDER`, `MM_POST_ID`, `MM_ROOT_ID`, `MM_MESSAGE`.
## Zwei Wege, und der Boden ist der tragende
Der Websocket ist der Beschleuniger; darunter läuft ein Boden, der ohnehin
nachsieht. Die Aufteilung stammt aus hive (`hive_runtime/channels/team.py`) und
ist dort begründet: *„A stream that dies quietly must make us LATE, never
blind."*
**Hier ist der Boden nicht nur Vorsicht.** Gemessen am 2026-09-01 gegen
Mattermost 11.7.10:
| | |
|---|---|
| **Direktnachricht** | `posted` kommt **sofort** an |
| eigener Kanalbeitrag | `posted` kommt an |
| Kanalbeitrag eines anderen Kontos | kommt **nie** an |
**Das Gespräch mit einem Agenten läuft damit in Echtzeit.** Eine DM weckt ihn
ohne Verzögerung; nur Erwähnungen in Kanälen gehen über den Boden. Dieselbe
Trennung ist [für andere Mattermost-Integrationen
dokumentiert](https://github.com/openclaw/openclaw/issues/11797): *„DMs work
fine"* — Kanal-Ereignisse fehlen, DMs kommen durch.
Die Verbindung ist also voll funktionsfähig und hat die Kanal-Abos — es fehlen
ausschließlich die Ereignisse fremder Konten. Ausgeschlossen wurden:
`authentication_challenge`, Token im Authorization-Header beim Handshake, ein
echter Bot-Account statt eines Nutzers mit Token, und das Sitzungs-Cookie
`MMAUTHTOKEN` wie im Browser. Alle vier verhalten sich gleich.
Das Verhalten ist [im Mattermost-Forum
beschrieben](https://forum.mattermost.com/t/websocket-does-not-provide-posted-events-from-other-accounts/14876)
und tritt dort auch mit einem System-Admin-Token auf; eine Lösung steht nicht
dabei.
Deshalb ist `floor_seconds` der Weg und nicht der Notnagel, und der Standard
ist kurz: **15 Sekunden**. Ein Durchgang kostet einen API-Aufruf — billig genug,
um es oft zu tun. Wer den Stream repariert bekommt, kann ihn hochsetzen.
Daraus folgt die zweite Entscheidung: **mmnotify liest den Beitrag nicht aus
dem Ereignis.** Es klopft an; was zu tun ist, entscheidet der Agent, wenn er
nachsieht. Zwei Leser wären zwei Antworten.
## Anwesenheit
Solange mmnotify verbunden ist, zeigt der Punkt neben dem Namen, woran man ist:
- **abwesend**, sobald die Verbindung steht — da, aber untätig
- **grün**, während der Befehl wegen einer Erwähnung läuft
- **abwesend** wieder, wenn es `away_after_seconds` still war
- **offline** beim Beenden, statt einen grünen Punkt zurückzulassen, hinter dem
niemand mehr sitzt
Das routinemäßige Nachsehen des Bodens macht ausdrücklich **nicht** grün. Läuft
der Boden häufiger als die Away-Frist, leuchtete der Agent sonst rund um die
Uhr — und ein Punkt, der immer grün ist, sagt genauso wenig wie gar keiner.
(Beim Testen genau so passiert: Boden alle 20 s, Frist 25 s, nie abwesend.)
Ein Status ist eine Höflichkeit gegenüber dem, der in die Seitenleiste schaut.
Weigert sich der Server, wird das protokolliert und weitergearbeitet: Ein Agent,
der aufhört zu antworten, weil er einen Punkt nicht einfärben konnte, wäre
absurd.
## Robustheit
- Wiederaufbau mit wachsendem Abstand (1 s bis 60 s). Ein Server, der neu
startet, wird nicht von einem Client bestürmt, der im Sekundentakt anklopft.
- Herzschlag alle 30 s, Lesefrist 90 s. Eine halboffene Verbindung liest sich
sonst für immer wie Stille.
- Höchstens ein Lauf gleichzeitig; wer währenddessen schreibt, wird vom
laufenden Durchgang mitgenommen. Ohne das lösen fünf Beiträge in einer Minute
fünf Agentenläufe aus, die sich gegenseitig überholen.
- Eigene Beiträge und `system_*`-Ereignisse wecken nie — sonst antwortet ein
Agent auf sich selbst.
- `SIGTERM` beendet sofort und sauber.
## Bauen
```bash
go build -o mmnotify .
```
Ein statisches Binary ohne Laufzeitabhängigkeiten — gedacht zum Einbacken in
das Agenten-Image, neben `goimapnotify`.