Skip to content

feat(agents): open Claude Code's agent view with Left and show agent status in the footer - #443

Closed
ayush5harma wants to merge 16 commits into
srothgan:mainfrom
ayush5harma:contrib/agent-view
Closed

ayush5harma wants to merge 16 commits into
srothgan:mainfrom
ayush5harma:contrib/agent-view

Conversation

@ayush5harma

Copy link
Copy Markdown

Summary

  • Agent view: Left on an empty prompt hands the terminal to Claude Code's own claude agents view, and claude-rs takes it back when the view exits (Esc, or Ctrl+C twice). The claude-rs session keeps running meanwhile: a turn in progress continues, and its output is there on return. Claude Code's leftArrowOpensAgents setting turns this off. The key is the rebindable action app.open_agents_or_move_left, Ctrl+B always moves left, and /agents stays Claude Code's subagent configuration.
  • Shared terminal hand-over: the child-terminal hand-over /login used moves into terminal_runtime/child_command.rs, so both commands go through one path. Only one hand-over runs at a time, and repeated presses open one view. Keys read in the batch that started the child are dropped rather than replayed into the composer. A Ctrl+C while the view starts closes only the view: the bridge now runs in its own process group (CREATE_NEW_PROCESS_GROUP on Windows), so the interrupt never reaches the session, and pending interrupts are handled before claude-rs takes the terminal back. A Ctrl+C exit of the view counts as a normal close on both platforms.
  • Footer status: while the input is empty and Left would open the view, the footer shows the background sessions after the mode badges, as ← N agents · K awaiting input · W working. These are the counts claude agents --json gives, minus completed sessions. claude-rs reads them from Claude Code's own files (jobs/*/state.json, the daemon roster and the session registry) every 10 seconds, and again right after the view closes, re-reading only files that changed. claude agents --json is a once-a-minute fallback for a file layout it does not recognise, so the status costs a few file checks rather than a process per poll. On Windows, where claude-rs cannot yet check whether a job's process is alive, it always uses the CLI, at most once a minute. If the action is rebound, the footer names that key in place of ←.
  • Tests: unit tests for the job-file reader, the status text, and the key routing, plus an ignored, run-by-hand check that the reader's counts match claude agents --json on a real profile; PTY tests that hand the terminal to a fake agent view and back, cover Ctrl+C during start-up, and keep the status poller away from the developer's real Claude Code.

Why

See #439. Claude Code opens the agent view with Left on an empty prompt and shows background agent status in its footer. In claude-rs, someone running background agents has to leave the app, or open a second terminal, to see or answer them.

Closes #439

Validation

  • Automated, on this branch (macOS 27.2 on Apple Silicon, rustc 1.98.1 from nixpkgs rather than the 1.89 CI toolchain):
    • cargo fmt --all -- --check: passes
    • cargo clippy --all-targets --all-features -- -D warnings: passes, with five lints new in clippy 1.98 allowed (collapsible_match, manual_is_multiple_of, manual_is_variant_and, map_unwrap_or, while_let_loop). Across all targets, clippy 1.98 reports the same seven warnings on main and on this branch, none of them in this diff.
    • cargo fetch --locked: passes
    • cargo test --locked --all-features: all targets pass, 2034 tests (lib 1914, terminal_resize 39), plus the serial terminal_resize run. I ran it with CLAUDE_RUST_NO_UPDATE_CHECK unset: my environment sets it, which makes two update_check tests fail on every branch.
    • npm --prefix agent-sdk test (162) and npm --prefix agent-sdk run lint: pass
    • Not run: cargo deny, the MSRV check, the duplicate-code scan, Linux and Windows. I'm leaving those to CI. The two Windows-only additions from review (CREATE_NEW_PROCESS_GROUP and the STATUS_CONTROL_C_EXIT check) have not been compiled locally; the Windows clippy job is their first build. Before the review fixes, these commits, together with the session name and mascot ones, passed this repository's CI on all three runners in my fork (feat(parity): preserve session identity and expose stock agent workflows ayush5harma/claude-code-rust#1).
  • Manual: I drove the debug binary against the real Claude Code 2.1.295 in tmux, with one real background session waiting for input. The footer showed ← 1 agent · 1 awaiting input. Left on the empty prompt opened Claude Code's agent view, which listed that session under "Needs input". Esc returned to the same claude-rs session, with its transcript and footer intact. On main, the same keys only move the cursor.
  • Screenshot/video (if UI changed): the footer as it renders (the agent view itself is Claude Code's own screen):
 ❯ Type a message...
[Auto]  [Opus 5.5/XHigh]  [FAST:OFF]  ← 1 agent · 1 awaiting input

Notes

  • Breaking changes: N/A. Left on an empty prompt used to do nothing; with text in the input it still moves the cursor.
  • Docs updated: docs/src/shortcuts.md (the key, the agent view hand-over and its limits, and the footer status and where it is read from), docs/src/about.md (claude agents and claude agents --json added to the CLI dependency list), tests/fixtures/README.md.
  • Suggested changelog entries for the release:
  • This PR and fix: show local command replies and carry the session name and colour #442 both touch src/app/events/mod.rs and tests/terminal_resize.rs. They merge without conflicts (checked with git merge-tree).
  • On Unix the bridge's process group is now a background group. A tool under the session that reads /dev/tty (a sudo or ssh prompt) is stopped by SIGTTIN instead of competing with claude-rs for keys. Neither worked before; this is noted in a comment at the spawn.
  • With no claude on PATH and no CLAUDE_CODE_EXECUTABLE, Left on an empty prompt now shows "claude CLI not found" instead of doing nothing. I kept the message because it says how to enable the view, but it can fall back to moving the cursor if you prefer.
  • Governance/release impact: N/A

🤖 Generated with Claude Code

ayush5harma and others added 16 commits October 9, 2026 20:40
- move the release, spawn, cancel and restore sequence from the /login and
  /logout executor into terminal_runtime::run_with_terminal
- parameterise it by release reason, argv, working directory and labels so
  a second interactive child (the stock agent view) reuses it instead of
  copying it
- keep the auth error strings unchanged; the auth PTY tests pass as before
- poll `claude agents --json` at start and every 10 s, one call at a time,
  each run and parsed on a runtime worker and killed after 5 s
- run the session's own CLAUDE_CODE_EXECUTABLE (falling back to `claude`
  on PATH, as /login does) so the listing comes from the same stock build
- count background sessions only, with stock's "blocked" state shown as
  awaiting input and "working" as working
- render `<- N agents . K awaiting input . W working` after the mode badges
  while the composer is empty, dropping whole segments on narrow widths
- hide it when there are none, the listing fails, or stock's global
  leftArrowOpensAgents setting is false
- add app.open_agents_or_move_left, bound to Left in chat input: from an
  empty composer it hands the terminal to `<claude> agents` through the
  shared child hand-over, otherwise it moves the cursor as before
- honour stock's global leftArrowOpensAgents setting, read at each use
- keep the session running while the view owns the terminal, and poll the
  agent status again as soon as it exits (Esc, or Ctrl+C twice)
- report a missing CLI, a spawn failure or a failing exit in the chat
- the action is rebindable and appears in /help and /docs shortcuts; Ctrl+B
  still moves left unconditionally
- teach the native fake claude `agents --json` (a listing the test places
  in the profile) and `agents` (records argv and cwd, reads one line of
  inherited stdin, exits)
- check that Left with text edits the draft, that Left on an empty prompt
  gives the child the terminal with the TUI silent, and that the next
  prompt reaches the bridge afterwards
- log each agent status poll with its trigger so the test proves the
  footer counts came from the poll the return triggered, not the interval
- describe Left on an empty prompt, how the agent view returns (Esc or
  Ctrl+C twice), and that the session keeps running meanwhile
- explain the footer status, its refresh, when it hides, and the stock
  leftArrowOpensAgents setting that turns both off
- note that /agents stays Claude Code's subagent configuration
- record the fake claude's agents modes in the PTY fixture notes
- the harness inherited CLAUDE_CODE_EXECUTABLE and PATH, so inside a
  claude-rs session the agent view test ran the real binary and timed out,
  and every other PTY test ran the developer's `claude agents --json`
  against its temp profile every 10 s, unlike CI
- always set CLAUDE_CODE_EXECUTABLE: a missing path turns the poller off,
  and the agent view tests point it at the compiled fake
…e child

- claim the terminal synchronously in the shared hand-over: a second
  request (key repeat, batched keys, /login) while one is claimed or
  running is refused instead of overwriting the first child's cancel
  sender, and a dropped sender no longer kills the child as "shutdown"
- start the bridge in its own process group so a Ctrl+C typed while a
  cooked-mode child owns the terminal cannot reach the session, and do
  not treat that SIGINT as a shutdown request in claude-rs; an agent view
  ended by SIGINT is not reported as an error
- show the footer status only when the key would open the view (one
  predicate for both), naming the bound key after a rebinding
- read leftArrowOpensAgents on the poller's worker instead of on the UI
  thread at every Left press; start the poller once trust is settled
- check that a timed-out poll child is really killed; test the repeated
  Left, Ctrl+C during the hand-over, the claim rules and the footer gate
- spawning the 236 MB stock binary for `claude agents --json` cost about
  0.21 s of CPU per call, every 10 s, in every claude-rs window; a poll now
  stats jobs/*/state.json, the daemon roster and the session registry and
  re-parses only files whose mtime or size changed (about 0.06 ms of CPU)
- port the counting of `claude agents --json` without --all: job liveness
  through live roster workers and bg sessions, the 5 s grace for new jobs,
  unclaimed jobs shown as blocked or failed, recurring done jobs, and bg
  sessions without a job
- fall back to the CLI only for a job layout this reader does not know,
  at most once a minute, time-boxed and killed as before
- cache leftArrowOpensAgents by the config file's mtime and size, and drop
  the idle backoff now that a poll is nearly free
- pin the file format with fixtures for every state, live sessions, the
  roster, garbage and the cache; add an ignored real-profile parity check
- say what happens while the agent view is open: turns continue, prompts
  wait but their timers run, notifications are still delivered
- explain that Ctrl+C while the view starts closes only the view and that
  repeated presses open one view
- describe the file-based status, its refresh, the CLI fallback, when the
  hint hides and that it names a rebound key
- Left and Ctrl+C arriving in one read reached claude-rs before it
  released the terminal, so the Ctrl+C was taken as clear-or-quit and
  claude-rs exited (seen in a real run); from the claim on, input belongs
  to the child and the TUI ignores everything but resizes
- unit test and PTY test (Left+Ctrl+C in one write keeps claude-rs and the
  session running; fails with the guard removed)
- docs: a Ctrl+C while the view starts closes the view or is dropped
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ship

- retain OS signal receivers throughout the TUI event loop
- prioritize pending signals before input and child-return events in both phases
- preserve later parent shutdown and active-child SIGTERM behavior in regressions
- link the parity report to cross-platform CI follow-up results
…reliably

- bypass the CLI cache after agent-view return while retaining routine polling limits
- supply equivalent job fixtures and observed command entry for Windows terminal tests
- capture early bridge-test child completion and bound slow-reader cleanup
- document the cross-platform refresh behavior and preserve regression coverage
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Windows-safe submit_command helper only serves the session name and
colour test, which is not on this branch; clippy flagged it as unused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review follow-ups: the bridge gets CREATE_NEW_PROCESS_GROUP on Windows, as
it gets its own process group on Unix, and a Ctrl+C exit of the agent view
(STATUS_CONTROL_C_EXIT) is a normal close there. The CLI dependency list
in about.md gains claude agents and claude agents --json, shortcuts.md
says Windows reads the status through the CLI, the spawn comment records
that a background group stops /dev/tty readers, and the fixtures README
and harness comment say a missing executable disables the CLI fallback
rather than the poller.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ayush5harma
ayush5harma requested a review from srothgan as a code owner October 9, 2026 15:38

@srothgan srothgan left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

I don't want claude-rs launching Claude Code's agent UI or reimplementing its private job-state logic, so this architecture won't be merged. Please have your agent look into the Anthropic SDK we're already using. In agent-sdk/node_modules/@anthropic-ai/claude-agent-sdk/sdk.d.ts (currently 0.3.288), read:

  • SDKTaskStartedMessage, SDKTaskProgressMessage, SDKTaskUpdatedMessage, and SDKTaskNotificationMessage around lines 5993–6084.
  • listSubagents() at line 1152 and getSubagentMessages() at line 939.
  • Query.stopTask() at line 3219.

These provide the foundation for a native view of the current session's agents. They aren't a replacement for Claude Code's global background-session roster, so please scope the feature accordingly.

@srothgan

srothgan commented Oct 9, 2026

Copy link
Copy Markdown
Owner

And please, for the next version of this pr: follow the architecture we have and read some documentation (at least have your agent read it)!!!

@ayush5harma

Copy link
Copy Markdown
Author

Understood. Launching claude agents and reading Claude Code's job files are both out. I'll close this PR and rescope #439 to the current session. Before writing any code, here is the design, so you can redirect it early.

Scope: the active session's subagents and background tasks only. No other sessions, no global roster, and nothing from Claude Code's UI or private files.

Source of truth: the task lifecycle state the bridge already normalizes (tasks.ts, task_links.ts) and Rust already holds (sdk_inventory.tasks, plus the Detached tool calls). The view is a projection of that state, with no second store.

Bridge (types.ts and wire.rs change together):

  • Forward what task_progress carries but the bridge only logs or drops today, as fields of the existing TaskMetadata: the token and tool-use counts from usage, last_tool_name, and the progress summary. Duration already arrives as task timing, and only the notification's summary is kept today.
  • stop_task { session_id, task_id } calls Query.stopTask(). The reply only acknowledges the request or reports an error; the state change arrives through the existing task_notification path (status: "stopped").
  • get_subagent_messages { session_id, agent_id } calls getSubagentMessages(). listSubagents() tells which subagents of the session have a transcript, including after resume. Both follow the get_usage command/reply pattern and reuse the existing history mapping.

Rust: a tab next to Status and Usage in the config view lists the session's tasks, grouped by task_type (subagents, shells, other), with description, status, elapsed time, tokens and last tool. Enter opens a subagent's transcript read-only, and a key stops a running task after a confirmation. A slash command opens the tab. There is no default key binding, and Left stays a cursor key.

Questions:

  1. Should this be a tab in the config view, or its own fullscreen view? I'd use a tab: tabs already provide navigation, Esc handling, and the async fetch pattern that Usage uses.
  2. Which command name do you want for it?
  3. Should the footer show a count of running tasks, derived from the same state, or should nothing appear outside the view?

Before relying on it, I'll check at runtime whether the agent ids from listSubagents() match the task_id of local_agent tasks. The type definitions don't say.

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.

[Feature]: Show the current session's subagents and background tasks, with stop and transcript view

2 participants