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:
@@ -107,6 +107,11 @@ class Decision:
|
||||
|
||||
ACCEPTED = Decision(True)
|
||||
|
||||
# Refuzuri de rutina: botul isi vede propriile mesaje si pe ale altor boti la
|
||||
# fiecare raspuns. Nu sunt semnal de securitate, deci merg la DEBUG — altfel
|
||||
# ineaca in bot.log refuzurile care chiar conteaza (guild/canal/utilizator strain).
|
||||
ROUTINE_REJECTS = ("propriul mesaj", "bot")
|
||||
|
||||
|
||||
def _attr(obj, name, default=None):
|
||||
return getattr(obj, name, default)
|
||||
@@ -141,11 +146,13 @@ def check_message(message, self_id: str | None = None) -> Decision:
|
||||
author = _attr(message, "author")
|
||||
if author is None:
|
||||
return Decision(False, "fara autor")
|
||||
if bool(_attr(author, "bot", False)):
|
||||
return Decision(False, "bot")
|
||||
# Verificarea `self_id` sta INAINTEA celei generice de bot: propriile mesaje au
|
||||
# si ele `author.bot == True`, iar altfel ar fi jurnalizate ca "bot strain".
|
||||
uid = str(_attr(author, "id", ""))
|
||||
if self_id is not None and uid == str(self_id):
|
||||
return Decision(False, "propriul mesaj")
|
||||
if bool(_attr(author, "bot", False)):
|
||||
return Decision(False, "bot")
|
||||
|
||||
guild = _attr(message, "guild")
|
||||
gid = _attr(guild, "id") if guild is not None else _attr(message, "guild_id")
|
||||
@@ -285,6 +292,16 @@ def rss_mb(pid: int | None) -> float:
|
||||
return 0.0
|
||||
|
||||
|
||||
def _reset_text(resets_at: int) -> str:
|
||||
"""Momentul resetarii ferestrei, in ora locala. 0 / valoare aiurea -> „?"."""
|
||||
try:
|
||||
if not resets_at:
|
||||
return "?"
|
||||
return time.strftime("%H:%M", time.localtime(int(resets_at)))
|
||||
except (TypeError, ValueError, OSError):
|
||||
return "?"
|
||||
|
||||
|
||||
def _tool_line(ev) -> str:
|
||||
if isinstance(ev, stream_mod.ToolUse):
|
||||
inp = ev.input or {}
|
||||
@@ -294,6 +311,13 @@ def _tool_line(ev) -> str:
|
||||
return ""
|
||||
|
||||
|
||||
def _progress_line(ev) -> str:
|
||||
"""`tool_progress` -> „ruleaza de 2m30s". Doar asta arata ca turul e viu."""
|
||||
secs = max(0, int(ev.elapsed_s or 0))
|
||||
human = f"{secs}s" if secs < 60 else f"{secs // 60}m{secs % 60:02d}s"
|
||||
return f"⏳ ruleaza de {human}"
|
||||
|
||||
|
||||
# --------------------------------------------------------------------- Bridge
|
||||
class Bridge:
|
||||
"""Logica adaptorului, fara nicio dependinta de discord.py."""
|
||||
@@ -323,6 +347,10 @@ class Bridge:
|
||||
self.last_result = None # ultimul `result` primit (diagnostic)
|
||||
self.steered = 0
|
||||
self.approvals_wired = False
|
||||
# Ultimul `rate_limit_event` din stream. Cum nu exista plafon de cost
|
||||
# (abonament, nu API), fereastra de utilizare e singura limita reala.
|
||||
self.rate_limit = None
|
||||
self._rate_limit_warned: str | None = None
|
||||
|
||||
# ------------------------------------------------------------ iesire
|
||||
async def _edit(self, target, text: str) -> None:
|
||||
@@ -455,7 +483,9 @@ class Bridge:
|
||||
if not decision.ok:
|
||||
# Refuz TACUT: nu raspundem, nu reactionam. Doar log local.
|
||||
self.rejected.append(decision.reason)
|
||||
log.warning(
|
||||
level = logging.DEBUG if decision.reason in ROUTINE_REJECTS else logging.WARNING
|
||||
log.log(
|
||||
level,
|
||||
"mesaj respins (%s): guild=%s canal=%s user=%s",
|
||||
decision.reason,
|
||||
_attr(_attr(message, "guild"), "id"),
|
||||
@@ -627,6 +657,11 @@ class Bridge:
|
||||
f"in coada : {'da' if self.limits.queued(thread_id) else 'nu'}",
|
||||
f"procese : {self.runner.live_count()} vii, {self.limits.free_slots} sloturi libere",
|
||||
f"cost fir : ${float(rec.get('cost_usd_total') or 0.0):.4f}",
|
||||
f"utilizare : " + (
|
||||
"in regula" if (self.rate_limit is None or self.rate_limit.ok)
|
||||
else f"⚠️ {self.rate_limit.status} ({self.rate_limit.limit_type or '?'}), "
|
||||
f"reset {_reset_text(self.rate_limit.resets_at)}"
|
||||
),
|
||||
f"cost azi : ${self.limits.cost_today():.4f}"
|
||||
+ (f" / ${cap:.2f}" if self.limits.cap_enabled else " (fara plafon)")
|
||||
+ (" ⛔ PLAFON ATINS" if self.limits.stopped() else ""),
|
||||
@@ -638,6 +673,19 @@ class Bridge:
|
||||
lines.append("stderr (ultimele linii):\n```\n" + body + "\n```")
|
||||
return "\n".join(lines)
|
||||
|
||||
def note_rate_limit(self, ev) -> None:
|
||||
"""Retine fereastra de utilizare; avertizeaza o data per schimbare de stare."""
|
||||
self.rate_limit = ev
|
||||
if ev.ok:
|
||||
self._rate_limit_warned = None
|
||||
return
|
||||
if self._rate_limit_warned != ev.status:
|
||||
self._rate_limit_warned = ev.status
|
||||
log.warning(
|
||||
"limita de utilizare: status=%s tip=%s reset=%s",
|
||||
ev.status, ev.limit_type or "?", _reset_text(ev.resets_at),
|
||||
)
|
||||
|
||||
async def cmd_permisiuni(self, message, tid: str, cmd: Command) -> None:
|
||||
await self.say(message.channel, self.permissions_text(tid, "revoca" in cmd.args))
|
||||
|
||||
@@ -729,22 +777,30 @@ class Bridge:
|
||||
placeholder = await channel.send("⏳ lucrez…")
|
||||
chunks: list[str] = []
|
||||
tools: list[str] = []
|
||||
progress = "" # ultimul heartbeat al uneltei curente, sters cand incepe alta
|
||||
loop = self.render.loop_for(str(tid))
|
||||
|
||||
async def on_event(ev) -> None:
|
||||
nonlocal progress
|
||||
if isinstance(ev, stream_mod.SystemInit) and ev.session_id:
|
||||
proc.sid = ev.session_id
|
||||
elif isinstance(ev, stream_mod.AssistantText):
|
||||
chunks.append(ev.text)
|
||||
elif isinstance(ev, stream_mod.ToolUse):
|
||||
tools.append(_tool_line(ev))
|
||||
progress = ""
|
||||
elif isinstance(ev, stream_mod.ToolProgress):
|
||||
progress = _progress_line(ev)
|
||||
elif isinstance(ev, stream_mod.RateLimit):
|
||||
self.note_rate_limit(ev)
|
||||
return
|
||||
elif isinstance(ev, runner_mod.SessionRestarted):
|
||||
chunks.append(f"_{ev.text}_\n")
|
||||
else:
|
||||
return
|
||||
live = "".join(chunks)[-LIVE_TAIL:]
|
||||
if tools:
|
||||
live = (live + "\n" + tools[-1]).strip()
|
||||
live = (live + "\n" + tools[-1] + (f" · {progress}" if progress else "")).strip()
|
||||
loop.queue(placeholder, live or "⏳ lucrez…")
|
||||
|
||||
started = time.time()
|
||||
|
||||
Reference in New Issue
Block a user