158 lines
9.2 KiB
Markdown
158 lines
9.2 KiB
Markdown
# PR#100: matrix send and matrix read
|
|
|
|
Version: minor
|
|
|
|
> **WARNING:** This document describes only the change applied in this PR.
|
|
> It may already be outdated once the next PR is merged.
|
|
> Historic PR documentation is not maintained along with new PRs; treat it as a snapshot.
|
|
|
|
## Related Links
|
|
|
|
- [werkregie PR#91](https://git.javagil.de/mi/werkregie/pulls/91), `matrix react`.
|
|
- [werkregie PR#93](https://git.javagil.de/mi/werkregie/pulls/93), the running relay sends the reaction through `control.sock`.
|
|
|
|
## Problem
|
|
|
|
An agent can only react to a message in a Matrix room, not write one or read the room.
|
|
The rooms are end-to-end encrypted, so the client API with `curl` is not enough;
|
|
only the verified device of `matrix login` has the keys.
|
|
|
|
## Non-Goals
|
|
|
|
- No Markdown: the message is plain text; the human decided against a new runtime dependency for it.
|
|
- No replies, threads, edits, files or reactions with `send`.
|
|
- No new device or login: the commands use the device of `matrix login`.
|
|
- No change of `matrix react`.
|
|
|
|
## Specification
|
|
|
|
### Feature: an agent writes and reads a Matrix room with the user's device
|
|
|
|
#### Scenario#100.01: send a message
|
|
|
|
… so that an agent can write into an encrypted room.
|
|
|
|
- **Given** the device of `matrix login` is verified and member of the room "HSAdmin NG"
|
|
- **When** an agent runs `werkregie-dashboard matrix send --room "HSAdmin NG" "Build ist grün"`
|
|
- **Then** the message is in the room, encrypted like the room, and the command prints `SENT` and its event ID,
|
|
`SENT` alone when the sync took the message out of the outbox before the client saw its ID
|
|
|
|
##### Verified by
|
|
|
|
- [MatrixSenderTest "a sent message gives its event ID, a failed one ends the command"](../../dashboard/src/test/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixSenderTest.kt)
|
|
- [MainTest "matrix send takes room and text, the text also from stdin"](../../dashboard/src/test/kotlin/de/hoennig/werkregie/dashboard/MainTest.kt)
|
|
|
|
#### Scenario#100.02: the text from stdin
|
|
|
|
… so that a text with several lines needs no quoting in the shell.
|
|
|
|
- **Given** the device is member of the room
|
|
- **When** the agent runs `matrix send --room "HSAdmin NG" -` with the text on stdin
|
|
- **Then** the message holds the text from stdin, without its final line break
|
|
|
|
##### Verified by
|
|
|
|
- [MainTest "matrix send takes room and text, the text also from stdin"](../../dashboard/src/test/kotlin/de/hoennig/werkregie/dashboard/MainTest.kt)
|
|
|
|
#### Scenario#100.03: read the newest messages
|
|
|
|
… so that an agent sees what was said in the room.
|
|
|
|
- **Given** the room holds messages, some of which the device cannot decrypt
|
|
- **When** the agent runs `matrix read --room "HSAdmin NG"`
|
|
- **Then** the command prints the newest 20 messages, oldest first, each as a block of time, sender, event ID and text,
|
|
and a message without key as `(nicht entschlüsselt)`
|
|
|
|
##### Verified by
|
|
|
|
- [MatrixReaderTest "the reading takes the messages back to where it starts, at most the limit"](../../dashboard/src/test/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReaderTest.kt)
|
|
- [MatrixReaderTest "a message gives its text, an undecryptable one none, other events nothing"](../../dashboard/src/test/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReaderTest.kt)
|
|
- [MatrixReadingTest "each message is a block of time, sender, event ID and its indented text, the oldest first"](../../dashboard/src/test/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReadingTest.kt)
|
|
|
|
#### Scenario#100.04: read since a time or an event
|
|
|
|
… so that an agent reads only what is new since it looked last.
|
|
|
|
- **Given** the room holds messages before and after an event `$e` sent at 14:00
|
|
- **When** the agent runs `matrix read --room "HSAdmin NG" --since '$e'`, or `--since 2026-10-23T14:00`
|
|
- **Then** the command prints the messages after `$e`, or those from 14:00 on, at most 500 or `--limit N`
|
|
(the newest of them);
|
|
an event ID that is not among the last 500 events of the room fails the command
|
|
|
|
##### Verified by
|
|
|
|
- [MatrixReaderTest "the reading takes the messages back to where it starts, at most the limit"](../../dashboard/src/test/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReaderTest.kt)
|
|
- [MatrixReaderTest "an event to start after that the room does not show ends the command, also beyond the limit"](../../dashboard/src/test/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReaderTest.kt)
|
|
- [MatrixReaderTest "an event before the time to start from is skipped, and enough of them in a row end the reading"](../../dashboard/src/test/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReaderTest.kt)
|
|
- [MainTest "matrix read takes room, since and limit"](../../dashboard/src/test/kotlin/de/hoennig/werkregie/dashboard/MainTest.kt)
|
|
|
|
#### Scenario#100.05: the running relay sends the message
|
|
|
|
… so that the relay keeps running, as for `matrix react`.
|
|
|
|
- **Given** the relay of the machine runs
|
|
- **When** an agent runs `matrix send --room "HSAdmin NG" "Build ist grün"`
|
|
- **Then** the message is in the room, and the relay still runs
|
|
|
|
##### Verified by
|
|
|
|
- [MatrixControlTest "the socket takes a reaction, a message or a reading, each with its fields"](../../dashboard/src/test/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixControlTest.kt)
|
|
- [MatrixCommandsTest "while another process syncs, the command asks it"](../../dashboard/src/test/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixCommandsTest.kt)
|
|
|
|
#### Scenario#100.06: a wrong call
|
|
|
|
… so that the agent tells a wrong call from a failure.
|
|
|
|
- **Given** any state
|
|
- **When** the agent runs `matrix send` without room or text, or `matrix read` with a limit that is no number from 1 to 500,
|
|
or a time it cannot read
|
|
- **Then** nothing is sent, and the command prints its usage and ends with exit code 2
|
|
|
|
##### Verified by
|
|
|
|
- [MainTest "matrix send needs room and text, none of them blank"](../../dashboard/src/test/kotlin/de/hoennig/werkregie/dashboard/MainTest.kt)
|
|
- [MainTest "matrix read needs a room, a limit from 1 to 500 and a readable since"](../../dashboard/src/test/kotlin/de/hoennig/werkregie/dashboard/MainTest.kt)
|
|
|
|
## Solution
|
|
|
|
- `MatrixCommands`, new: the way of `matrix react` for each command that needs the device's client.
|
|
`run` asks `control.sock` first and takes `sync.lock` and a client of its own only when nobody answers;
|
|
`handle` waits for the first sync, finds the room by name or ID and runs the command.
|
|
The answer is the text the command prints, so the socket carries it as it is.
|
|
- `MatrixRequest`, new: `ReactionRequest`, `SendRequest` and `ReadRequest`.
|
|
- `MatrixControl` takes the commands `react`, `send` and `read`, each with its fields, and refuses any other;
|
|
a relay of an older version answers `send` and `read` with an error, so it needs a restart after the update.
|
|
- `MatrixSender`: `m.text` with `body` only, through the outbox as the reaction; prints `SENT` and the event ID,
|
|
as `matrix react` does without the event ID when the outbox gave it up before the client saw it.
|
|
- `MatrixReader`: the newest events of the room back to `--since` or up to the limit, at most 500;
|
|
a reaction does not count, by its relation, which stays cleartext also in an encrypted room;
|
|
an encrypted event of any algorithm counts until its decryption shows that it is no message.
|
|
Each event gets 30 s for its key, all at once, as for `react`; without it the message prints as `(nicht entschlüsselt)`.
|
|
An event ID of `--since` that is not among the last 500 events fails the command, also with a smaller limit:
|
|
the reading looks for it beyond the limit and keeps the newest messages after it.
|
|
- With `--since` a time, an event before it is skipped, and the reading ends after 20 of them in a row:
|
|
the room orders its events as the server got them, so an older time stamp may stand among newer ones.
|
|
- The request carries the caller's time zone, so the relay prints the times in the zone of the command.
|
|
- `MatrixReading`, free of the client library: `--since` as event ID (`$…`) or time, time without offset in local time;
|
|
the blocks with the text indented by two spaces, so that a blank line in a message ends no block.
|
|
- `matrix read` prints nothing for a room without messages, rather than a blank line.
|
|
- `matrix send` refuses more than one text argument: an unquoted text would send only its first word.
|
|
- The tests of `settled` and `sentId` and of the request to `control.sock` move from `MatrixReactorTest`
|
|
to `MatrixCommandsTest` and `MatrixSenderTest`, with the code; `MatrixReactorTest` keeps those of `react`.
|
|
- `pitestPartial mutationCrapCheck` against main: the highest Mutation-CRAP is 14, `readRequest` in `Main.kt`;
|
|
`MatrixReader.candidates` takes the timeline as a flow, so that its tests need no client.
|
|
- Tested on 2026-10-23 with the relay stopped, by the branch's own client:
|
|
`matrix read` on "HSAdmin NG" decrypted 3 messages in 11 s;
|
|
`matrix send` into the test room `!B8yKiz2HJtweVdVbIhQdo2iOvHdx2L8KoeD4K7QNnW8`, with the human's consent,
|
|
printed `SENT $DbfeJUag…`, and `matrix read` showed the message.
|
|
|
|
## Release Notes
|
|
|
|
- `matrix send --room RAUM TEXT` writes a message into a room, also an end-to-end encrypted one.
|
|
- `matrix read --room RAUM [--since ZEIT|EVENT-ID] [--limit N]` prints the messages of a room, decrypted.
|
|
- Both go through the running relay or local mode, as `matrix react`; restart it after the update.
|
|
|
|
## Open Questions
|
|
|
|
None.
|