feat(discord): binding reload without a restart, and a per-user channel allowlist (#1509)
`reload` validates the binding file and sends SIGHUP to the live owner; the running connector re-reads it and swaps guildName, channels, users and limits in place. name, seat, guildId, botUserId, tokenFile, engine and context are fixed for the life of the process; a change there, an invalid file or a channel outside the guild refuses the reload and keeps the old binding. Every attempt is one line in reloads.jsonl. The service unit maps `systemctl --user reload` to the same signal. A user entry may carry `channels`, an allowlist of listed channel ids; absent means every listed channel. Outside the list the message is dropped as channel-not-for-user; threads count as their parent. Suite 41/41, 101 node tests. QUEUE rows 19 and 20 opened. Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
@@ -18,6 +18,7 @@ scripts/discord.sh run <binding> [--supervised]
|
||||
scripts/discord.sh stop <binding>
|
||||
scripts/discord.sh unlock <binding>
|
||||
scripts/discord.sh recover <binding>
|
||||
scripts/discord.sh reload <binding>
|
||||
scripts/discord-service.sh render | install | uninstall | status <binding>
|
||||
```
|
||||
|
||||
@@ -63,6 +64,22 @@ scripts/discord-service.sh render | install | uninstall | status <binding>
|
||||
line it wrote itself; a brake an operator wrote at any point, even during
|
||||
the recovery, stays and the start is refused. Nothing automatic ever
|
||||
removes an operator's `STOP`.
|
||||
- `reload` applies an edited binding to the running connector without a
|
||||
restart. It validates the file first (an invalid file exits 2 and nothing
|
||||
is signaled), then sends SIGHUP to the live owner in `run.lock` (no live
|
||||
owner exits 1; the file applies at the next start). The process re-reads
|
||||
the file and swaps `guildName`, `channels`, `users` and `limits` in place;
|
||||
a channel that is new to the binding is read over REST and must be in the
|
||||
bound guild. `name`, `seat`, `guildId`, `botUserId`, `tokenFile`, `engine`
|
||||
and `context` are fixed for the life of the process, because the engine
|
||||
and its prompt are launched once and the token is read once; a change
|
||||
there, an invalid file or a failed channel lookup refuses the reload and
|
||||
keeps the old binding. The turn in flight finishes under the limits it
|
||||
started with; the next admission uses the new binding. Every attempt is
|
||||
one line in `reloads.jsonl`, `applied` with the differences by id or
|
||||
`refused` with the reason, and one line in the log. Nothing is sent to
|
||||
Discord. Under the service unit, `systemctl --user reload
|
||||
mosaic-discord@<binding>` sends the same signal.
|
||||
|
||||
Exit codes: 0 ok, 1 operation failed, 2 invalid data or configuration, 3
|
||||
refused by a brake (`STOP` present or the binding held; a supervisor must
|
||||
@@ -114,13 +131,15 @@ is `src/binding.mjs`.
|
||||
| `guildId`, `guildName`, `botUserId` | the one server and the bot identity `check` confirms |
|
||||
| `tokenFile` | absolute path to the bot token, 0600, read into memory at start, never printed or journaled |
|
||||
| `channels[]` | `{id, name, mode}`; `open` answers every message, `mention` only when the bot is mentioned; threads inherit the parent's mode |
|
||||
| `users[]` | `{id, name}`; the only authors that get a turn |
|
||||
| `users[]` | `{id, name, channels?}`; the only authors that get a turn. `channels` is an optional allowlist of listed channel ids; absent means every listed channel, present means those and their threads only, everything else is dropped as `channel-not-for-user` |
|
||||
| `engine` | `provider`, `model`, `thinking` for pi |
|
||||
| `limits` | `turnsPerDay` (200), `turnTimeoutSeconds` (180), `replyChunkChars` (1900), `inboundMaxChars` (4000) |
|
||||
| `context.files[]` | files appended to pi's system prompt in order, repository-relative and inside the repository (no absolute paths, `..` or symlinks); the Discord block is added after them |
|
||||
|
||||
Unknown keys, missing fields, wrong types, empty allowlists and a bot listed
|
||||
as a user all refuse with exit 2.
|
||||
Unknown keys, missing fields, wrong types, empty allowlists, a user channel
|
||||
that is not listed and a bot listed as a user all refuse with exit 2. A
|
||||
running connector picks up an edit through `reload`; the fields it will not
|
||||
take in place are listed under that command.
|
||||
|
||||
## What happens to a message
|
||||
|
||||
@@ -172,6 +191,7 @@ start and names the nonces.
|
||||
<dataRoot>/discord/<binding>/launches/ context snapshot and sha256 per run
|
||||
<dataRoot>/discord/<binding>/STOP stop switch
|
||||
<dataRoot>/discord/<binding>/notices.jsonl once-per-day fixed lines already attempted
|
||||
<dataRoot>/discord/<binding>/reloads.jsonl one line per reload attempt, applied or refused
|
||||
<dataRoot>/discord/<binding>/run.lock/ ownership directory (atomic mkdir) with owner.json {pid, start, boot}; stale ones need `unlock`
|
||||
<dataRoot>/sessions/discord-<binding>/ the pi session, continued across runs
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user