Stopping the bot previously halted conversation syncing too. On the
multi-account path the run-flag check sat above both the reply-sync and
response-check branches, so a stop left only MediaSyncService's coarse
30-minute chat refresh. The legacy single-account path did the opposite
and kept syncing while stopped — one boolean meaning two things.
Replace the boolean with an explicit three-way mode:
running — discover, screen, send, automate replies
scan — refresh chats, detect replies, check responses; send nothing
and discover nothing; actionable replies surface as backlog
stopped — everything halts, including background media sync
scan differs from browser.dryRun, which still discovers and pays for AI
screening before suppressing the send.
The mode persists in runtime_state.bot_mode; a migration backfills it
from bot_running, and rows written by an older build still read
correctly. Both loops now gate on the mode, the account runtime loop
reconciles background media sync on transitions only, and the dashboard
and agent API expose all three states.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The `conversations` section was only reachable by editing config.yaml, which
production ignores: config is database-backed and the YAML is imported once,
when the database has not yet been seeded. `AdvancedConfigSchema` is also a
strict object, so a PUT carrying the section was rejected outright.
- Add `conversations` to the advanced config schema and payload, so it appears
in the dashboard's advanced JSON editor and survives a save.
- Apply the edited section field by field rather than replacing the objects,
and read the flags through to the live config from the account runtime, so a
save applies to a running account without a restart.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Seller detection on replies was a case-insensitive substring match over the
configured keyword list, and every hit landed in `human-required` alongside
genuine prospects that need a person. Off-platform pushes were only checked
for r4r policy modes, as were the under-18 signals.
Three layers now:
- New `off-platform-signals` module matches on normalized text (unicode
folding, zero-width stripping, leet mapping, separator removal), so
`0nlyf4ns`, `t3l3gr4m` and `t e l e g r a m` are caught. Ambiguous terms
need context: `snap` on its own stays a photo request, never a kill.
- New terminal `killed` automation status. It records the evidence, sets the
durable seller flag, supersedes the pending claim, and resolves triage to
`done`, so killed conversations leave every actionable queue. Off-platform
kills are independent of `reddit.filterSellers`.
- Optional AI screening of inbound replies behind
`conversations.aiReplyScreening` (off by default). Runs only after the
deterministic gates pass, memoizes per inbound event, and fails open.
Under-18 and in-person/contact-detail signals now apply to every policy mode
rather than r4r only, and remain human handoffs rather than kills.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>