The dispatcher deduped on inbound text but relayed a prefixed copy
("[net/sender] text"), so a re-heard relayed message hashed differently
and was re-relayed, stacking another prefix each hop — an unbounded loop,
exactly what the dedup was meant to prevent.
Add MessageBus.mark_seen() and record the on-air relayed string before
sending, so the bridge recognises and drops its own relayed copies.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
close() stopped the asyncio loop without disconnecting first, leaving the
EventDispatcher._process_events() task and the serial transport pending
("Task was destroyed but it is pending!" / "Event loop is closed").
Now await MeshCore.disconnect() and let the transport's connection_lost
callback settle before stopping the loop, then join the thread.
Found by running the adapter against a live MeshCore-flashed Heltec V3.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>