From 53de7ff3264283f39f98df3f8a931c49b018c3e7 Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Sat, 17 Oct 2026 08:59:41 -0700 Subject: [PATCH] feat(clarify): public get_pending() query replaces _lock/_entries reads Adapters rendering native clarify buttons (core Discord/Telegram, base send_clarify, the community QQ plugin) read tools.clarify_gateway._entries under _lock to learn whether a request is still pending and its choices. get_pending(clarify_id) returns the entry while it awaits an answer, else None; internal readers move to it. --- gateway/platforms/base.py | 5 ++--- plugins/platforms/discord/adapter.py | 4 ++-- plugins/platforms/telegram/adapter.py | 4 ++-- tests/tools/test_clarify_gateway.py | 10 ++++++++++ tools/clarify_gateway.py | 10 ++++++++++ 5 files changed, 26 insertions(+), 7 deletions(-) diff --git a/gateway/platforms/base.py b/gateway/platforms/base.py index a8350f8da5..63aa741bff 100644 --- a/gateway/platforms/base.py +++ b/gateway/platforms/base.py @@ -2901,9 +2901,8 @@ class BasePlatformAdapter(ABC): if choices: # Multi-select flag lives on the pending entry (signature stays adapter-compatible). try: - from tools import clarify_gateway as _cg - with _cg._lock: - _is_multi = bool(getattr(_cg._entries.get(clarify_id), "multi_select", False)) + from tools.clarify_gateway import get_pending + _is_multi = bool(getattr(get_pending(clarify_id), "multi_select", False)) except Exception: _is_multi = False hint = t("gateway.clarify.hint_multi") if _is_multi else t("gateway.clarify.hint_single") diff --git a/plugins/platforms/discord/adapter.py b/plugins/platforms/discord/adapter.py index bf36f07b4d..563e547d3a 100644 --- a/plugins/platforms/discord/adapter.py +++ b/plugins/platforms/discord/adapter.py @@ -6924,8 +6924,8 @@ def _define_discord_view_classes() -> None: # Round-trip the canonical choice text from the entry, not the button label. resolved_text: Optional[str] = None try: - from tools.clarify_gateway import _entries as _clarify_entries # type: ignore - entry = _clarify_entries.get(self.clarify_id) + from tools.clarify_gateway import get_pending + entry = get_pending(self.clarify_id) if entry and entry.choices and 0 <= index < len(entry.choices): resolved_text = entry.choices[index] except Exception: diff --git a/plugins/platforms/telegram/adapter.py b/plugins/platforms/telegram/adapter.py index 851069c0b6..2a2f6f5464 100644 --- a/plugins/platforms/telegram/adapter.py +++ b/plugins/platforms/telegram/adapter.py @@ -4847,8 +4847,8 @@ class TelegramAdapter(TelegramHeldInboundMixin, BasePlatformAdapter): return resolved_text: Optional[str] = None try: - from tools.clarify_gateway import _entries as _clarify_entries # type: ignore - entry = _clarify_entries.get(clarify_id) + from tools.clarify_gateway import get_pending + entry = get_pending(clarify_id) if entry and entry.choices and 0 <= idx < len(entry.choices): resolved_text = entry.choices[idx] except Exception: diff --git a/tests/tools/test_clarify_gateway.py b/tests/tools/test_clarify_gateway.py index 31b409f123..5e633ba69f 100644 --- a/tests/tools/test_clarify_gateway.py +++ b/tests/tools/test_clarify_gateway.py @@ -54,6 +54,16 @@ class TestClarifyPrimitive: assert cm.resolve_gateway_clarify("id-race", "") is False assert entry.response == "A" + def test_get_pending_tracks_lifecycle(self): + """Adapters read a pending entry through ``get_pending``; answered or unknown ids read None.""" + from tools import clarify_gateway as cm + + entry = cm.register("id-pend", "sk-pend", "Pick", ["A", "B"]) + assert cm.get_pending("id-pend") is entry + cm.resolve_gateway_clarify("id-pend", "A") + assert cm.get_pending("id-pend") is None + assert cm.get_pending("never-registered") is None + def test_open_ended_auto_awaits_text(self): """Clarify with no choices is in text-capture mode immediately.""" from tools import clarify_gateway as cm diff --git a/tools/clarify_gateway.py b/tools/clarify_gateway.py index 4b0b610fa7..f9480ebf2f 100644 --- a/tools/clarify_gateway.py +++ b/tools/clarify_gateway.py @@ -111,6 +111,16 @@ def resolve_gateway_clarify(clarify_id: str, response: str) -> bool: return True +def get_pending(clarify_id: str) -> Optional[_ClarifyEntry]: + """The entry for ``clarify_id`` while it is still awaiting an answer, else None (unknown, + answered, timed out or cleared). Read-only: adapters rendering native choice cards compare it + by identity with the entry they rendered and read ``session_key`` / ``choices`` / + ``multi_select`` from it; answering goes through ``resolve_gateway_clarify``.""" + with _lock: + entry = _entries.get(clarify_id) + return entry if entry is not None and not entry.event.is_set() else None + + def get_pending_for_session(session_key: str, *, include_choice_prompts: bool = False) -> Optional[_ClarifyEntry]: """Oldest pending entry awaiting free text (open-ended, or after "Other"); ``include_choice_prompts=True`` returns the oldest unresolved entry of any kind (user