Skip to content

feat(hc): review in an hc terminal and send feedback back with hc send - #129

Open
bailob wants to merge 2 commits into
plannotator:mainfrom
bailob:feat/hc-host
Open

bailob wants to merge 2 commits into
plannotator:mainfrom
bailob:feat/hc-host

Conversation

@bailob

@bailob bailob commented Nov 3, 2026

Copy link
Copy Markdown

Summary

Add plannotator-tui hc open|last for herdr-core (hc) and herdr-app. Neither has Herdr's plugins, so the review is started by a command. It opens in a terminal of its own beside the agent, and a send pastes the feedback into the agent's terminal with hc send.

sequenceDiagram
    participant L as plannotator-tui hc open|last
    participant HC as hc
    participant App as herdr-app
    participant R as review terminal
    participant A as agent terminal
    L->>HC: get <terminal>  (foreground pid, name, reported)
    L->>HC: run --bg --rm --no-notify … hc pane --held
    L->>App: do terminal.peek | terminal.keep  (retried)
    L->>R: keys enter  (once shown, +0.25 s)
    R->>HC: send <terminal> <feedback>
    HC->>A: paste + Enter
Loading
src/
├── cli.rs          # dispatches `hc`; pane helpers are pub(crate)
└── hc/
    ├── command.rs  # runs hc, maps its error kinds
    ├── agent.rs    # delivery target: checks the agent, then hc send
    ├── launch.rs   # finds the agent, starts the review terminal
    ├── show.rs     # herdr-app: peek/keep, retry, release
    └── cli.rs      # hc open | last | pane
  • hc send refuses a blocked agent (kind: blocked); that maps to the existing Blocked outcome. A gone terminal maps to Unavailable, so the review copies instead.
  • A send also needs the agent to still be the terminal's foreground program and to have reported its state (OSC 7501): without a report hc cannot say the agent is not at a dialog, and Enter would answer it.
  • herdr-app takes about 0.4 s to learn of a new terminal and refuses terminal.peek/terminal.keep until then ("no running terminal"), so showing is retried for up to 3 s, and not at all when there is no herdr-app.
  • A new terminal answers an OSC 11 colour query with the core's dark default until herdr-app tells it its colours (measured: dark up to 0.4 s after start, the real colour from 0.5 s). The review waits for Enter (hc pane --held) that the launcher presses after the terminal is shown and has settled, so a light window gets the light palette.
  • hc last --session <transcript> names the transcript. hc reports no session for an agent, so without it the newest transcript in the agent's folder is read, which another agent there may have written. An empty or missing path is refused by the launcher.
  • Not included: terminals on another machine, and a terminal review (a terminal's recent output). The latter shares code that feat(rex): open reviews in rex and send feedback back #127 is moving.

This touches the same cli.rs hunks as #127 (Rex). The shared helpers keep the names and bodies used there (USAGE, last_command, gated_pane, interactive, wait_on_error, known_host), so whichever lands second needs only a small rebase.

Evidence

  • Before: plannotator-tui hc open plan.md is not a command; hc is taken for a file.

    plannotator_tui: reading <cwd>/hc: No such file or directory (os error 2)
    

    After: with herdr-app and an agent that reports state, run in an isolated core and window. Real key events go through the peek:

    $ plannotator-tui hc open plan.md --terminal t3b
    overlays:  [..., 'layout']                     # terminal.peek is showing it
    header:    Send 0 new ▸ pi in t3b (ctrl+s)
    keys:      j  c  "use shorter lived tokens"  Enter  ctrl+s
    status:    sent 1 annotation(s) → pi in t3b
    agent got: ESC[200~# Annotations on plan.md … > use shorter lived tokens ESC[201~ CR
    after q:   review terminal removed
    
  • Degradations (nothing is typed into the agent; the review copies):

    agent reports blocked      → "pi in ag-blocked is at a dialog · copied to clipboard · E retry"
    agent never reports state  → "no agent to send to (pi in ag-silent has not reported its state to hc)"
    agent exits, shell returns → "no agent to send to (pi no longer runs in agent-z)"
    terminal killed            → "no agent to send to (agent-z has finished)"
    
  • Also checked live: --placement split puts the review in a pane beside the agent; open --gate --result-file delivers Approved with Notes and writes {"decision":"approved",…}; with no herdr-app the launcher falls back at once to printing the hc attach command; --session "" is refused before anything starts.

  • Tests (one per invariant):

    a_blocked_program_is_told_apart_from_a_terminal_that_is_gone
      classify({"error":{"kind":"blocked"}})  == Blocked
      classify({"error":{"kind":"finished"}}) == Gone
    an_agent_takes_a_message_only_while_it_runs_there_and_reports_its_state
      finished → "t7 has finished"; other foreground pid → "pi no longer runs in t7"; reported=false → "…has not reported its state to hc"
    showing_waits_for_herdr_app_to_know_the_terminal_but_not_for_a_missing_herdr_app
      [Again, Again, Shown] → 3 attempts;  [GiveUp] → 1 attempt
    a_transcript_that_is_missing_empty_or_a_folder_is_refused_by_the_launcher
    text_over_the_argument_limit_is_a_size_problem_not_a_missing_terminal
    

    cargo fmt --all --check, cargo clippy --workspace --all-targets -- -D warnings, cargo test --workspace (476 tests) and python3 herdr/test-manifest.py pass locally on Linux. The Windows and musl jobs were not run.

Merge Danger

Door: two-way

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

Blast Radius: cli dispatch

cli.rs gains an hc arm, and the delivery target is chosen from PLANNOTATOR_TUI_HC_TERMINAL before the Herdr environment; only the new launcher sets that variable. herdr/launch.rs changes one visibility (known_host) and herdr_pane calls the extracted wait_on_error. Herdr behavior does not change. The feedback travels as one argument to hc send, so the OS caps its size (128 KiB on Linux); a longer review fails with that reason and is copied.


Written by an AI agent using Claude Sonnet 5.5.

plannotator-tui hc open|last starts a review in a terminal of its own for
an agent running in herdr-core, shows it in herdr-app, and sends the
feedback to the agent with hc send.

- The review runs as `hc run --bg --rm --no-notify`, so it is removed when
  it ends and hc does not type a note about it into the agent's prompt.
- herdr-app shows it with terminal.peek (overlay, popup) or terminal.keep
  (split). It takes about 0.4 s to know a new terminal and refuses until
  then, so showing is retried for up to 3 s, and not at all without
  herdr-app.
- A new terminal answers a colour query with the core's dark default
  until herdr-app tells it its colours, so the review waits for Enter
  (`hc pane --held`) that the launcher presses after it is shown and has
  settled. Without this a light window got the dark palette.
- A send needs the agent to still be the terminal's foreground program
  and to have reported its state. hc's refusal of a blocked agent maps to
  the existing Blocked outcome; a gone terminal to Unavailable.
- `hc last --session <transcript>` names the transcript; without it the
  newest one in the agent's folder is read. An empty or missing path is
  refused by the launcher.

The pane helpers in cli.rs become pub(crate) and herdr::launch::known_host
is shared, so the hc module reuses them; Herdr behavior does not change.
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