mirror of
https://github.com/zvx-echo6/meshai.git
synced 2026-06-11 01:14:45 +02:00
feat(notifications): alert routing with channels, rules, and delivery
- Notification pipeline: categories -> rules -> channels - Channels: mesh broadcast, mesh DM, email (SMTP), webhook (generic) - Per-rule severity threshold and category filtering - Quiet hours with emergency override - LLM summarization for mesh delivery over 200 chars only - !subscribe shows available categories, easy mesh subscription - Dashboard notification rules API endpoints - Extensible channel system for future transports (Winlink, JS8Call) - config.yaml notification section with examples Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
f8874104ad
commit
3bf5e3dfbc
12 changed files with 1215 additions and 105 deletions
|
|
@ -44,6 +44,7 @@ class MeshAI:
|
|||
self.mesh_reporter = None
|
||||
self.subscription_manager = None
|
||||
self.alert_engine = None
|
||||
self.notification_router = None
|
||||
self.env_store = None # Environmental feeds store
|
||||
self._last_sub_check: float = 0.0
|
||||
self.router: Optional[MessageRouter] = None
|
||||
|
|
@ -337,6 +338,18 @@ class MeshAI:
|
|||
)
|
||||
logger.info(f"Alert engine initialized (critical: {mi.critical_nodes}, channel: {mi.alert_channel})")
|
||||
|
||||
|
||||
# Notification router
|
||||
if self.config.notifications.enabled:
|
||||
from .notifications.router import NotificationRouter
|
||||
self.notification_router = NotificationRouter(
|
||||
config=self.config.notifications,
|
||||
connector=self.connector,
|
||||
llm_backend=self.llm,
|
||||
timezone=self.config.timezone,
|
||||
)
|
||||
logger.info("Notification router initialized")
|
||||
|
||||
# Environmental feeds
|
||||
env_cfg = self.config.environmental
|
||||
if env_cfg.enabled:
|
||||
|
|
@ -394,6 +407,7 @@ class MeshAI:
|
|||
health_engine=self.health_engine,
|
||||
subscription_manager=self.subscription_manager,
|
||||
env_store=self.env_store,
|
||||
notification_router=self.notification_router,
|
||||
)
|
||||
|
||||
# Message router
|
||||
|
|
@ -406,6 +420,7 @@ class MeshAI:
|
|||
health_engine=self.health_engine,
|
||||
mesh_reporter=self.mesh_reporter,
|
||||
env_store=self.env_store,
|
||||
notification_router=self.notification_router,
|
||||
)
|
||||
|
||||
# Responder
|
||||
|
|
@ -539,40 +554,48 @@ class MeshAI:
|
|||
if pid_file.exists():
|
||||
pid_file.unlink()
|
||||
|
||||
async def _dispatch_alerts(self, alerts: list[dict]) -> None:
|
||||
"""Dispatch alerts to subscribers and alert channel."""
|
||||
mi = self.config.mesh_intelligence
|
||||
alert_channel = getattr(mi, 'alert_channel', -1)
|
||||
|
||||
for alert in alerts:
|
||||
message = alert["message"]
|
||||
logger.info(f"ALERT: {message}")
|
||||
|
||||
# Send to alert channel if configured
|
||||
if alert_channel >= 0 and self.connector:
|
||||
try:
|
||||
self.connector.send_message(
|
||||
text=message,
|
||||
destination=None, # Broadcast
|
||||
channel=alert_channel,
|
||||
)
|
||||
logger.info(f"Alert sent to channel {alert_channel}")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to send channel alert: {e}")
|
||||
|
||||
# Send DMs to matching subscribers
|
||||
if self.alert_engine and self.subscription_manager:
|
||||
subscribers = self.alert_engine.get_subscribers_for_alert(alert)
|
||||
for sub in subscribers:
|
||||
user_id = sub["user_id"]
|
||||
try:
|
||||
await self._send_sub_dm(user_id, message)
|
||||
logger.info(f"Alert DM sent to {user_id}: {alert['type']}")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to send alert DM to {user_id}: {e}")
|
||||
|
||||
self.alert_engine.clear_pending()
|
||||
|
||||
async def _dispatch_alerts(self, alerts: list[dict]) -> None:
|
||||
"""Dispatch alerts to subscribers and alert channel."""
|
||||
mi = self.config.mesh_intelligence
|
||||
alert_channel = getattr(mi, 'alert_channel', -1)
|
||||
|
||||
for alert in alerts:
|
||||
message = alert["message"]
|
||||
logger.info(f"ALERT: {message}")
|
||||
|
||||
# Route through notification router if enabled
|
||||
if self.notification_router:
|
||||
try:
|
||||
await self.notification_router.process_alert(alert)
|
||||
except Exception as e:
|
||||
logger.error(f"Notification router error: {e}")
|
||||
|
||||
# Fallback: Send to alert channel if no notification router
|
||||
elif alert_channel >= 0 and self.connector:
|
||||
try:
|
||||
self.connector.send_message(
|
||||
text=message,
|
||||
destination=None,
|
||||
channel=alert_channel,
|
||||
)
|
||||
logger.info(f"Alert sent to channel {alert_channel}")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to send channel alert: {e}")
|
||||
|
||||
# Fallback: Send DMs to matching subscribers
|
||||
if self.alert_engine and self.subscription_manager:
|
||||
subscribers = self.alert_engine.get_subscribers_for_alert(alert)
|
||||
for sub in subscribers:
|
||||
user_id = sub["user_id"]
|
||||
try:
|
||||
await self._send_sub_dm(user_id, message)
|
||||
logger.info(f"Alert DM sent to {user_id}: {alert['type']}")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to send alert DM to {user_id}: {e}")
|
||||
|
||||
if self.alert_engine:
|
||||
self.alert_engine.clear_pending()
|
||||
|
||||
async def _check_scheduled_subs(self) -> None:
|
||||
"""Check for and deliver due scheduled reports."""
|
||||
from datetime import datetime
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue