Skip to content

feat(rex): open reviews in rex and send feedback back - #127

Open
kwigley wants to merge 7 commits into
plannotator:mainfrom
kwigley:feat/rex-integration
Open

kwigley wants to merge 7 commits into
plannotator:mainfrom
kwigley:feat/rex-integration

Conversation

@kwigley

@kwigley kwigley commented Oct 29, 2026 •

Copy link
Copy Markdown

Summary

Add plannotator-tui rex open|last|terminal|find. Each command finds the agent in a Rex block, opens plannotator-tui in a split to the right of the block, and exits. A send pastes the feedback into the block as one bracketed paste and presses Enter. Without an agent, the review copies to the clipboard.

plannotator-tui rex last
  rex do …terminal.process          # find the agent in the block
  rex api session.new_split         # open the split
    plannotator-tui rex pane last   # runs in the split
      review → Send
        rex do …terminal.program_status   # refuse while the agent is blocked
        rex api block.event (paste)
        rex send-key enter

Rex mirrors the Herdr module. The terminal review code moves out of herdr/ so both hosts share it.

 src/
 ├── herdr/
 │   ├── context.rs
 │   ├── launch.rs
-│   └── terminal.rs     # read + clean + wrap + open
+│   └── terminal.rs     # read, run
+├── rex/
+│   ├── agent.rs        # delivery target for a block
+│   ├── cli.rs          # `rex` subcommands
+│   ├── command.rs      # the `rex` binary
+│   ├── launch.rs       # plan the split
+│   ├── paths.rs        # Markdown files named in output
+│   └── terminal.rs     # read, run
+└── terminal.rs         # clean + wrap + open, shared by Herdr and Rex
  • --paste leaves the feedback in the agent's prompt and focuses the agent's block.
  • A send waits while Rex reports the agent blocked on a permission prompt or a question, like agent_blocked in Herdr.
  • The block is --block, else REX_BLOCK.

Evidence

  • Before: plannotator-tui rex open is an unknown command.
    After: the command opens a split. 15 new tests pass, for example:
claude_code_is_found_by_its_invoked_name_not_its_versioned_binary
a_working_or_finished_agent_takes_messages_and_a_blocked_one_does_not
pasting_is_passed_on_only_where_there_is_an_agent_to_paste_into
the_newest_existing_file_comes_first_and_prose_punctuation_is_dropped

cargo fmt --all, cargo clippy --workspace --all-targets, and cargo test --workspace are clean.

Merge Danger

Door: two-way

The Rex code is new and runs only through plannotator-tui rex. A revert removes it.

Blast Radius: Herdr

herdr/terminal.rs loses its review code to src/terminal.rs, and herdr terminal now calls the shared copy. known_host in herdr/launch.rs becomes pub(crate). Herdr behavior does not change.

Copilot AI balanced review requested due to automatic review settings October 29, 2026 15:36
Add `plannotator-tui rex last|open|terminal`. Each command finds the
agent in a Rex block, opens plannotator-tui in a split to the right
of the block, and exits. The split is an ordinary pane, so it keeps
the frame and theme of Rex. When an agent runs in the block, a send
pastes the feedback into the block as one bracketed paste and presses
Enter. Without an agent, the review copies to the clipboard.

- The block is --block, else REX_BLOCK. An agent that runs a
  launcher in its own shell hands the human a document to review.
- --paste leaves the feedback in the agent's prompt and focuses the
  agent's block, so the human can edit it before sending.
- A send waits while Rex reports the agent blocked on a permission
  prompt or a question, like agent_blocked in Herdr.
- A Rex action runs commands with a bare PATH. The launcher then
  runs the rex binary of its parent process, the Rex server.
- `rex pane` is the entry point of the split. An error waits for
  Enter, because Rex closes the split when the program exits.

Move the terminal review out of src/herdr into src/terminal.rs,
because Herdr and Rex now share it. Each host only reads the output.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Missing agent and session validation can cause annotation text to execute in a shell or display another session’s replies while sending feedback to the selected block.

3 open issues
What changed in this PR

Adds Rex integration so users can open reviews beside an agent’s block and return feedback to it, while sharing terminal-review rendering with Herdr.

Changes:

  • Adds rex open, last, terminal, and find launchers.
  • Adds agent feedback delivery with send/paste modes and blocked-agent checks.
  • Extracts shared terminal-review code and documents Rex usage.
File Description
skills/​plannotator-tui/​SKILL.md Extends review instructions to Rex.
README.md Documents Rex commands and feedback behavior.
crates/​plannotator-tui/​src/​terminal.rs Houses shared terminal cleaning, wrapping, rendering, and tests.
crates/​plannotator-tui/​src/​rex/​terminal.rs Captures Rex output for terminal reviews.
crates/​plannotator-tui/​src/​rex/​paths.rs Finds Markdown files mentioned in output.
crates/​plannotator-tui/​src/​rex/​mod.rs Declares the Rex integration modules.
crates/​plannotator-tui/​src/​rex/​launch.rs Detects agents and opens review splits.
crates/​plannotator-tui/​src/​rex/​command.rs Wraps session-scoped Rex CLI calls.
crates/​plannotator-tui/​src/​rex/​cli.rs Parses launchers and review-pane commands.
crates/​plannotator-tui/​src/​rex/​agent.rs Implements feedback delivery to Rex agents.
crates/​plannotator-tui/​src/​main.rs Registers Rex and shared terminal modules.
crates/​plannotator-tui/​src/​herdr/​terminal.rs Delegates rendering to shared terminal code.
crates/​plannotator-tui/​src/​herdr/​launch.rs Exposes agent-name recognition for reuse.
crates/​plannotator-tui/​src/​cli.rs Routes Rex commands and selects Rex delivery.
AGENTS.md Documents the Rex module boundary.

Review effort: Balanced

Comment on lines +57 to +60
let status = self.rex.terminal_method(&self.block, "program_status").map_err(unavailable)?;
if let Some(reason) = blocked_reason(&status) {
return Err(DeliveryError::Blocked(format!("{} is {reason}", self.describe())));
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. The launcher passes the agent's PID, and each send checks that it still runs in the block. If not, the send returns Unavailable and the clipboard takes over.

Comment on lines +80 to +82
let host = fg.host.context("no supported agent runs in this block")?;
args.extend(["last".to_owned(), "--pid".to_owned(), fg.pid.to_string()]);
args.extend(["--host".to_owned(), host.to_owned()]);

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comes from the existing macOS fallback in last, which Herdr uses too. This PR does not change it. I will keep it out of scope and open a separate issue.

Comment thread crates/plannotator-tui/src/rex/agent.rs Outdated
Comment on lines +65 to +68
self.rex.api("block.event", &paste).map_err(unavailable)?;
let next: &[&str] =
if self.submit { &["send-key", "-b", &self.block, "enter"] } else { &["focus", &self.block] };
self.rex.run(next, None).map_err(unavailable)?;

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. A retry of the same feedback presses Enter without pasting again, and a focus failure no longer counts as a delivery failure.

@kwigley
kwigley force-pushed the feat/rex-integration branch from 88c6205 to 6c5e762 Compare October 29, 2026 15:43
Add `plannotator-tui rex find`. It reads the pane's recent output, picks
the newest .md path that exists on disk, and opens it in a split.
@kwigley
kwigley force-pushed the feat/rex-integration branch from 3039971 to 0dc4658 Compare October 29, 2026 15:50
An empty program_status said "not blocked", so a send pasted and pressed
Enter into whatever was on screen. Claude Code reports nothing at its
startup trust prompt, where Enter picks "No, exit": the agent quit and
the review still said "sent". A done or idle record left by an earlier
process passed the check too.

A send now needs a status record owned by the agent's process, else it
is unavailable and the review copies instead, like Herdr's
agent_not_ready. --paste presses no Enter, so it still pastes.
When Enter failed after the paste, the send failed and the notes stayed
pending. A retry of the same text pressed Enter only, but any change in
between (one more note) pasted everything again on top of the text
already waiting in the prompt, and the agent got the first notes twice.

Once the paste lands the feedback is in the agent's prompt, so the send
counts: the block takes focus and the status line says to press Enter
there. Delivery gains a notice for that; Herdr leaves it empty.
The refusal read "claude no longer runs in claude in 4eb9f1".
SKILL.md tells agents to open a plan with --gate for a sign-off, but
`rex open` rejected the flag. It now takes --gate and --result-file
like `herdr open`: the split shows Approve, the decision reaches the
agent's block, and the result file records it. A gated `rex open` was
also caught by the direct gate, which read `rex` as the file to review.
…ument --gate

AGENTS.md listed src/rex/ as a repo-root path, the delivery module doc described Herdr only, and SKILL.md's --gate step had no Rex form.
@backnotprop

Copy link
Copy Markdown
Contributor

Thanks, Kyle. We tested this in Rex on macOS (build 1630, Claude Code 2.1.312, Codex 0.160) and pushed five small commits on top of your 0dc4658:

  • 6c43cdd: send only to an agent that has reported its state to Rex. At Claude's startup trust dialog, program_status has no records. The send pressed Enter on "No, exit", Claude quit, and the review said "sent". An idle record left by a process that has exited also passed the check. A send now needs a record owned by the agent's pid; otherwise it falls back to the clipboard, the same way Herdr handles an agent it can't read. --paste is unchanged.
  • f7db291: once the paste lands, it counts as sent. Your identical-retry fix holds. But if you add a note and then retry, everything is pasted again. Now a failed Enter after a successful paste leaves the feedback in the prompt, focuses the block, and the status line says to press Enter there.
  • 11ac8b7: the "no longer runs" message named the block twice.
  • 000e827: --gate and --result-file now work with rex open. It also fixes a direct gate that read rex as the file to review.
  • 816f677: docs. AGENTS.md path, the delivery module doc, and SKILL.md.

Checked live and left alone:

  • Enter straight after the paste: 18 of 18 sends submitted, including 600-line pastes.
  • rex capture returns full scrollback (--lines 200 read lines 101 to 300).
  • Your agent-exit fix works.

Agreed that the Codex fallback is pre-existing and out of scope here.

Not tested: key bindings, and status reporting from pi, opencode and agy.

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.

3 participants