Squash of the 54 commits on victor-kyriazakos:feat/user-channel-warning-suppression (PR #112302, head f45c640e55) so the contributor's authorship survives a rebase-merge; the commits interleave with a cron delivery-ledger rework that the salvage removes in follow-up commits, so per-commit cherry-picks were not practical. Adds display.suppress_warning_notifications (global + per-platform, default false): one resolver (gateway/warning_notifications.py), BasePlatformAdapter.emit_warning / emit_media_warning / warning_text, a notification_category classification carried through wakes, queues and persistence, and render/present boundaries for CLI/TUI.
68 lines
3.6 KiB
Python
68 lines
3.6 KiB
Python
"""CLI notification ownership, structured queueing and last-moment consumption."""
|
|
|
|
|
|
class CLIProcessNotificationsMixin:
|
|
def _owns_process_notification(self, event: dict) -> bool:
|
|
"""Whether this session owns a delegation event (pre-compression keys resolve to their continuation; fail closed)."""
|
|
event_key = str(event.get("session_key") or "")
|
|
current_key = str(getattr(self, "session_id", "") or "")
|
|
if not event_key or not current_key:
|
|
return False
|
|
if event_key == current_key:
|
|
return True
|
|
try:
|
|
session_db = getattr(self, "_session_db", None)
|
|
resolved_key = (
|
|
session_db.resolve_resume_session_id(event_key) if session_db is not None else event_key
|
|
) or event_key
|
|
except Exception:
|
|
resolved_key = event_key
|
|
return str(resolved_key) == current_key
|
|
|
|
def _drain_process_notifications(self, consumer: str) -> None:
|
|
from tools.process_registry import process_registry
|
|
from tools.async_delegation import claim_event_delivery, complete_event_delivery
|
|
from tools.process_registry_notifications import (
|
|
ProcessNotificationBatch, TimelineNotification, group_process_notifications)
|
|
|
|
claimed = []
|
|
for event, text in process_registry.drain_notifications(
|
|
session_key=getattr(self, "session_id", "") or "", owns_event=self._owns_process_notification,
|
|
):
|
|
claim = claim_event_delivery(event, consumer)
|
|
if claim is None:
|
|
continue
|
|
claimed.append((event, text))
|
|
complete_event_delivery(event, claim)
|
|
for notifications in group_process_notifications(claimed):
|
|
event, text = notifications[0]
|
|
if event.get("type", "completion") == "completion":
|
|
pending = ProcessNotificationBatch(notifications)
|
|
else:
|
|
pending = TimelineNotification.for_delegation(text, event) if event.get("type") == "async_delegation" else text
|
|
from agent.notification_presentation import diagnostic_process_event
|
|
if diagnostic_process_event(event) and not isinstance(pending, TimelineNotification):
|
|
pending = TimelineNotification(text, text, "internal_notification", "diagnostic")
|
|
self._pending_input.put(pending)
|
|
|
|
def _tui_unwrap_input(self, user_input):
|
|
"""Unwrap ``_VoiceInputMessage`` / ``_SeededQueryMessage`` -> ``(text_or_tuple, is_voice_input, is_seeded_query)``."""
|
|
from cli import _VoiceInputMessage, _SeededQueryMessage
|
|
from tools.process_registry import process_registry
|
|
from tools.process_registry_notifications import (
|
|
PROCESS_COMPLETE_DISPLAY_KIND, ProcessNotificationBatch, TimelineNotification)
|
|
if isinstance(user_input, ProcessNotificationBatch):
|
|
rendered = user_input.render(process_registry)
|
|
user_input = rendered and TimelineNotification(
|
|
rendered, user_input.display_text(process_registry), PROCESS_COMPLETE_DISPLAY_KIND)
|
|
# Voice-transcribed messages arrive wrapped in a sentinel so only genuine STT output gets the voice
|
|
# prefix (#65827).
|
|
is_voice_input = isinstance(user_input, _VoiceInputMessage)
|
|
if is_voice_input:
|
|
user_input = user_input.text
|
|
is_seeded_query = isinstance(user_input, _SeededQueryMessage)
|
|
if is_seeded_query:
|
|
user_input = (user_input.text, user_input.images) if user_input.images else user_input.text
|
|
return user_input, is_voice_input, is_seeded_query
|
|
|