Skip to content

feat: add Rex backend - #344

Open
agawish wants to merge 7 commits into
raine:mainfrom
agawish:rex-backend
Open

agawish wants to merge 7 commits into
raine:mainfrom
agawish:rex-backend

Conversation

@agawish

@agawish agawish commented Oct 30, 2026 •

Copy link
Copy Markdown

Refs #341

Summary

I managed to get access to Rex and loved it, and didn't want to give up workmux. So here's a Rex backend, built the same way as the WezTerm and kitty ones.

workmux picks it up from $REX_BLOCK, which Rex sets in every terminal. Detection checks it last, because anything started from a Rex pane inherits it. So tmux, Zellij or kitty running inside Rex still wins.

The big win for me is the dashboard. Agents running in Rex panes now show up with their status, the preview shows the agent's pane, and Enter or 1-9 jumps to it.

What changed

  • src/multiplexer/rex.rs implements the Multiplexer trait. Rex sessions map to sessions, Rex windows to windows and Rex blocks to panes. A pane ID is the Rex block ID.
  • It only talks to Rex through the rex CLI. Listing panes is one Lua script run through rex do, which returns every session and pane on the server, with each pane's process, cwd and title. That's one process spawn per refresh, about 25ms here.
  • BackendType::Rex, detection and WORKMUX_BACKEND=rex.
  • The status hook treats $REX_BLOCK as a backend signal.
  • RexEnvironment in tests/conftest.py, so the suite runs with --backend=rex. Rex has one server per user, so each test gets its own session and removes it afterwards, the same idea as the WezTerm workspaces.
  • A Rex guide page, plus Rex in the README, the guide index and CONTRIBUTING.

Testing

  • cargo test passes, with 13 new unit tests for snapshot parsing, session lookup, key name translation, block IDs and detection order, including Rex alongside every other backend.
  • test_agent_state.py and test_workmux_capture.py pass on Rex, with 4 workers in parallel too.
  • On tmux the full suite gives 633 passed and 4 failed. The same 4 fail on main for me, two dashboard base branch tests and two OpenCode setup tests.
  • I've been using it day to day with Claude Code agents in Rex. Status tracking, the dashboard preview and jumping all work.

Not done in here

The full suite on Rex is 572 passed and 65 failed. The biggest group is session mode, which create.rs and open.rs only allow on tmux, and those tests also assert through env.tmux(...) helpers. The rest are mostly agent launch in add.I'd like to sort those out in this PR or a follow-up, whichever you prefer. it is done as part of 345

Two things work differently from tmux:

  • Switching the Rex app to another session is a client action, so it needs Remote Control turned on in Rex's settings. Without it, workmux prints the session to open instead of failing. Jumping within the current session works either way.
  • Rex appends new windows at the end of the tab bar, so after_window is ignored. WezTerm has the same limitation.

Happy to change how any of this is structured.

Rex (https://www.superlogical.com/rex) is detected via $REX_BLOCK, after
tmux and Zellij so a multiplexer started inside Rex still wins. Rex
sessions map to sessions, windows (tabs) to windows, and blocks to panes.

The backend drives Rex only through the rex CLI. Pane listing runs one
Lua script through `rex do`, which returns every pane on the server with
its process, working directory and title in a single call.

Refs raine#341
Add RexEnvironment so the suite can run with --backend=rex. Each test
gets its own session on the running Rex server and removes it after,
like the WezTerm workspaces. The 'test:' target follows tmux and sends
to the session's active pane.
@agawish
agawish marked this pull request as ready for review October 30, 2026 15:17
Behaviour:
- Pass `--` to `rex send` so text starting with "-" isn't read as a flag
- Capture with `--trim` so previews don't end in blank rows
- Scope `block.close` to the pane's session; it failed outside it
- Make switching the Rex app's session best effort. Without Remote
  Control, print the session to open instead of failing after the
  work is done
- List sessions that have no panes, so session_exists and kill_session
  see them, and prefer the current session when labels repeat
- Check Rex last in detection, so kitty started from a Rex pane wins,
  and ignore an empty REX_BLOCK
- Return an error from resolve_instance_id when REX_SERVER is missing,
  like WezTerm and kitty
- Implement zoom_pane, rename_window, rename_window_at_pane and
  rename_session

Structure:
- Name detection signals in a struct instead of five bools
- Derive the accepted WORKMUX_BACKEND names from BackendType::ALL
- Share REX_BLOCK detection with the status hook
- Pull repeated lookups and JSON into helpers, in rex.rs and conftest.py

Docs:
- Add a Rex guide page and mention Rex in the README, the guide index
  and CONTRIBUTING
@agawish

agawish commented Nov 2, 2026

Copy link
Copy Markdown
Author

#345 supersedes this one. It has all the commits from this PR, plus:

  • session mode on Rex
  • the full test suite on Rex, with a private Rex server per test (785 passed, and the same 5 failures as main)
  • a fix for workmux remove run from inside its own session, which closed the session but left the worktree and branch behind

Merging #345 alone covers both. I kept them split because I thought two smaller PRs would be easier to read. If you'd rather review it all in one go, merge #345 and close this one.

kong109 added a commit to kong109/workrex that referenced this pull request Nov 3, 2026
Detection checks Rex last, after tmux, WezTerm, Zellij and kitty, since
every terminal started from a Rex pane inherits REX_BLOCK. Detection
signals move into a BackendSignals struct, and BackendType::ALL with
expected_names() lists the backends in one place. The mod.rs and
types.rs seams are taken from raine#345 so the fork stays
rebaseable on it; supports_session_mode is left to WR-3.

Label lookups (kill_session, kill_window and the rest) now act on one
match, the caller's own session or window when it matches, never every
window that shares the label.

Rex key names were checked live against cat -v: delete, pageup and
pagedown are what Rex documents and accepts, and dc is rejected. An
empty send_text makes no call. Agent scans skip floating layers, which
hold the dashboard and sidebar. A refused app switch says to turn on
Remote Control in Rex Settings.

An unsupported server now stops add with the version message instead
of reporting that Rex is not running.

Approach follows raine#344 by agawish

Co-authored-by: Amr Gawish <amr@gawi.sh>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kong109 added a commit to kong109/workrex that referenced this pull request Nov 3, 2026
A refused or ambiguous `session.select` failed `add` and `open` after
the worktree and window were already made. The switch is now a courtesy:
workmux asks one app client with `-C`, and on refusal prints the Remote
Control fix once and carries on.

Only the app showing the caller's pane is moved, as tmux moves only the
clients on the caller's session. Rex.app attaches to every session in its
sidebar, so `session_ids` alone would still move an app showing another
session; the app must also own the caller's block.

Call-site tests now cover the app switch, one-match kills, the parent
session lookup and the pane scans over onboarding and floating blocks.
The version check is keyed on the canonical socket, `workmux list` names
an unsupported server instead of listing nothing, the unread
`WindowRecord.block_id` is gone, and the Rex test fixture stops its
server when startup fails. rex.mdx describes detection, nesting, window
ownership and Remote Control as they now work.

B2 scan filter and S4 test helpers follow raine#344/raine#345 by agawish.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
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