feat(dashboard): focus right pane on Enter re-attach (in tmux)
test / unit (pull_request) Successful in 18s
test / integration (pull_request) Successful in 1m8s

The Enter key on a focused agents-pane row is the operator's
explicit "I want to interact with this agent" signal — after
respawning the right pane with claude, move tmux's keyboard
focus to that pane so the operator can start typing
immediately. Without this, every Enter required a manual tmux
nav (C-b →) to actually use the session.

Mechanics:

  - `_attach_in_tmux` gains `focus_right_pane: bool = False`.
  - When True, runs `tmux select-pane -t <pane_id>` after the
    respawn.
  - `_attach_to_bottle` (the Enter handler's helper) passes
    True.
  - Other callers (new-agent flow, stop's auto-attach) leave
    it False so the operator stays in the dashboard for
    follow-up navigation.

`_tmux_select_pane` is a small subprocess wrapper, best-effort
on failure.
This commit is contained in:
2026-05-26 15:25:22 -04:00
parent 8d6e382af5
commit 7e20d75f00
+29 -2
View File
@@ -995,12 +995,19 @@ def _attach_in_tmux(
*,
resume: bool,
tmux_state: dict,
focus_right_pane: bool = False,
) -> str:
"""Spawn / respawn the right pane with `bottle`'s claude
session. Mutates `tmux_state` ({'pane_id': str|None,
'slug': str|None}) so the main loop can track which slug is
in the right pane (used by the agents-pane indicator + the
explicit-stop hook)."""
explicit-stop hook).
`focus_right_pane=True` runs `tmux select-pane` after the
respawn so the operator is dropped into claude immediately.
The Enter re-attach key passes this; passive paths (the
auto-attach after a stop) leave it False so the operator
stays in the dashboard pane."""
docker_argv = bottle.claude_docker_argv(
_claude_runtime_args(resume=resume),
)
@@ -1011,9 +1018,24 @@ def _attach_in_tmux(
# operator still gets a session.
return _attach_via_handoff(stdscr, bottle, slug, resume=resume)
tmux_state["slug"] = slug
if focus_right_pane:
_tmux_select_pane(pane_id)
return f"[{slug}] in right pane"
def _tmux_select_pane(pane_id: str) -> None:
"""`tmux select-pane -t <id>` — moves tmux's keyboard focus
to the pane. Best-effort; failure is silent (logged only via
subprocess's stderr, which we suppress)."""
try:
subprocess.run(
["tmux", "select-pane", "-t", pane_id],
capture_output=True, text=True, check=False,
)
except FileNotFoundError:
pass
def _attach_to_bottle(
stdscr: "curses._CursesWindow",
bottle,
@@ -1028,8 +1050,13 @@ def _attach_to_bottle(
blocks until the operator exits claude. Re-attach always uses
`--continue` — first attach happens via `_new_agent_flow`."""
if _in_tmux() and tmux_state is not None:
# Enter re-attach is an explicit "I want to interact with
# this agent" signal — move tmux focus to the right pane
# so keypresses land in claude instead of the dashboard.
return _attach_in_tmux(
stdscr, bottle, slug, resume=True, tmux_state=tmux_state,
stdscr, bottle, slug,
resume=True, tmux_state=tmux_state,
focus_right_pane=True,
)
return _attach_via_handoff(stdscr, bottle, slug, resume=True)