Skip to content

fix(send_message): pick active composer when ghost frames exist#169

Open
longXboy wants to merge 1 commit into
thisnick:mainfrom
longXboy:fix/send-message-ghost-frame-composer
Open

fix(send_message): pick active composer when ghost frames exist#169
longXboy wants to merge 1 commit into
thisnick:mainfrom
longXboy:fix/send-message-ghost-frame-composer

Conversation

@longXboy

@longXboy longXboy commented Jun 7, 2026

Copy link
Copy Markdown

Problem

wx messages send intermittently fails with No action selected, most reliably reproduced when sending to a chat after another chat had been detached into a separate window.

WeChat's accessibility tree can contain multiple edit + Send(S) button pairs:

  • the live main-window composer, and
  • stale "ghost" frames left behind by chats previously detached into separate windows.

The previous find_edit_send_pair did a depth-first walk and returned the first pair it found. When a ghost frame appeared earlier in the tree, it grabbed that ghost composer — whose input never receives the typed text and whose Send(S) button stays DISABLED forever. The plan then loops and eventually bails out with No action selected.

The practical workaround until now was to restart the container (wx down && wx up) to clear the stale windows, which also drops automation state.

Fix

Instead of "take the first pair", 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 / is interactive)
  3. pair under the main "Weixin" application frame (not a detached ghost frame)

DFS order breaks any remaining ties. Frame-context is tracked during recursion: once we descend into the frame named Weixin, everything below it is flagged in_main_frame, so ghost frames outside it are naturally deprioritized.

Result

wx messages send now selects the correct composer even when stale ghost frames are present in the a11y tree — verified by sending to a private chat that previously failed with No action selected. This removes the need to restart the container to clear ghost windows.

Changes

  • packages/agent-server-rust/src/plans/send_message.rs
    • Replace single-pair DFS (find_edit_near_send / first-match find_edit_send_pair) with collect_edit_send_pairs + scored selection in find_edit_and_send_button.
    • Add node_has_state helper and ComposerPair struct (tracks in_main_frame).

Testing

  • cargo build passes (no new warnings/errors).
  • Deployed the rebuilt binary into a running container and sent to a previously-failing private chat → Message sent successfully!, message confirmed delivered.

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.
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.

1 participant