cbeb925f0a
Every Buzz reply opened a fresh thread, including when the user was already replying inside one. A threaded client fills up with an endless ladder of one-message threads and the conversation becomes unreadable. The adapter itself never had threading logic; the behaviour comes from the generic gateway default. `_reply_anchor_for_event()` in gateway/platforms/base.py returns `event.message_id`, which is right for reply-style platforms (Telegram/Discord "reply to this message") but wrong for a thread-style one: anchoring to the message you are answering nests a new sub-thread under every single turn. Buzz threads are NIP-10, so the information needed is already on the inbound event. The adapter now records each inbound message's thread root from its `e` tags and resolves the outbound anchor to that root, so a reply joins the thread the user is typing in. When the trigger was itself top-level there is no root and the anchor passes through unchanged, preserving the existing behaviour of opening exactly one thread from a top-level message. Fixed in the adapter rather than in `_reply_anchor_for_event()`: Buzz is a plugin-supplied platform, and its NIP-10 tag semantics do not belong in core. Root extraction prefers an explicit `root` marker, falls back to a lone `reply` marker (a message bearing only `reply` started the thread, so that parent is the root for everything after it), and treats a legacy unmarked `e` tag as the parent. A mention-only `p` tag is not a reply. The root cache is an OrderedDict bounded at 512 entries with FIFO eviction so a long-lived gateway cannot leak, and the resolver is applied to the image send path as well as `send()`. Both helpers tolerate a missing `_thread_roots` attribute, since the standalone/cron send path constructs an adapter without running `__init__`. Tests cover root extraction (top-level, thread opener, nested, legacy unmarked tag), the top-level passthrough that guards the existing behaviour, unknown/None anchors, cache bounding and eviction, and an end-to-end assertion through `send()` that `--reply-to` carries the root. Verified against the real event shapes returned by a live hosted relay.