feat: RSO observation system, child safety, Discord adapter, Telegram watchdog, email attachments
Core agent improvements: - RSO (Relevance Scoring & Observation) system: interaction_logger, memory_scorer, signal_detector - Memory access logging (memory_access_log table) for relevance scoring; high-signal turn detection - Rich conversation storage for notable turns; compact_conversation truncates long user messages - Task-type classifier (query/action/analysis/creative) for observation tagging - Nested sub-agent visibility: deep delegations now register against the main agent's manager Child safety (Gabriel profile): - child_safety.py: filtering, audit logging, prompt constants for restricted sessions - .kiro/specs/child-safety-profile: requirements, design, tasks specs - GABRIEL_BOT_PROPOSAL.md: initial proposal doc - Reduced context window (10 msgs) and tutor-mode identity for restricted users Telegram adapter: - Polling watchdog: auto-restarts updater if polling drops unexpectedly - get_me() with exponential-backoff retry on NetworkError at startup - Correct stop() ordering: signal watchdog before cancelling tasks Email / Gmail: - send_email: supports file attachments (attachments list param) - get_email: surfaces attachment metadata in response Scheduled tasks / weather: - Remove OpenWeatherMap API calls from morning-weather task; use wttr.in exclusively - New scheduled tasks and scheduler state persistence Discord: - adapters/discord/__init__.py scaffold - discord-plugin: MCP plugin for Claude Code Discord integration (server.ts, skills, config) Infrastructure: - n8n workflow exports (garvis_webhook, content_pipeline variants) - memory_workspace: context, homelab-repo-updates, weekly observation summaries, error logs - UCS C240 migration plan doc - requirements.txt: new deps - .claude/settings.json, fix_hooks.py: hook/permission tuning
This commit is contained in:
@@ -17,6 +17,7 @@ import asyncio
|
||||
import signal
|
||||
import traceback
|
||||
from dotenv import load_dotenv
|
||||
from telegram.error import NetworkError as TelegramNetworkError
|
||||
|
||||
# Load environment variables from .env file
|
||||
load_dotenv()
|
||||
@@ -85,15 +86,35 @@ class BotRunner:
|
||||
|
||||
self.runtime = AdapterRuntime(self.agent)
|
||||
|
||||
# Wire child safety filter (no-op if child_safety block is absent from config)
|
||||
try:
|
||||
from pathlib import Path as _Path
|
||||
from child_safety import ChildSafetyConfig, ChildSafetyFilter, ChildAuditLogger
|
||||
_cs_config = ChildSafetyConfig.from_yaml(_Path("config/adapters.local.yaml"))
|
||||
if _cs_config:
|
||||
_cs_audit = ChildAuditLogger(_Path("./memory_workspace"))
|
||||
_cs_audit.cleanup_old_logs(_cs_config.audit_retention_days)
|
||||
_cs_filter = ChildSafetyFilter(_cs_config, _cs_audit)
|
||||
self.runtime.add_preprocessor(_cs_filter.preprocess_adapter)
|
||||
self.runtime.add_postprocessor(_cs_filter.postprocess_adapter)
|
||||
print(f"[Setup] Child safety filter active for: {_cs_config.restricted_users}")
|
||||
except Exception as _e:
|
||||
print(f"[Setup] Child safety filter not loaded: {_e}")
|
||||
|
||||
enabled_count = sum(
|
||||
self._load_adapter(platform)
|
||||
for platform in _ADAPTER_CLASSES
|
||||
)
|
||||
|
||||
# Load user mappings
|
||||
# Config format: "platform_userid" — runtime lookup format: "platform:userid"
|
||||
user_mapping = self.config_loader.get_user_mapping()
|
||||
for platform_user_id, username in user_mapping.items():
|
||||
self.runtime.map_user(platform_user_id, username)
|
||||
if "_" in platform_user_id:
|
||||
platform, uid = platform_user_id.split("_", 1)
|
||||
self.runtime.map_user(f"{platform}:{uid}", username)
|
||||
else:
|
||||
self.runtime.map_user(platform_user_id, username)
|
||||
print(f"[Setup] User mapping: {platform_user_id} -> {username}")
|
||||
|
||||
if enabled_count == 0:
|
||||
@@ -147,30 +168,54 @@ class BotRunner:
|
||||
print("Starting bot...")
|
||||
print("=" * 60 + "\n")
|
||||
|
||||
try:
|
||||
await self.runtime.start()
|
||||
_startup_delays = [10, 30, 60, 120, 300] # backoff between full restart attempts
|
||||
|
||||
# Start scheduler if configured
|
||||
if self.scheduler:
|
||||
self.scheduler.start()
|
||||
print("[Scheduler] Task scheduler started\n")
|
||||
for attempt, delay in enumerate([0] + _startup_delays, 0):
|
||||
if self.shutdown_event.is_set():
|
||||
break
|
||||
if attempt > 0:
|
||||
print(f"\n[Reconnect] Waiting {delay}s before restart attempt {attempt}...")
|
||||
try:
|
||||
await asyncio.wait_for(self.shutdown_event.wait(), timeout=delay)
|
||||
break # Shutdown was requested during the wait
|
||||
except asyncio.TimeoutError:
|
||||
pass
|
||||
print(f"[Reconnect] Restarting adapters (attempt {attempt})...")
|
||||
|
||||
print("=" * 60)
|
||||
print("Bot is running! Press Ctrl+C to stop.")
|
||||
print("=" * 60 + "\n")
|
||||
try:
|
||||
await self.runtime.start()
|
||||
|
||||
# Wait for shutdown signal
|
||||
await self.shutdown_event.wait()
|
||||
# Start scheduler if configured
|
||||
if self.scheduler:
|
||||
self.scheduler.start()
|
||||
print("[Scheduler] Task scheduler started\n")
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n[Error] {e}")
|
||||
traceback.print_exc()
|
||||
finally:
|
||||
if self.scheduler:
|
||||
self.scheduler.stop()
|
||||
print("[Scheduler] Task scheduler stopped")
|
||||
await self.runtime.stop()
|
||||
print("\n[Shutdown] Bot stopped cleanly")
|
||||
print("=" * 60)
|
||||
print("Bot is running! Press Ctrl+C to stop.")
|
||||
print("=" * 60 + "\n")
|
||||
|
||||
# Wait for shutdown signal
|
||||
await self.shutdown_event.wait()
|
||||
break # Clean shutdown — don't retry
|
||||
|
||||
except TelegramNetworkError as e:
|
||||
print(f"\n[Reconnect] Telegram network error during startup: {e}")
|
||||
if attempt >= len(_startup_delays):
|
||||
print("[Reconnect] Max retries reached. Giving up.")
|
||||
traceback.print_exc()
|
||||
break
|
||||
# Will retry in next loop iteration
|
||||
except Exception as e:
|
||||
print(f"\n[Error] {e}")
|
||||
traceback.print_exc()
|
||||
break # Non-network errors are not retried
|
||||
finally:
|
||||
if self.scheduler:
|
||||
self.scheduler.stop()
|
||||
print("[Scheduler] Task scheduler stopped")
|
||||
await self.runtime.stop()
|
||||
|
||||
print("\n[Shutdown] Bot stopped cleanly")
|
||||
|
||||
async def health_check(self) -> None:
|
||||
"""Check health of all adapters."""
|
||||
|
||||
Reference in New Issue
Block a user