feat(meshcore): decouple per-mesh LLM DM gate + mesh-scoped chat context (#19)

- router.should_respond branches on message.transport: MeshCore DMs are
  governed solely by meshcore_context.respond_to_dms (enforced at the
  transport); Meshtastic solely by bot.respond_to_dms. No global gate.
- MeshObservation tagged per-transport; the LLM "recent traffic" block is
  scoped to the originating mesh (keyword override for the other mesh),
  labeled by mesh so the model knows which it is describing.
- MeshCore observe_channels is now opt-in (empty = observe none).
- Chat-context retention 30d -> 14d (both meshes).
- Meshtastic integer channel-index filter no longer misapplied to MeshCore
  observations (their channel is a companion slot index).
- Frontend: relabel DM toggles per-mesh ("Answer direct messages",
  Meshtastic-only / MeshCore-only), remove the false channel-mention
  tooltips, opt-in wording for MeshCore observe-channels.

Tests: +tests/test_llm_scoping.py (10), context-filter updated for opt-in;
0 new failures (34 pre-existing).

Co-authored-by: Matt Johnson <mj@k7zvx.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
malice 2026-07-03 18:39:26 -06:00 committed by GitHub
commit a191200a12
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 243 additions and 28 deletions

View file

@ -303,7 +303,7 @@ export default function MeshCoreConnection() {
</div>
)}
</div>
<p className="text-xs text-slate-600">Channels to monitor (none selected = observe all)</p>
<p className="text-xs text-slate-600">Choose which MeshCore channels feed MeshAI's context. Empty = none are watched pick channels to include their chatter in what the bot knows about the mesh. Leave busy/public channels out to keep them out of context.</p>
</div>
<ListInput
label="Ignore MeshCore Contacts"
@ -313,11 +313,10 @@ export default function MeshCoreConnection() {
info="Messages from these MeshCore contacts won't be included in passive context. Enter contact names or public-key prefixes."
/>
<Toggle
label="Respond to DMs"
label="Answer direct messages"
checked={!!mcContext.respond_to_dms}
onChange={(v) => setMcContext({ ...mcContext, respond_to_dms: v })}
helper="Reply when someone sends a MeshCore direct message"
info="When enabled, the bot responds to MeshCore direct messages. When disabled, it only responds to channel messages that mention its name."
helper="When on, MeshAI replies to MeshCore direct messages using the LLM. Applies to MeshCore only."
/>
</div>
)}

View file

@ -235,11 +235,10 @@ export default function MeshtasticConnection() {
info="Messages from these nodes won't be included in passive context. Useful for filtering out noisy automated nodes."
/>
<Toggle
label="Respond to DMs"
label="Answer direct messages"
checked={!!bot.respond_to_dms}
onChange={(v) => setBot({ ...bot, respond_to_dms: v })}
helper="Reply when someone sends a direct message"
info="When enabled, the bot responds to direct messages from any node. When disabled, the bot only responds to channel messages that mention its name."
helper="When on, MeshAI replies to Meshtastic direct messages using the LLM. Applies to Meshtastic only."
/>
</div>
)}