- Add _extract_node_num() helper to normalize node IDs across formats
- Add _normalize_edge_key() helper for undirected edge deduplication
- Update get_all_nodes() to deduplicate by nodeNum with _sources field
- Update get_all_edges() to deduplicate by sorted (from, to) tuple
- Update get_all_telemetry() to deduplicate by (nodeNum, timestamp)
- Add get_dedup_stats() method for raw vs dedup counts
- Add get_stats_by_source() method for per-source statistics
- Change _source field to _sources (list) for multi-source tracking
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
10 regions covering Idaho and Utah:
- Northern ID (Coeur d alene)
- Central ID (Salmon)
- South Western ID (Boise)
- South Central ID (Twin Falls)
- South Eastern ID (Idaho Falls)
- Northern UT (Ogden)
- Central UT (Salt Lake City)
- Eastern UT (Vernal)
- Western UT (Tooele)
- Southern UT (St. George)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Nodes without GPS can be assigned to a region based on their
neighbors. Uses edge data to build neighbor graph, then assigns
unlocated nodes to the most common region among their neighbors.
Iterates until no more assignments possible.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Regions are now user-defined anchor points (name + lat/lon)
- Nodes assigned to nearest region, no distance limits
- Removed auto-naming and region_labels/infra_overrides
- Added Idaho region defaults in TUI
- Simpler, deterministic, user-controlled
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- last_lat/last_long are scaled integers (divide by 1e7)
- last_seen_us is in microseconds (divide by 1e6)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Meshview API returns {"nodes": [...]} and {"edges": [...]} wrapper
dicts, not raw lists. Added _extract_list() helper to unwrap.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Adds MeshAI/x.x User-Agent for good API citizenship and easier
traffic identification by Meshview/MeshMonitor operators.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Implements mesh intelligence with geo clustering, four-pillar health scoring,
and auto-naming regions from GPS data.
New: geo.py, mesh_health.py
Modified: config.py, main.py, router.py, configurator.py, config.example.yaml
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add MeshviewSource class for fetching nodes, edges, stats from Meshview API
- Add MeshMonitorDataSource class for fetching nodes, channels, telemetry,
traceroutes, network stats, topology, packets, solar from MeshMonitor API
- Add MeshSourceManager for managing multiple sources with aggregation
- Add MeshSourceConfig dataclass and mesh_sources list to config
- Integrate source_manager into main.py with periodic refresh
- Add source_manager parameter to MessageRouter (for future Phase 3)
- Add Mesh Sources TUI menu with add/edit/remove/test functionality
- Update config.example.yaml with mesh_sources section
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add MeshMonitor Integration section with features and configuration
- Add credit line in Features list
- Link to github.com/Yeraze/meshmonitor
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Build system prompt dynamically using bot.name and bot.owner from config
- Reorder prompt: identity -> static prompt -> MeshMonitor (conditional) -> mesh context
- MeshMonitor description only injected when meshmonitor.enabled is true
- Update default system_prompt to static parts only (commands, architecture, rules)
- Fix meshmonitor.py to handle trigger arrays (not just strings)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add MeshMonitor Sync as main menu option 9
- Toggle enabled/disabled, triggers file path, inject_into_prompt
- View Triggers shows loaded patterns from triggers.json
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add MeshMonitorSync class that reads trigger patterns from a JSON file
and compiles them to regex. The router checks incoming messages against
these patterns and ignores messages that MeshMonitor will handle.
- New meshai/meshmonitor.py: Pattern compilation and file watching
- MeshMonitorConfig dataclass with enabled, triggers_file, inject_into_prompt
- Router integration: ignore matching messages, inject commands into prompt
- Main loop refresh: watch triggers file for changes without restart
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
All three LLM backends (Google, OpenAI, Anthropic) now wrap API calls
in asyncio.wait_for() using config.timeout (default 30s). Previously
Gemini could hang indefinitely with grounding+AFC enabled.
Router catches TimeoutError with user-friendly "request timed out" message.
Empty context buffer now injects "[No recent mesh traffic observed yet.]"
so the LLM knows the capability exists even when buffer is empty.
Default system prompt updated to mention mesh awareness.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Unknown !commands (like !mail, !login, !bbs from advBBS users) were
getting "Unknown command, try !help" responses. Now returns None so
the bot stays silent on commands it doesn't recognize.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New context.py module: ring buffer (50K hard cap, ~25MB ceiling) passively
records all channel broadcasts. Observations are formatted with relative
timestamps and injected into the system prompt when generating LLM responses.
Only public channel traffic is observed; DMs to the bot are excluded (already
in per-user history). Bot's own node ID is auto-added to ignore list.
Config: context.enabled, observe_channels, ignore_nodes, max_age, max_context_items
TUI: new Context settings submenu (menu item 7)
Hourly prune removes expired observations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
MeshAI is now DM-only. Removed all unreachable channel response
paths, @mention detection, ChannelsConfig, and channel TUI menu.
Fixed restart mechanism with integrated watcher and SIGKILL fallback.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Mesh messages are short enough that summarization is unnecessary
with Gemini Flash's 1M token context window.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Delete unused CommandResult class from commands/base.py and unused
format_dm_response method from responder.py.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Removed dead menu items that referenced deleted config sections.
Renumbered menu options. Added BBS filter toggle to bot settings.
Added missing _handle_exit method.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
FallbackBackend was never configured in practice. generate_with_search
was an unused abstract method on all backends.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
These modules were wired up but never actually functional in the
running bot. Strips all imports and usage from main.py and router.py.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
These were LLM-generated planning artifacts from the memory
implementation phase. Not user-facing documentation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- 7a: Config.get_system_prompt() now logs a deprecation warning.
PersonalityManager.get_system_prompt() is the canonical source (wired
in commit 4). LLMConfig.system_prompt kept for backwards compat as
fallback when personality is not configured.
- 7b: Fix AnnouncementScheduler callback type from asyncio.coroutine
(a decorator, not a type) to Awaitable[None] from typing
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- 6a: Change healthcheck in Dockerfile and docker-compose.yml to verify
the PID file exists and the process is alive (kill -0) instead of
testing SQLite connectivity, which only proves the DB file exists
- 6b: In DMs, skip !commands so MeshMonitor or other bots handle them.
MeshAI only responds conversationally in DMs (no bang commands)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- 5a: Import html.escape and apply to all values rendered into the
HTML template in _serve_status_page() — uptime, counts, status text,
node counts, errors. Prevents XSS via crafted node names or errors.
- 5b: Add basic prompt injection detection to _clean_query() with
configurable safety.prompt_injection_guard (default: on). Detects
patterns like "ignore all previous", "you are now", "system prompt:",
etc. Truncates query before the injection phrase and logs a warning.
Not foolproof but better than nothing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- 2a: SafetyFilter + UserFilter — check user access before processing,
filter LLM responses through SafetyFilter before sending
- 2b: RateLimiter — check rate limits before processing, record
messages after successful response delivery
- 2c: PersonalityManager — pass to MessageRouter, used for system
prompt generation instead of raw config.llm.system_prompt
- 2d: WebhookClient — start/stop in lifecycle, fire events on
message_received, response_sent, error, startup, shutdown
- 2e: WebStatusServer — start/stop in lifecycle, record messages,
responses, and errors in StatusData
- 2f: AnnouncementScheduler — start/stop in lifecycle, uses
connector.send_message as callback
- 2g: FallbackBackend — wrap primary backend when config.llm.fallback
is configured, otherwise use primary directly
- 2h: CommandDispatcher — pass prefix, disabled_commands, and
custom_commands from config to create_dispatcher()
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
RollingSummaryMemory now accepts a generic async summarize callable
instead of hardcoding AsyncOpenAI. Each backend provides its own
_summarize_messages() method that uses the appropriate API client.
- Removed AnthropicMemory class from anthropic_backend.py
- Removed GoogleMemory class from google_backend.py
- Changed RollingSummaryMemory.__init__ signature to accept
summarize_fn: Callable[[list[dict]], Awaitable[str]]
- All three backends (OpenAI, Anthropic, Google) now instantiate
the same RollingSummaryMemory class with backend-specific callables
- Extracted shared summarize prompt to module-level _SUMMARIZE_PROMPT
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- 4a: Add threading.Lock to MeshConnector protecting _node_names and
_node_positions dicts that are read/written from Meshtastic's pubsub
thread callbacks (_on_receive, _on_node_update, _cache_node_info)
and read from async code (get_node_position, get_node_name)
- 4b: Add threading.Lock to StatusData protecting counters and activity
list that are written from the async event loop and read from the
HTTP server thread in to_dict()
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- 1a: Declare _position as proper dataclass field with field(default=None, init=False)
so hasattr() check isn't needed and the attribute always exists
- 1b: Load persisted conversation summaries from DB into memory cache on startup
via new _load_summaries() method called after backend creation
- 1c: Use Gemini's system_instruction parameter on GenerativeModel instead of
only prepending to first message, so system prompt persists across all turns
- 1d: Move 'import os' from line 198 to top of main.py with other imports
- 1e: Replace unreliable modulo-based cleanup timer with _last_cleanup timestamp
comparison that won't miss hours due to async sleep jitter
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Use PID file to track bot process instead of pkill pattern matching
- Add 3-second debounce after restart to prevent signal storms
- Properly distinguish bot process from config tool process
- Clean up PID file on bot exit