Skip to content

fix(tui-v2): skip ask_user picker when candidates list is empty (closes #615)#655

Open
Kailigithub wants to merge 1 commit into
lsdefine:mainfrom
Kailigithub:fix/issue-615-v2-ask-user-empty-candidates
Open

fix(tui-v2): skip ask_user picker when candidates list is empty (closes #615)#655
Kailigithub wants to merge 1 commit into
lsdefine:mainfrom
Kailigithub:fix/issue-615-v2-ask-user-empty-candidates

Conversation

@Kailigithub

Copy link
Copy Markdown
Contributor

Summary

Issue #615: when ask_user is called with candidates=[],
_drain_ask_user_events in frontends/tuiapp_v2.py still mounted a
ChoiceList containing only the "Type something" free-text escape hatch.
That widget calls .focus() on mount, which steals keyboard focus from
the text input. The agent's empty-candidate prompt then becomes
unreachable from the keyboard.

Fix

Add a defensive early-return at the drain layer when candidates is
empty, mirroring tui_v3 commit dfab299. The 'Waiting for your answer ...' marker is already in scrollback as part of the assistant
stream, so the user replies via the normal input box without a
redirected picker.

Note: _install_ask_user_hook already filters empty candidates at the
producer side (line 3983). This drain-side guard is belt-and-suspenders
— any future producer that bypasses the hook (or a stale event pushed
before the hook was installed) will still be safe.

Diff

--- a/frontends/tuiapp_v2.py
+++ b/frontends/tuiapp_v2.py
@@ -6164,6 +6164,12 @@
             except queue.Empty: break
         if not latest: return
         question = latest["question"]; candidates = latest["candidates"]
+        # v2 parity with tui_v3 (dfab299): when the agent didn't supply
+        # candidates, mounting an empty ChoiceList still calls .focus() on
+        # mount and steals keyboard focus from the text input. Skip the
+        # card; the 'Waiting for your answer ...' marker is already in
+        # scrollback, and the user replies via the normal input box.
+        if not candidates: return
         multi = bool(self._MULTI_RE.search(question))
         kind = "multi_choice" if multi else "choice"
         choices = [(c, c) for c in candidates] + [(FREE_TEXT_LABEL, FREE_TEXT_CHOICE)]

Verification

tests/test_tuiapp_v2_ask_user_drain.py covers three cases:

  1. candidates=[] → no ChatMessage appended (bug case)
  2. candidates=['A','B'] → exactly one picker mounted, with the
    free-text escape hatch appended (happy-path regression)
  3. candidates=[] with [多选] marker → still skipped (multi phrasing
    must not bypass the guard)

The first test fails on main (mounts the empty ChoiceList) and passes
with this fix, confirming the regression test catches the bug.

Closes #615

lsdefine#615)

v2 parity with tui_v3 (dfab299). When ask_user is called with
candidates=[], mounting an empty ChoiceList still calls .focus() on
mount and steals keyboard focus from the text input. Early-return at
the drain layer.

Verified via tests/test_tuiapp_v2_ask_user_drain.py (3 cases, all
failing on main without this fix and passing with it).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

tui-v2: ChoiceList steals focus when ask_user has no candidates

1 participant