adapter.py was a 3,493-line monolith. Split it into focused modules with clear separation of responsibilities, bringing it down to ~857 lines: - Module-level helpers: hooks, classify, pickers, commands, setup, defaults, secrets - Frame-handler mixins: inbound, tool_frames, push_frames, media_frames, picker_frames, channel_frames, query_frames - mixin_base: IrisAdapterBase (declaration-only base for shared attrs) - adapter.py now holds only IrisAdapter (the composition of the 7 mixins + BasePlatformAdapter), register(), and test-facing re-exports The mixins come before BasePlatformAdapter in the MRO so their methods override the base; super() calls (e.g. send_image) still resolve to BasePlatformAdapter. No circular imports; dispatch.py and http_server.py (instance-method callers) are unaffected. Ruff complexity ceilings (PLR0911/0912/0913/0915) restored to Ruff's built-in defaults (12/50/6/5) instead of "just above the current maxima", which ratchets the bar down as code grows. The existing genuinely-complex functions (frame builders mirroring the wire schema, the QR matrix builder, the dispatch table) carry an explicit `# noqa: PLR09xx` marking them as reviewed, frozen exceptions; new code is held to the default ceilings. All 125 tests green (94 test_android + 31 test_android_http); no new ruff errors introduced.
84 lines
3.2 KiB
Python
84 lines
3.2 KiB
Python
"""Choice-picker callbacks: clarify + exec approval.
|
|
|
|
Build the ``on_choice_selected`` closures the adapter stores in
|
|
``_pending_pickers``; a ``picker.select`` from the app runs the closure and
|
|
delivers its reply text as a normal message.
|
|
"""
|
|
|
|
def _clarify_is_multi(clarify_id: str) -> bool:
|
|
"""True when the pending clarify [clarify_id] allows multiple selections.
|
|
|
|
The flag lives on the gateway's pending entry; a missing/expired entry (or
|
|
any lookup error) is treated as single-select.
|
|
"""
|
|
try:
|
|
from tools import clarify_gateway as _cg
|
|
|
|
with _cg._lock:
|
|
_entry = _cg._entries.get(clarify_id)
|
|
return bool(_entry and getattr(_entry, "multi_select", False))
|
|
except Exception:
|
|
return False
|
|
|
|
|
|
def _clarify_picker_callback(clarify_id: str, choices: list[str]):
|
|
"""Build the ``on_choice_selected`` callback for a clarify picker.
|
|
|
|
Option values are positional (``c0``..``cN``) plus an ``other`` sentinel;
|
|
the closure maps them back to the real choice strings. A real pick resolves
|
|
the clarify (the agent then continues and replies); "Other" flips the entry
|
|
to text-capture so the next typed message is the answer. An unmappable value
|
|
also flips to text so a clarify never dead-ends.
|
|
"""
|
|
|
|
async def on_choice_selected(chat_id: str, value: str) -> str | None:
|
|
from tools.clarify_gateway import mark_awaiting_text, resolve_gateway_clarify
|
|
|
|
if value == "other":
|
|
mark_awaiting_text(clarify_id)
|
|
return "✏️ Type your answer:"
|
|
try:
|
|
idx = int(value[1:]) if value.startswith("c") else -1
|
|
except ValueError:
|
|
idx = -1
|
|
if 0 <= idx < len(choices):
|
|
resolve_gateway_clarify(clarify_id, choices[idx])
|
|
return None
|
|
mark_awaiting_text(clarify_id)
|
|
return "✏️ Type your answer:"
|
|
|
|
return on_choice_selected
|
|
|
|
|
|
# The four exec-approval outcomes hermes understands (tools.approval).
|
|
_APPROVAL_CHOICES = ("once", "session", "always", "deny")
|
|
|
|
|
|
def _approval_picker_callback(session_key: str):
|
|
"""Build the ``on_choice_selected`` callback for an exec-approval picker.
|
|
|
|
The option values are the raw hermes approval outcomes (``once`` /
|
|
``session`` / ``always`` / ``deny``); a tap resolves the waiting agent
|
|
thread via ``resolve_gateway_approval`` (the same primitive the text
|
|
``/approve`` / ``/deny`` handlers use) and returns a short confirmation
|
|
label, which the picker handler delivers as a normal message. An unknown
|
|
value is treated as a deny so a stray tap never approves a command.
|
|
"""
|
|
|
|
async def on_choice_selected(chat_id: str, value: str) -> str | None:
|
|
from tools.approval import resolve_gateway_approval
|
|
|
|
choice = value if value in _APPROVAL_CHOICES else "deny"
|
|
count = resolve_gateway_approval(session_key, choice)
|
|
label = {
|
|
"once": "✅ Approved once",
|
|
"session": "✅ Approved for this session",
|
|
"always": "✅ Approved permanently",
|
|
"deny": "❌ Denied",
|
|
}[choice]
|
|
if not count:
|
|
label = "⌛ Approval expired — no command was waiting."
|
|
return label
|
|
|
|
return on_choice_selected
|