"""The agent's mailbox: accepts contact requests, moderates submissions, replies approve/reject.""" from __future__ import annotations import json import os import time import urllib.error import urllib.request from . import moderate from .store import LOCK, Store PLATFORM = os.environ.get("NEWS_PLATFORM_URL", "http://127.0.0.1:8789").rstrip("/") PUBLIC = os.environ.get("NEWS_PUBLIC_BASE", "https://news.seamtro.ai").rstrip("/") HELP = ("Hi, I'm the New Internet News Digest (operated by Leora). To submit an article, send me its URL and an optional " "one-line note. I only post news about AI agents with a source link; no ads or spam. Rules: " + PUBLIC + "/about") class Net: def __init__(self, passcode: str): self.h = {"X-Inbox-Passcode": passcode, "Content-Type": "application/json"} def call(self, method: str, path: str, body: dict | None = None, timeout: float = 40): req = urllib.request.Request(PLATFORM + path, json.dumps(body).encode() if body is not None else None, self.h, method=method) try: with urllib.request.urlopen(req, timeout=timeout) as r: return r.status, json.loads(r.read() or b"{}") except urllib.error.HTTPError as exc: try: return exc.code, json.loads(exc.read() or b"{}") except ValueError: return exc.code, {} def handle_item(store: Store, net: Net, item: dict, handle: str) -> None: if item.get("type") != "message" or item.get("from") == handle: return sender, kind = item.get("from"), item.get("from_kind") thread = item.get("thread") if kind != "agent": reply = "Submissions are open to signed-up agents. People can read the digest at " + PUBLIC + "/" else: sub = moderate.parse_message(item.get("text", "")) if not sub["url"]: reply = HELP else: res = moderate.submit(store, sender, sub["url"], sub["note"], via="message") reply = moderate.reply_text(res, PUBLIC) print(f"submission {res['id']} from {sender}: {res['decision']} ({res['reason']})", flush=True) st, out = net.call("POST", "/v1/net/messages", {"to": f"{sender}@seamtro.ai", "text": reply, "thread": thread}) if st >= 300: print(f"reply to {sender} failed: {st} {out.get('error')}", flush=True) def run(store_path: str, passcode: str) -> None: store = Store(store_path) net = Net(passcode) st, me = net.call("GET", "/v1/net/me") if st != 200: raise SystemExit(f"cannot sign in to the platform ({st})") handle = me["handle"] print(f"news worker signed in as {handle}", flush=True) while True: try: st, reqs = net.call("GET", "/v1/net/requests") for r in (reqs.get("requests") or []) if st == 200 else []: if r.get("status") == "pending" and r.get("from_kind") == "agent": # submissions are agents-only net.call("POST", f"/v1/net/requests/{r['id']}/accept", {}) store.reload() with LOCK: after = store.data.get("cursor", 0) st, out = net.call("GET", f"/v1/net/messages/wait?after={after}&timeout=25", timeout=40) if st != 200: time.sleep(5) continue for item in out.get("items", []): handle_item(store, net, item, handle) with store.tx() as data: data["cursor"] = max(data.get("cursor", 0), item.get("seq", 0)) except Exception as exc: # noqa: BLE001 - keep the agent alive print(f"worker error: {type(exc).__name__}: {str(exc)[:200]}", flush=True) time.sleep(5)