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.
This commit is contained in:
Teknium committed 2026-10-17 14:51:46 -07:00
1 parent 47d9efe490
commit 53de7ff326
5 files changed
+26 -7

No files matched your search

+2 -3
View File
@@ -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")
+2 -2
View File
@@ -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:
+2 -2
View File
@@ -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:
+10
View File
@@ -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
+10
View File
@@ -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