feat: push selected events to a phone over ntfy

Adds a Notifications tab to Settings so an operator can pick which events
reach their phone, published to a single self-hosted ntfy topic.

The notifier subscribes to the realtime event hub the dashboard already
consumes, so every notifiable event keeps exactly one publisher. Inbound
media had no event, so `media.received` is added and published from the
existing durable `gotImage` gate in the chat service — one notification
per conversation, never repeated when the room re-syncs. It feeds the
in-app notification bell as well.

- Best-effort delivery: a rejecting or unreachable ntfy server is logged
  and never propagated into the path that produced the event.
- Events buffered before the notifier attaches are skipped, so a restart
  cannot replay a backlog to somebody's phone.
- Settings are applied field by field onto the live config object, so a
  save takes effect without a restart.
- The access token is environment-owned (NTFY_AUTH_TOKEN) like the other
  secrets: stripped before the settings record is written, never returned
  by the API, which reports only `hasAuthToken`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Jamie BruntonandClaude Opus 5 committed 2026-08-10 13:55:00 +01:00
1 parent 827eb3a1e0
commit 38d175e4a1
34 files changed
+1728 -8

No files matched your search

+3
View File
@@ -103,6 +103,9 @@ an administrator credential.
| `GET` | `/api/outreach` | Get active outreach candidates and funnel stages | read | no |
| `GET` | `/api/config` | Get non-secret application configuration | read | no |
| `PUT` | `/api/config` | Update application configuration | admin | yes |
| `GET` | `/api/push-notifications` | Get push notification settings | read | no |
| `PUT` | `/api/push-notifications` | Update push notification settings | admin | yes |
| `POST` | `/api/push-notifications/test` | Send a test push notification | admin | yes |
| `GET` | `/api/login/status` | Get Reddit session state | read | no |
| `POST` | `/api/login/session-import` | Import Reddit and Matrix credentials | admin | yes |
| `GET` | `/api/accounts` | List Reddit accounts | read | no |
+7
View File
@@ -18,6 +18,7 @@ After the initial seed, editing `config.yaml` does not replace an existing `app_
| `OPENROUTER_API_KEY` | Yes | OpenRouter authentication. |
| `WEB_DASHBOARD_TOKEN` | When web is enabled | Administrator UI/API credential. |
| `DATABASE_PATH` | No | SQLite location; defaults to `./data/reddit-bot.db`. |
| `NTFY_AUTH_TOKEN` | No | Bearer token for a protected ntfy topic. |
| `REDDIT_BOT_AGENT_READ_TOKEN` | No | Read-only API/SSE bearer credential. |
| `REDDIT_BOT_AGENT_CONTROL_TOKEN` | No | Read plus versioned control/job bearer credential. |
| `REDDIT_BOT_API_URL` | MCP only | Base URL; defaults to `http://127.0.0.1:3000`. |
@@ -73,6 +74,12 @@ Contains `path`, but runtime ownership is `DATABASE_PATH`, not the persisted app
Controls periodic sync enablement and interval. Media work remains authentication-bound.
### `pushNotifications`
Publishes selected application events to a single ntfy topic so they reach a phone. Holds the enabled flag, server URL, topic, ntfy priority, and a boolean per event (`imageReceived`, `replyReceived`, `messageFailed`, `botStopped`, `rateLimited`, `sessionExpired`, `syncFailed`). Editable from Settings → Notifications, which writes through `PUT /api/push-notifications`; `POST /api/push-notifications/test` sends a fixed test notification. The access token is environment-owned (`NTFY_AUTH_TOKEN`), is never persisted to the settings record, and is never returned by the API — the response only reports `hasAuthToken`.
Delivery is best-effort: an unreachable or rejecting ntfy server is logged and never propagated into the path that produced the event.
### `strategies` and `chatStrategies`
Each has exploration rate, minimum mature samples, response window, and weighted variants. Variants include an ID and prompt override. Image and chat sources use separate sections so their results are not compared as one experiment.