Andreas: "koennen wir da nicht noch einbauen das der nur feuert wenn tatsaechlich was anliegt? das wuerde ich nicht dem client ueberlassen sondern schon im notifier loesen. der mail notifier muss doch auch sowas machen." Richtig -- und der Vergleich trifft den Kern: goimapnotify hat das Problem nicht, weil IMAP IDLE ein echtes Ereignis liefert. Bei Mattermost fehlt der Push fuer Kanaele, und mein Boden hat das mit blindem Feuern ausgeglichen. Jetzt sieht der Boden selbst nach: je Kanal ein posts?since=<marke>, das bei Stille leer zurueckkommt. Eigene Beitraege und system-Ereignisse zaehlen nicht, sonst loeste die eigene Antwort den naechsten Durchgang aus. Ein Dutzend Kanaele bei 60 s Takt sind zwoelf Anfragen pro Minute -- gegen einen Modelllauf ist das nichts. Der Weg dahin war laenger als noetig, weil drei Mattermost-Felder auf dieser Instanz luegen: last_post_at blieb nach einem neuen Beitrag unveraendert, msg_count zaehlte weiter obwohl der Kanal als gesehen galt, und last_viewed_at war neuer als die ungelesenen Beitraege. posts?since= ist das einzige, das nachweislich stimmt. Gemessen: zwei Durchgaenge Stille ohne einen einzigen Aufruf des Befehls, dann ein fremder Beitrag -> gefeuert. Genau das Verhalten von goimapnotify.
156 lines
6.7 KiB
Markdown
156 lines
6.7 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.
|
|
|
|
## Der Türsteher vor dem teuren Teil
|
|
|
|
`mmnotify` weckt **nur, wenn wirklich etwas anliegt** — auch beim Boden. Das ist
|
|
keine Bequemlichkeit für den Aufrufer, sondern der Sinn eines Notifiers: Hinter
|
|
`on_mention` hängt in der Regel ein Sprachmodell, und ein Boden, der blind
|
|
feuert, lässt es rund um die Uhr laufen. Bei vier Agenten und 15 Sekunden Takt
|
|
waren das 16 Modellläufe pro Minute, ohne dass jemand geschrieben hatte.
|
|
|
|
`goimapnotify` hat dieses Problem nicht, weil IMAP IDLE ein echtes Ereignis
|
|
liefert. Bei Mattermost fehlt der Push für Kanäle, also muss der Notifier
|
|
selbst nachsehen — und zwar so:
|
|
|
|
- Je Kanal ein `posts?since=<marke>`. Bei Stille kommt eine leere Liste zurück.
|
|
Ein Dutzend Kanäle bei 60 s Takt sind zwölf Anfragen pro Minute; gegen einen
|
|
Modelllauf ist das nichts.
|
|
- Eigene Beiträge und `system_*`-Ereignisse zählen nicht — sonst löste die
|
|
eigene Antwort den nächsten Durchgang aus.
|
|
- Beim Start wird der Ausgangsstand gesetzt, mit zwei Minuten Rückgriff: Was
|
|
unmittelbar vor dem Start eintraf, geht nicht verloren, aber es wird auch
|
|
nicht für die halbe Kanalgeschichte geweckt.
|
|
|
|
**Nicht** über `last_post_at`, `msg_count` oder `last_viewed_at`: Auf alle drei
|
|
war auf der getesteten Instanz kein Verlass — `last_post_at` blieb nach einem
|
|
neuen Beitrag unverändert, `msg_count` zählte weiter, obwohl der Kanal als
|
|
gesehen galt, und `last_viewed_at` war neuer als die ungelesenen Beiträge.
|
|
`posts?since=` ist das einzige Feld, das nachweislich stimmt.
|
|
|
|
Gelesen wird der Beitrag dabei nicht. Der Türsteher fragt „gibt es etwas?",
|
|
nicht „was steht drin" — das bleibt beim Agenten, sonst gäbe es zwei Leser und
|
|
zwei Antworten.
|
|
|
|
## 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`.
|