agnes-the-ai-analyst/services/telegram_bot/dispatch.py
Vojtech 38f6b639d2
feat(observability): request_id end-to-end + dev debug toolbar + centralized logging (#136)
Cuts release 0.20.0.

## Highlights
- X-Request-ID header on every response + sanitized to [A-Za-z0-9_-] (CRLF log-forging mitigation)
- Error pages (HTML + JSON 500) surface request_id for support tickets
- Dev debug toolbar gated by DEBUG=1 — fastapi-debug-toolbar with custom DuckDBPanel
- Centralized app.logging_config.setup_logging() replaces 23 scattered basicConfig calls
- Telegram bot drops bot.log file — stdout only (BREAKING)

## Devin findings addressed
- BUG_0001: .env.template no longer claims FastAPI debug=True
- BUG_0002: subprocess extractor logs INFO to stderr again
- ANALYSIS_0003: _wants_html no longer matches Accept: */* (curl gets JSON as before)
- BUG on b1c6ee9: HTML 500 page no longer leaks str(exc) in production
- BUG on b13d2fe: 2 CLAUDE.md compliance flags (transform.py + ws_gateway) accepted as scope-limited logging refactor — follow-up to update CLAUDE.md if needed

See CHANGELOG [0.20.0] for full notes.
2026-04-29 22:54:21 +02:00

44 lines
1.5 KiB
Python

"""
Shared notification dispatch to WebSocket gateway.
Used by both the Telegram bot and the webapp REST API to push
notifications to connected desktop app clients.
"""
import logging
import os
import time
import uuid
logger = logging.getLogger(__name__)
WS_GATEWAY_SOCKET_PATH = os.environ.get("WS_GATEWAY_SOCKET", "/run/ws-gateway/ws.sock")
def dispatch_to_ws_gateway(username: str, output: dict, script_name: str) -> None:
"""Dispatch notification to WebSocket gateway for desktop app clients."""
if not os.path.exists(WS_GATEWAY_SOCKET_PATH):
return
try:
import httpx
transport = httpx.HTTPTransport(uds=WS_GATEWAY_SOCKET_PATH)
with httpx.Client(transport=transport, timeout=10) as client:
notification = {
"id": str(uuid.uuid4()),
"title": output.get("title", ""),
"message": output.get("message", ""),
"script": script_name,
"timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
}
image_path = output.get("image_path", "")
if image_path and os.path.isfile(image_path):
filename = os.path.basename(image_path)
notification["image_url"] = f"/api/notifications/images/{filename}"
client.post(
"http://localhost/dispatch",
json={"user": username, "notification": notification},
)
except Exception:
logger.exception("WS gateway dispatch failed")