fix(chat-select): bounded non-blocking reads and shared composer finder - #170
Open
longXboy wants to merge 2 commits into
Open
fix(chat-select): bounded non-blocking reads and shared composer finder#170longXboy wants to merge 2 commits into
longXboy wants to merge 2 commits into
Conversation
WeChat's accessibility tree can contain multiple edit+send pairs: the live main-window composer plus stale "ghost" frames left behind by chats previously detached into separate windows. The old depth-first "take the first pair" logic grabbed the wrong (ghost) composer, whose input never received text and whose Send button stayed DISABLED forever, causing the plan to loop and ultimately fail with "No action selected". Collect every candidate edit+send pair and rank them so the genuinely active composer wins: 1. editable currently FOCUSED (strongest signal) 2. Send button NOT disabled (composer already has text) 3. pair under the main "Weixin" frame (not a ghost/detached frame) DFS order breaks any remaining ties. This makes 'wx messages send' reliable even when stale frames are present, removing the need to restart the container to clear ghost windows.
- chat-select.py: replace blocking readline() loops with a select()-based read_lines_until() so deadlines hold even when the frida child stays alive but silent; short-circuit when the target chat is already selected regardless of force, since clicking the selected item never fires the selectSession hook - move the ghost-frame-aware edit+send composer finder from send_message.rs into ia::helpers and reuse it in chat_open.rs, ranking candidates lexicographically (focused > send-enabled > main frame) - send_message: always run chat-select even in "chat" state; the open chat may not be the target and chat-select skips when it already is Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This was referenced Aug 12, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes two reliability issues in
wx sendand deduplicates the ghost-frame composer logic:chat-select.pycould hang past its deadline. The read loops used blockingproc.stdout.readline(), so when the frida child stayed alive but silent (e.g. a hookthat never fires), the time-based guard never got a chance to run and
wx sendtimedout. Replaced all three loops with a
select()-basedread_lines_until()that readsthe raw fd non-blocking, so the wall-clock deadline holds even for partial lines.
Clicking an already-selected chat never fires the hook. Clicking the currently
selected chat list item does not trigger
selectSession(), so withforce=Truethefrida hook waited out its deadline and reported a false "Hook did not fire" failure.
The already-selected short-circuit now applies unconditionally —
current_selisfreshly read on every invocation, so there is no stale state for
forceto override.Shared composer finder. Moved the ghost-frame-aware edit+send pair finder from
send_message.rsintoia::helpersand reused it inchat_open.rs, which still hadthe naive take-first-pair logic and could pick a stale detached-chat composer. Ranking
is now a lexicographic
min_by_key(focused > send-enabled > main frame > DFS order),and the main-frame check accepts both "Weixin" and "WeChat" frame names.
With (2) making the skip safe,
send_messagenow always runs chat-select even in state"chat" — the open chat may not be the target, and chat-select short-circuits when it is.
Test plan
wx sendto a chat that is already selected (previously: false "Hook did not fire")wx sendto a different chat while another chat is openwx sendwith a leftover detached/ghost chat window in the a11y treecargo testinpackages/agent-server-rust