mirror of
https://github.com/andrewyng/openworker.git
synced 2026-09-03 04:49:26 +00:00
Imported from andrewyng/aisuite@1b4bbf303e (contents of its platform/ directory, hoisted to the repo root). Development history prior to this commit lives in that repository. Co-authored-by: Devika <devikaverma11@gmail.com>
68 lines
2.5 KiB
Python
68 lines
2.5 KiB
Python
"""Unrouted / dead-letter store — a durable record of inbound messages that had nowhere to go and
|
|
of background turns that failed, so neither vanishes silently.
|
|
|
|
Two producers:
|
|
- a DM (or any non-channel) inbound message with no designated session to handle it;
|
|
- a background turn (channel delivery, self-wake) that errored on an ERROR engine event.
|
|
|
|
JSON-backed and capped (newest kept), mirroring SubscriptionStore's persistence. This is a
|
|
visibility/debugging surface, not a queue — entries are read in the GUI, not redelivered.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import threading
|
|
import time
|
|
from dataclasses import asdict, dataclass, field
|
|
from pathlib import Path
|
|
from typing import Optional
|
|
|
|
|
|
@dataclass
|
|
class UnroutedItem:
|
|
source: str # message origin / session id (e.g. "slack:D123" or a session id)
|
|
sender: str # who sent it ("-" when not applicable, e.g. a turn failure)
|
|
text: str # the message (or the failing instruction)
|
|
reason: str # why it landed here ("no DM session designated", an error string, …)
|
|
ts: float = field(default_factory=time.time)
|
|
|
|
|
|
class UnroutedStore:
|
|
def __init__(self, path: Optional[str | Path] = None, *, cap: int = 200) -> None:
|
|
self.path = Path(path) if path else None
|
|
self._cap = cap
|
|
self._lock = threading.Lock()
|
|
self._items: list[UnroutedItem] = []
|
|
self._load()
|
|
|
|
def _load(self) -> None:
|
|
if self.path and self.path.is_file():
|
|
data = json.loads(self.path.read_text(encoding="utf-8"))
|
|
self._items = [UnroutedItem(**raw) for raw in data.get("items", [])]
|
|
|
|
def _save(self) -> None:
|
|
if not self.path:
|
|
return
|
|
self.path.parent.mkdir(parents=True, exist_ok=True)
|
|
self.path.write_text(
|
|
json.dumps({"items": [asdict(i) for i in self._items]}, indent=2),
|
|
encoding="utf-8",
|
|
)
|
|
|
|
def record(self, source: str, sender: str, text: str, reason: str) -> UnroutedItem:
|
|
item = UnroutedItem(
|
|
source=source or "?", sender=sender or "-", text=text or "", reason=reason
|
|
)
|
|
with self._lock:
|
|
self._items.append(item)
|
|
if len(self._items) > self._cap:
|
|
self._items = self._items[-self._cap :]
|
|
self._save()
|
|
return item
|
|
|
|
def list(self, n: int = 100) -> list[dict]:
|
|
"""Most-recent-first, for the GUI panel."""
|
|
items = list(reversed(self._items))[: max(1, n)]
|
|
return [asdict(i) for i in items]
|