feat: recover missing conversations, fix media attachment, add manual inbox tab

Missing conversations: recipient Reddit accounts that are deleted or suspended
left conversations unaccounted for. A new RecipientAccountStatusService polls
Reddit for the users behind cached conversations and records their account
status durably, with progressive backfill and delta-media fixes in ChatService
so history and images are no longer dropped mid-sync.

Media: attachMedia now surfaces download failures as MediaAttachmentError so the
sync cursor can be retried instead of silently losing an image, recognizes image
bytes when Matrix returns a generic content type, and keeps separate gallery
occurrences when the same asset appears in more than one event.

Manual inbox tab: the conversations list now carries the reply-loop status, so
the inbox can separate conversations the operator is driving by hand from the
ones automation is still handling. A Manual filter pill and a per-row chip
explain why each conversation was handed over.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Jamie BruntonandClaude Opus 5 committed 2026-08-10 11:03:33 +01:00
1 parent 725b5fc4e6
commit ac12a3146a
35 files changed
+1164 -129

No files matched your search

+3 -1
View File
@@ -13,6 +13,7 @@ The application uses one `better-sqlite3` database, normally `data/reddit-bot.db
- `reddit_accounts`: verified Reddit/Matrix identity, encrypted-at-rest only to the degree provided by the host/volume, cookie JSON, Matrix credentials, active/status timestamps.
- `reddit_conversations`: account-scoped cached Matrix conversation payloads.
- `reddit_account_sync_state`: account-scoped sync cursor, full/delta timestamps, and invitation cooldown.
- `reddit_recipient_account_status`: process-global, case-insensitive recipient username status (`active`, `suspended`, or `deleted`) and last confirmed check time.
- `reddit_media`: account-scoped private-media metadata and relative file path.
- `media_library_assets`: process-global operator-owned image metadata, overlapping anatomy tags, SFW/NSFW classification, consent time, enablement, dimensions, hash, and private relative path.
- `media_library_sends`: account/conversation/inbound-event reservation and confirmed/failed Matrix delivery audit for a shared library asset.
@@ -52,8 +53,9 @@ The special legacy-unassigned account exists only to preserve rows created befor
5. Conversation policy is snapshotted in state JSON when automation starts for that conversation.
6. Secrets and the database path are not stored in `app_config`.
7. Private media paths in SQLite are relative to the persistent data root; HTTP access remains authenticated.
8. Received `reddit_media` and operator `media_library_assets` are separate ownership domains; no automatic copy or promotion exists.
8. Received `reddit_media` and operator `media_library_assets` are separate ownership domains; no automatic copy or promotion exists. Received-media identity represents the account-scoped Matrix message occurrence, so repeated use of one remote asset can produce multiple gallery rows that safely share the same private relative file path.
9. A library image content hash has at most one reservation per account conversation, even if the same file is later re-uploaded under a new asset ID; an inbound request also has at most one selected asset. Failed or uncertain attempts remain reserved to prevent duplicates.
10. Recipient account status changes only after a definitive Reddit profile response. Transient lookup failures retain the previous value, and the shared check timestamp prevents duplicate lookups across account runtimes during the four-hour interval.
## Repository map