"""Receive signed notifications without publishing inbound agent messages.""" import asyncio import os from pathlib import Path from typing import Any from nanobot.bus.events import OutboundMessage from nanobot.channels.base import BaseChannel from nanobot.config.schema import Base from .receiver import create_server class NotifyConfig(Base): enabled: bool = False port: int = 8787 target_channel: str = "" target_chat_id: str = "" secret_env: str = "PREMSIR_NOTIFICATION_SECRET" state: str = "~/.nanobot/premsir-notifications.sqlite3" class PremsirNotifyChannel(BaseChannel): name = "premsir_notify" display_name = "Premsir Notifications" def __init__(self, config: Any, bus): if isinstance(config, dict): config = NotifyConfig(**config) super().__init__(config, bus) self._server = None @classmethod def default_config(cls): return NotifyConfig().model_dump(by_alias=True) async def start(self): if not self.config.target_channel or self.config.target_channel == self.name or not self.config.target_chat_id: raise ValueError("Configure an existing IM channel and exact chat ID") loop = asyncio.get_running_loop() def accept(event): # Delivery means accepted into the gateway outbound queue, not an LLM turn. message = OutboundMessage(channel=self.config.target_channel, chat_id=self.config.target_chat_id, content=event["text"], metadata={"premsir_event_id": event["id"]}) future = asyncio.run_coroutine_threadsafe(self.bus.publish_outbound(message), loop) try: future.result(timeout=5) except Exception: future.cancel() raise self._server = create_server(os.environ.get(self.config.secret_env, ""), Path(self.config.state).expanduser(), accept, self.config.port) self._running = True try: await asyncio.to_thread(self._server.serve_forever) finally: self._server.server_close() async def stop(self): self._running = False if self._server: await asyncio.to_thread(self._server.shutdown) async def send(self, msg): raise RuntimeError("Premsir receiver is not an IM destination")