fix(discord-bridge): zgomot in bot.log si doua tipuri de stream necunoscute

Trei lucruri observate in logurile de productie dupa restartul precedent.

1. Ecoul propriilor mesaje umplea bot.log cu WARNING. Propriile mesaje au si
   ele `author.bot == True`, iar verificarea generica de bot venea INAINTEA
   celei pe `self_id` — deci raspunsurile botului se jurnalizau ca "bot
   strain", la fiecare mesaj. Verificarea pe `self_id` trece prima (motivul e
   acum precis), iar refuzurile de rutina — propriile mesaje si ceilalti boti —
   merg la DEBUG. Guild / canal / utilizator strain si webhook raman WARNING:
   alea chiar sunt semnal de securitate si erau inecate in zgomot.

2. `tool_progress` (heartbeat la 30s cat timp o unealta ruleaza) devine
   eveniment `ToolProgress`. Mesajul live arata acum "⏳ ruleaza de 2m30s" sub
   unealta curenta — singurul semn ca un tur lung lucreaza si nu a inghetat.

3. `rate_limit_event` devine eveniment `RateLimit` si apare in `/status` la
   randul `utilizare`. Cum nu exista plafon de cost (abonament, nu API),
   fereastra de utilizare e singura limita reala; se avertizeaza in log o data
   per schimbare de stare, nu la fiecare eveniment.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B29CApsP1JkSdjYaGaHpE7
This commit is contained in:
Claude Agent
2026-08-31 07:46:51 +00:00
parent 1cc1a572d9
commit a89cf201ab
6 changed files with 243 additions and 8 deletions

View File

@@ -2,6 +2,8 @@
Reguli (T6):
- tip necunoscut -> se logeaza O SINGURA DATA per tip, cu versiunea CLI, si se ignora
(asa au fost descoperite `tool_progress` si `rate_limit_event` in claude 2.1.251;
cand un tip nou se dovedeste util, i se face un eveniment si iese din lista)
- linie non-JSON -> se logeaza si se ignora
- EOF inainte de `result` -> StreamEOFError, explicit, fara hang
Parser-ul NU arunca niciodata pe continut de stream; singura exceptie e EOF-ul de mai sus.
@@ -84,7 +86,41 @@ class Result:
raw: dict = field(default_factory=dict, repr=False)
Event = SystemInit | AssistantText | ToolUse | ToolResult | Result
@dataclass(frozen=True)
class ToolProgress:
"""Semn de viata pentru o unealta care ruleaza de mult (`tool_progress`).
CLI-ul il trimite periodic (heartbeat) cat timp o unealta e in executie. E
singurul semnal ca un tur lung inca lucreaza si nu a inghetat.
"""
tool_name: str
tool_id: str | None
elapsed_s: int
heartbeat: bool = False
session_id: str | None = None
raw: dict = field(default_factory=dict, repr=False)
@dataclass(frozen=True)
class RateLimit:
"""`rate_limit_event`: starea ferestrei de utilizare a abonamentului.
`status` e "allowed" cand totul e in regula; orice altceva inseamna ca
urmatoarele tururi pot fi franate sau refuzate.
"""
status: str
limit_type: str = ""
resets_at: int = 0
raw: dict = field(default_factory=dict, repr=False)
@property
def ok(self) -> bool:
return self.status == "allowed"
Event = SystemInit | AssistantText | ToolUse | ToolResult | ToolProgress | RateLimit | Result
def _blocks(msg: Any) -> list[dict]:
@@ -228,6 +264,38 @@ class StreamParser:
)
]
if typ == "tool_progress":
try:
elapsed = int(obj.get("elapsed_time_seconds") or 0)
except (TypeError, ValueError):
elapsed = 0
return [
ToolProgress(
tool_name=str(obj.get("tool_name") or "?"),
tool_id=obj.get("tool_use_id"),
elapsed_s=elapsed,
heartbeat=bool(obj.get("heartbeat")),
session_id=self.session_id,
raw=obj,
)
]
if typ == "rate_limit_event":
info = obj.get("rate_limit_info")
info = info if isinstance(info, dict) else {}
try:
resets = int(info.get("resetsAt") or 0)
except (TypeError, ValueError):
resets = 0
return [
RateLimit(
status=str(info.get("status") or "necunoscut"),
limit_type=str(info.get("rateLimitType") or ""),
resets_at=resets,
raw=obj,
)
]
key = str(typ)
if key not in self.unknown_types:
self.unknown_types.add(key)