Fix bugs: MeshMessage position field, summary loading, Google system prompt, import placement, cleanup timer

- 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>
This commit is contained in:
Ubuntu 2026-02-23 20:11:46 +00:00
commit c1f2c48494
3 changed files with 53 additions and 13 deletions

View file

@ -162,6 +162,12 @@ class GoogleBackend(LLMBackend):
)
try:
# Create model with system instruction for persistent system prompt
model = genai.GenerativeModel(
self.config.model,
system_instruction=enhanced_system if enhanced_system else None,
)
# Convert messages to Gemini format
# Gemini uses "user" and "model" roles
history = []
@ -170,15 +176,11 @@ class GoogleBackend(LLMBackend):
history.append({"role": role, "parts": [msg["content"]]})
# Start chat with history
chat = self._model.start_chat(history=history)
chat = model.start_chat(history=history)
# Get the last user message
last_message = final_messages[-1]["content"] if final_messages else ""
# Prepend system prompt to first message if needed
if enhanced_system and not history:
last_message = f"{enhanced_system}\n\n{last_message}"
# Generate response
response = await chat.send_message_async(
last_message,

View file

@ -2,7 +2,7 @@
import asyncio
import logging
from dataclasses import dataclass
from dataclasses import dataclass, field
from typing import Callable, Optional
import meshtastic
@ -26,13 +26,12 @@ class MeshMessage:
channel: int # Channel index
is_dm: bool # True if direct message to us
packet: dict # Raw packet for additional data
_position: Optional[tuple[float, float]] = field(default=None, repr=False, init=False)
@property
def sender_position(self) -> Optional[tuple[float, float]]:
"""Get sender's GPS position if available (lat, lon)."""
# Position comes from node info, not the message itself
# This will be populated by the connector if available
return self._position if hasattr(self, "_position") else None
return self._position
class MeshConnector:

View file

@ -3,6 +3,7 @@
import argparse
import asyncio
import logging
import os
import signal
import sys
import time
@ -18,6 +19,7 @@ from .commands.status import set_start_time
from .config import Config, load_config
from .connector import MeshConnector, MeshMessage
from .history import ConversationHistory
from .memory import ConversationSummary
from .responder import Responder
from .router import MessageRouter, RouteType
@ -37,6 +39,7 @@ class MeshAI:
self.responder: Optional[Responder] = None
self._running = False
self._loop: Optional[asyncio.AbstractEventLoop] = None
self._last_cleanup: float = 0.0
async def start(self) -> None:
"""Start the bot."""
@ -52,6 +55,7 @@ class MeshAI:
self._running = True
self._loop = asyncio.get_event_loop()
self._last_cleanup = time.time()
# Write PID file
self._write_pid()
@ -63,8 +67,9 @@ class MeshAI:
await asyncio.sleep(1)
# Periodic cleanup
if int(time.time()) % 3600 == 0: # Every hour
if time.time() - self._last_cleanup >= 3600:
await self.history.cleanup_expired()
self._last_cleanup = time.time()
async def stop(self) -> None:
"""Stop the bot."""
@ -121,6 +126,9 @@ class MeshAI:
self.config.llm, api_key, window_size, summarize_threshold
)
# Load persisted summaries into memory cache
await self._load_summaries()
# Meshtastic connector
self.connector = MeshConnector(self.config.connection)
@ -183,6 +191,40 @@ class MeshAI:
except Exception as e:
logger.error(f"Error handling message: {e}", exc_info=True)
async def _load_summaries(self) -> None:
"""Load persisted summaries from database into memory cache."""
memory = self.llm.get_memory()
if not memory:
return
if not self.history or not self.history._db:
return
try:
async with self.history._lock:
cursor = await self.history._db.execute(
"SELECT user_id, summary, message_count, updated_at "
"FROM conversation_summaries"
)
rows = await cursor.fetchall()
loaded = 0
for row in rows:
user_id, summary_text, message_count, updated_at = row
summary = ConversationSummary(
summary=summary_text,
last_updated=updated_at,
message_count=message_count,
)
memory.load_summary(user_id, summary)
loaded += 1
if loaded:
logger.info(f"Loaded {loaded} conversation summaries from database")
except Exception as e:
logger.warning(f"Failed to load summaries from database: {e}")
def _write_pid(self) -> None:
"""Write PID file."""
pid_file = Path("/tmp/meshai.pid")
@ -195,9 +237,6 @@ class MeshAI:
pid_file.unlink()
import os
def setup_logging(verbose: bool = False) -> None:
"""Configure logging."""
level = logging.DEBUG if verbose else logging.INFO