aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.pi/prompts/pre-release-audit.md46
-rw-r--r--CHANGELOG.md21
-rw-r--r--CONFIGURATION.md37
-rw-r--r--INTEGRATIONS.md44
-rw-r--r--README.md11
-rw-r--r--SOCKET_API.md85
-rw-r--r--docs/next/CHANGELOG.md18
-rw-r--r--docs/next/SOCKET_API.md3
8 files changed, 223 insertions, 42 deletions
diff --git a/.pi/prompts/pre-release-audit.md b/.pi/prompts/pre-release-audit.md
index 62fe4362..4ff2ecd4 100644
--- a/.pi/prompts/pre-release-audit.md
+++ b/.pi/prompts/pre-release-audit.md
@@ -49,9 +49,11 @@ Process:
- Treat `docs/next/CHANGELOG.md` as the next-release changelog.
- Compare meaningful user-facing changes in the commit range against `docs/next/CHANGELOG.md`.
- Flag missing entries for new features, bug fixes, removals, breaking changes, defaults, compatibility changes, user-visible command/config/API behavior, and security-relevant changes.
+ - Do not require changelog entries solely for internal client/server protocol version bumps. Mention protocol only when the release intentionally changes user-facing compatibility guidance beyond the normal restart requirement.
- Inspect commit bodies for issue reference lines in the form `refs #<issue-number>`.
- Flag normal commits that use GitHub closing keywords like `fixes #<issue-number>`, `closes #<issue-number>`, or `resolves #<issue-number>`, because they close issues before release when they land on `master`.
- For each shipped issue reference, check whether the changelog has a matching user-facing entry that mentions `#<issue-number>` when appropriate.
+ - For each merged external PR, check whether the changelog entry mentions the PR number and thanks the contributor in the existing style, e.g. `(#129, thanks @username)`. If the PR primarily ships an issue fix, include both the issue and PR numbers when useful, e.g. `(#128, #129, thanks @username)`.
- Do not require or add GitHub closing keywords like `fixes #<issue-number>`, `closes #<issue-number>`, or `resolves #<issue-number>` to changelog entries or release notes.
- List shipped issue references under `Issue references to close after release:` so the release operator can verify what release CI will close after the GitHub Release is published.
- Flag stale entries that do not appear to correspond to shipped changes in the range.
@@ -82,16 +84,38 @@ Process:
Output format:
-- `Base ref:`
-- `PRs included:`
-- `Direct commits included:`
-- `Excluded as housekeeping:`
-- `Next-release changelog audit:`
-- `Issue references to close after release:`
-- `Next-release docs audit:`
-- `Root finalization status:`
-- `Required changes before release:`
-- `Proposed changelog edits:`
-- `Proposed docs edits:`
+```md
+Release readiness: READY | NOT READY
+
+Base: <base ref>
+Range: <base ref>..HEAD
+Meaningful shipped changes: yes | no
+
+Changelog: OK | MISSING ENTRIES | NEEDS ATTENTION
+Missing:
+- <only user-facing shipped changes missing from docs/next/CHANGELOG.md>
+
+Docs: OK | MISSING | INACCURATE | NEEDS DECISION
+Missing:
+- <only required next-release public docs gaps>
+
+Wrong or questionable:
+- <docs that disagree with implementation, if any>
+
+Issue refs: OK | NEEDS ATTENTION
+Will close after release:
+- #<issue>
+
+Accepted/no action:
+- <items the user explicitly accepted, such as known closing-keyword commits>
+
+Root docs finalized: YES | NO
+<result of just release-docs-check or why it was not run>
+
+Required before release:
+1. <short action>
+```
+
+Keep the main output glanceable. Put commit inventories, excluded housekeeping, and commands run in an appendix only when they materially help the operator.
If the range has no meaningful user-facing changes, say that plainly instead of forcing entries.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ec9402b7..e3435397 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,27 @@
## Unreleased
+### Added
+- Added indexed keybind families under `[keys.indexed]` for jumping directly to workspace, tab, or visible agent positions 1-9.
+- Added hook-owned custom agent status labels, so integrations can show short visual states like `indexing` without changing semantic agent status.
+- Added terminal-backed agent commands and socket API methods for listing, reading, sending to, renaming, focusing, waiting on, attaching to, and starting agent terminals.
+- Added direct terminal attach with `herdr agent attach <target>` and `herdr terminal attach <terminal_id>`.
+- Added `ui.prompt_new_tab_name = false` for creating new tabs immediately with generated names instead of opening the rename dialog. (#123)
+- Added optional `keys.edit_scrollback` to open the focused pane's retained scrollback in `$EDITOR` inside a temporary zoomed pane. (#122)
+
+### Changed
+- Renamed the focused pane fullscreen keybinding to `keys.zoom`; `keys.fullscreen` remains supported as a legacy alias.
+
+### Fixed
+- Grok Build is now detected as `grok`, with basic working, blocked, and idle state detection. Conflicting known-agent hook labels are ignored once native foreground-process detection identifies a different known agent. (#133)
+- Terminal cursor shapes now forward through attached clients. (#116)
+- Herdr now redraws immediately when the outer terminal regains focus.
+- GitHub Copilot is now correctly detected when its process name is `copilot`. (#118)
+- Integration installs now respect `PI_CODING_AGENT_DIR`, `CLAUDE_CONFIG_DIR`, and `CODEX_HOME` when choosing Pi, Claude Code, and Codex config paths. (#121)
+- Split pane resize hit areas no longer overlap the first content column or row, making text selection work from the start of right and bottom panes. (#120)
+- Dragging text selections near pane edges now autoscrolls into scrollback, and selection state now clears correctly when switching workspaces, tabs, or panes. (#128, #129, thanks @leeeanh)
+- Zoomed panes now keep their border visible in tabs that contain multiple panes. (#115)
+
## [0.5.9] - 2026-05-15
### Added
diff --git a/CONFIGURATION.md b/CONFIGURATION.md
index 5fb924d5..e988e72e 100644
--- a/CONFIGURATION.md
+++ b/CONFIGURATION.md
@@ -77,14 +77,14 @@ keybindings live under `[keys]`.
supported syntax:
- plain keys: `n`, `x`, `-`, `` ` ``
-- modifiers: `ctrl+b`, `shift+n`, `alt+x`
+- modifiers: `ctrl+b`, `shift+n`, `alt+x`, `cmd+x`, `super+x`
- special keys: `enter`, `esc`, `tab`, `backspace`, `left`, `right`, `up`, `down`
- function keys: `f1`, `f12`
- uppercase letters also imply shift: `D` works like `shift+d`
notes:
- most reliable bindings are plain keys, `ctrl+letter`, `esc`/`tab`/`enter`, and function keys
-- `alt+...` and punctuation-with-modifiers may vary depending on terminal/tmux setup
+- `alt+...`, `cmd`/`super`, and punctuation-with-modifiers may vary depending on terminal/tmux setup
- bindings marked `unset` in the key reference are supported actions with no default key assigned
- for navigate-mode actions, duplicate keybindings are treated as config errors; later conflicting bindings fall back to defaults
@@ -103,7 +103,8 @@ split_vertical = "d"
split_horizontal = "D"
close_pane = "x"
rename_pane = "" # optional, unset by default
-fullscreen = "f"
+edit_scrollback = "" # optional, opens focused pane scrollback in $EDITOR
+zoom = "f" # legacy alias: fullscreen
resize_mode = "r"
toggle_sidebar = "b"
previous_workspace = "ctrl+alt+["
@@ -116,6 +117,11 @@ focus_pane_left = "alt+h"
focus_pane_down = "alt+j"
focus_pane_up = "alt+k"
focus_pane_right = "alt+l"
+
+[keys.indexed]
+tabs = "" # optional; e.g. "ctrl" makes ctrl+1..9 switch tabs
+workspaces = "" # optional; e.g. "ctrl+shift" makes ctrl+shift+1..9 switch workspaces
+agents = "" # optional; follows visible agent panel order
```
### key reference
@@ -146,10 +152,30 @@ focus_pane_right = "alt+l"
| `split_horizontal` | `-` | split pane horizontally (stacked) |
| `close_pane` | `x` | close focused pane |
| `rename_pane` | unset | rename the focused pane |
-| `fullscreen` | `f` | toggle focused pane fullscreen |
+| `edit_scrollback` | unset | open the focused pane's retained scrollback in `$EDITOR` inside a temporary zoomed pane |
+| `zoom` | `f` | zoom focused pane; legacy alias: `fullscreen` |
| `resize_mode` | `r` | enter or leave resize mode |
| `toggle_sidebar` | `b` | collapse or expand the sidebar |
+`edit_scrollback` writes the focused pane's retained plain-text scrollback to a temporary file, opens `${EDITOR:-vi}` on that file in a temporary zoomed pane, then removes the file when the editor exits.
+
+### indexed keybindings
+
+Use `[keys.indexed]` to bind number keys `1` through `9` as positional shortcuts. Each value is a modifier combo only. Empty values disable that shortcut family.
+
+```toml
+[keys.indexed]
+tabs = ""
+workspaces = ""
+agents = ""
+```
+
+| key | default | action |
+|-----|---------|--------|
+| `tabs` | unset | switch to tab 1-9 in the active workspace, left to right |
+| `workspaces` | unset | switch to workspace 1-9 in sidebar order, top to bottom |
+| `agents` | unset | focus agent row 1-9 in the visible agent panel order |
+
### custom command keybindings
Use `[[keys.command]]` to bind a prefix-mode key to a command. Press the prefix key, then the configured key.
@@ -270,6 +296,7 @@ for `panel_bg`, you can also use `reset`, `default`, `none`, or `transparent` to
sidebar_width = 26
mouse_capture = true
confirm_close = true
+prompt_new_tab_name = true
show_agent_labels_on_pane_borders = false
agent_panel_scope = "all"
accent = "cyan"
@@ -282,6 +309,7 @@ accent = "cyan"
| `sidebar_width` | `26` | base sidebar width before auto-scaling |
| `mouse_capture` | `true` | capture mouse input for Herdr's mouse UI; set false to let the terminal handle normal clicks while still forwarding mouse to pane apps that request it |
| `confirm_close` | `true` | ask before closing a workspace |
+| `prompt_new_tab_name` | `true` | ask for a tab name before creating a new tab; set false to create tabs immediately with generated names |
| `show_agent_labels_on_pane_borders` | `false` | show detected/reported agent labels in split pane borders when no manual pane name is set |
| `agent_panel_scope` | `all` | sidebar agent list scope: `current` or `all` |
| `accent` | `cyan` | highlight and border color |
@@ -374,6 +402,7 @@ available agent keys:
- `kimi`
- `droid`
- `amp`
+- `grok`
## experimental
diff --git a/INTEGRATIONS.md b/INTEGRATIONS.md
index ec4e4884..23b41ab8 100644
--- a/INTEGRATIONS.md
+++ b/INTEGRATIONS.md
@@ -55,6 +55,14 @@ this writes the bundled pi extension to:
~/.pi/agent/extensions/herdr-agent-state.ts
```
+if `PI_CODING_AGENT_DIR` is set, herdr uses that agent directory instead and writes to:
+
+```text
+$PI_CODING_AGENT_DIR/extensions/herdr-agent-state.ts
+```
+
+`~` is expanded in `PI_CODING_AGENT_DIR`.
+
pi is the cleanest integration. it already has an authoritative hook model, so herdr can get direct state reports over the socket api without guessing as much from the terminal.
bundled source: [`src/integration/assets/pi/herdr-agent-state.ts`](./src/integration/assets/pi/herdr-agent-state.ts)
@@ -65,11 +73,7 @@ uninstall:
herdr integration uninstall pi
```
-this removes:
-
-```text
-~/.pi/agent/extensions/herdr-agent-state.ts
-```
+this removes the same extension path herdr would install, using `PI_CODING_AGENT_DIR` when it is set.
## claude code
@@ -84,6 +88,8 @@ this:
- writes the hook script to `~/.claude/hooks/herdr-agent-state.sh`
- updates `~/.claude/settings.json`
+if `CLAUDE_CONFIG_DIR` is set, herdr uses that directory instead, for example `$CLAUDE_CONFIG_DIR/hooks/herdr-agent-state.sh` and `$CLAUDE_CONFIG_DIR/settings.json`. `~` is expanded in `CLAUDE_CONFIG_DIR`.
+
bundled source: [`src/integration/assets/claude/herdr-agent-state.sh`](./src/integration/assets/claude/herdr-agent-state.sh)
current hook mapping:
@@ -102,6 +108,7 @@ notes:
- claude code hooks also run inside subagents. herdr treats subagent `working` and `blocked` reports as real pane state.
- subagent stop/release events are converted to `working` by the bundled hook script so a completed subagent does not make the parent claude pane look idle.
- `PostToolUse` and `PostToolUseFailure` move the pane back to `working` after a permissioned tool call resolves.
+- some non-claude tools, including grok cli, may load claude settings or plugins. herdr ignores conflicting known-agent hook labels once native foreground-process detection identifies a different known agent.
uninstall:
@@ -109,10 +116,7 @@ uninstall:
herdr integration uninstall claude
```
-this:
-
-- removes `~/.claude/hooks/herdr-agent-state.sh`
-- removes herdr-owned hook entries from `~/.claude/settings.json`
+this removes the same hook path and settings entries herdr would install, using `CLAUDE_CONFIG_DIR` when it is set.
## codex
@@ -129,6 +133,8 @@ this:
- ensures `hooks = true` under `[features]` in `~/.codex/config.toml`
- migrates the deprecated top-level `[features] codex_hooks = true` setting to `hooks = true`
+if `CODEX_HOME` is set, herdr uses that directory instead, for example `$CODEX_HOME/herdr-agent-state.sh`, `$CODEX_HOME/hooks.json`, and `$CODEX_HOME/config.toml`. `~` is expanded in `CODEX_HOME`.
+
bundled source: [`src/integration/assets/codex/herdr-agent-state.sh`](./src/integration/assets/codex/herdr-agent-state.sh)
current hook mapping:
@@ -153,9 +159,9 @@ herdr integration uninstall codex
this:
-- removes `~/.codex/herdr-agent-state.sh`
-- removes herdr-owned hook entries from `~/.codex/hooks.json`
-- intentionally leaves `~/.codex/config.toml` alone
+- removes the same hook path herdr would install, using `CODEX_HOME` when it is set
+- removes herdr-owned hook entries from the matching `hooks.json`
+- intentionally leaves the matching `config.toml` alone
that last point is deliberate: herdr does **not** try to guess whether `hooks = true` is still needed for some other codex hook setup.
@@ -204,6 +210,20 @@ this removes:
~/.config/opencode/plugins/herdr-agent-state.js
```
+## grok cli
+
+herdr does not currently install a grok hook or plugin.
+
+grok is heuristic-only in herdr. herdr detects `grok` and `grok-build` from the foreground process and uses screen heuristics for `working`, `blocked`, and `idle` states.
+
+grok cli may load claude settings or plugins as a compatibility feature, including hooks from `~/.claude/settings.json`. if that causes the claude herdr hook to run in a grok pane, herdr ignores the conflicting `claude` hook label once native foreground-process detection identifies the pane as `grok`.
+
+## amp
+
+herdr does not currently install an Amp plugin.
+
+Amp's public plugin API exposes lifecycle and tool-call hooks, but not passive permission/request-blocked events. A Herdr Amp plugin that only reports `idle` and `working` would take hook authority for the pane and mask Herdr's existing screen heuristics for Amp `blocked` states. Until Amp exposes permission state as an observable plugin event, Amp remains heuristic-only in Herdr.
+
## known limitations
- these integrations only activate inside herdr-managed panes.
diff --git a/README.md b/README.md
index 1b5ed7fa..16649d6e 100644
--- a/README.md
+++ b/README.md
@@ -108,6 +108,12 @@ Host workbox
same session, same agents, same state.
+### direct agent attach
+
+`herdr` and `herdr --remote` attach to the full Herdr session UI. `herdr agent attach <target>` attaches your current terminal directly to one server-owned terminal, like a single-pane terminal attach. `herdr terminal attach <terminal_id>` does the same by terminal id.
+
+Direct attach streams the current rendered terminal state first, then live ANSI frames. Your input goes straight to that terminal. Detach with `ctrl+b q`; send a literal `ctrl+b` with `ctrl+b ctrl+b`. One writable client owns input and resize for a terminal. A second attach fails unless you pass `--takeover`.
+
## agent awareness
the sidebar shows which agents are blocked, working, or done. workspaces roll up to their most urgent state so you can scan the full list at a glance.
@@ -147,7 +153,7 @@ herdr tab create --label "logs"
herdr pane split 1-1 --direction right
herdr pane run 1-2 "npm test"
-# wait for an agent
+# wait for a pane-level UI attention state
herdr wait agent-status 1-1 --status done
# read output
@@ -171,6 +177,7 @@ automatic detection works out of the box. process name matching plus terminal ou
| [droid](https://factory.ai) | ✓ | ✓ | ✓ |
| [amp](https://ampcode.com) | ✓ | ✓ | ✓ |
| [opencode](https://github.com/anomalyco/opencode) | ✓ | ✓ | ✓ |
+| [grok cli](https://x.ai/grok) | ✓ | ✓ | ✓ |
detected but not fully tested: gemini cli, cursor agent, cline, kimi, github copilot cli.
@@ -202,7 +209,7 @@ press `ctrl+b` to enter navigate mode.
| `v` / `-` | split pane |
| `x` | close pane |
| `b` | toggle sidebar |
-| `f` | fullscreen pane |
+| `f` | zoom pane |
| `r` | resize mode |
| `q` | detach (quit client) |
diff --git a/SOCKET_API.md b/SOCKET_API.md
index 51046860..7ffb276e 100644
--- a/SOCKET_API.md
+++ b/SOCKET_API.md
@@ -9,7 +9,7 @@ if you are teaching an agent that is already running inside herdr, start with [`
there are three practical ways to integrate with herdr:
- **agent skill** — [`SKILL.md`](./SKILL.md). best when an agent inside herdr just needs to learn the workflow quickly.
-- **cli wrappers** — `herdr server stop`, `herdr workspace ...`, `herdr tab ...`, `herdr pane ...`, `herdr wait ...`. best for shell scripts and simple orchestration.
+- **cli wrappers** — `herdr server stop`, `herdr workspace ...`, `herdr tab ...`, `herdr agent ...`, `herdr pane ...`, `herdr wait ...`. best for shell scripts and simple orchestration.
- **raw socket api** — best when you want direct request/response control or long-lived event subscriptions.
these layers are intentionally stacked on top of the same control surface.
@@ -141,6 +141,7 @@ for backward compatibility, requests also accept the older positional forms like
```json
{
"pane_id": "w64e95948145ed1-1",
+ "terminal_id": "term_64e95948145ed1",
"workspace_id": "w64e95948145ed1",
"tab_id": "w64e95948145ed1:1",
"focused": true,
@@ -148,18 +149,42 @@ for backward compatibility, requests also accept the older positional forms like
"label": "reviewer",
"agent": "pi",
"agent_status": "working",
+ "custom_status": "indexing",
"revision": 0
}
```
+`terminal_id` is an opaque terminal identity. during the pane-backed transition each pane has one terminal id, but clients should not derive it from the pane id.
+
`label` is an optional manual pane name set through `pane.rename`.
+`agent_info` responses contain a terminal-facing view of a live agent terminal:
+
+```json
+{
+ "terminal_id": "term_64e95948145ed1",
+ "name": "reviewer",
+ "agent": "pi",
+ "agent_status": "working",
+ "workspace_id": "w64e95948145ed1",
+ "tab_id": "w64e95948145ed1:1",
+ "pane_id": "w64e95948145ed1-1",
+ "focused": true,
+ "cwd": "/home/can/Projects/herdr",
+ "revision": 0
+}
+```
+
+`name` is an optional unique agent alias set by `agent.start` or `agent.rename`. it is separate from the pane `label`: `pane.rename` names a terminal/pane for UI purposes, while `agent.rename` declares or changes the agent-facing name and rejects duplicate active agent names.
+
`agent` is an optional display label string.
- when herdr detects a built-in agent, this is that built-in name like `pi` or `claude`
- when a hook or plugin reports a custom agent through `pane.report_agent`, this can be any non-empty label like `hermes`
- when no agent identity is known, it is omitted
+`custom_status` on `pane_info` and agent-status events is an optional hook-owned display label. It is present only while hook authority is active. It does not change `agent_status`, `done` semantics, waits, notifications, or workspace/tab rollup priority.
+
`pane_read` responses contain objects like:
```json
@@ -203,6 +228,13 @@ for backward compatibility, requests also accept the older positional forms like
| `tab.focus` | focus a tab | `tab_info` |
| `tab.rename` | rename a tab | `tab_info` |
| `tab.close` | close a tab | `ok` |
+| `agent.list` | list terminal-backed agents | `agent_list` |
+| `agent.get` | inspect one agent by terminal id, unique agent name, detected agent label, or pane id | `agent_info` |
+| `agent.read` | read output from one agent terminal | `pane_read` |
+| `agent.send` | send literal text to one agent terminal | `ok` |
+| `agent.rename` | set or clear the unique agent name | `agent_info` |
+| `agent.focus` | show the agent terminal in the TUI | `agent_info` |
+| `agent.start` | start a named agent terminal from argv | `agent_started` |
| `pane.list` | list panes, optionally filtered by workspace | `pane_list` |
| `pane.get` | inspect one pane | `pane_info` |
| `pane.rename` | set or clear a manual pane label | `pane_info` |
@@ -624,7 +656,8 @@ params:
"source": "custom:hermes",
"agent": "hermes",
"state": "working",
- "message": "running tools"
+ "message": "running tools",
+ "custom_status": "indexing"
}
```
@@ -637,6 +670,9 @@ notes:
- while this authority is active, the reported `agent` and `state` override heuristic display for that pane
- process detection still owns pane liveness and fallback when hook authority is cleared or released
- `message` is optional metadata for the reporting integration
+- `custom_status` is an optional short display label such as `scheduled`, `indexing`, or `stuck`
+- `custom_status` is visual-only; use `state` for semantic behavior like working or blocked
+- omitting `custom_status` clears any previous custom status from this reporting source
returns `ok`.
@@ -916,12 +952,13 @@ example pushed `pane.agent_status_changed` event:
"pane_id": "1-1",
"workspace_id": "1",
"agent_status": "done",
- "agent": "pi"
+ "agent": "pi",
+ "custom_status": "scheduled"
}
}
```
-`agent` in pushed events follows the same rules as `pane_info.agent`: it may be a built-in detected name, a custom hook-reported label, or omitted.
+`agent` in pushed events follows the same rules as `pane_info.agent`: it may be a built-in detected name, a custom hook-reported label, or omitted. `custom_status` may be included when the pane has hook-owned custom status metadata at the time of the semantic status transition.
## cli wrappers
these commands provide the shell-facing control surface. most command groups talk to the local socket; `status client` only inspects the local executable.
@@ -960,6 +997,30 @@ herdr tab rename <tab_id> <label>
herdr tab close <tab_id>
```
+agent commands:
+
+```text
+herdr agent list
+herdr agent get <target>
+herdr agent read <target> [--source visible|recent|recent-unwrapped] [--lines N] [--format text|ansi] [--ansi]
+herdr agent send <target> <text>
+herdr agent rename <target> <name>|--clear
+herdr agent focus <target>
+herdr agent wait <target> --status <idle|working|blocked|unknown> [--timeout MS]
+herdr agent attach <target> [--takeover]
+herdr agent start <name> [--cwd PATH] [--workspace ID] [--tab ID] [--split right|down] [--focus|--no-focus] -- <argv...>
+```
+
+agent targets accept terminal ids, unique agent names, detected/reported agent labels, and legacy pane ids. `agent list` shows terminals with explicit agent identity, detected/reported agent identity, or `agent.start` launch metadata. `pane.rename` does not make a plain terminal appear in `agent list`; use `agent.rename` for agent identity.
+
+terminal commands:
+
+```text
+herdr terminal attach <terminal_id> [--takeover]
+```
+
+`terminal attach` is the low-level direct attach primitive. it streams the current rendered terminal state first, then live ANSI frames. direct attach has one writable input/resize owner; a second direct attach fails unless you pass `--takeover`. detach with `ctrl+b q`; send a literal `ctrl+b` with `ctrl+b ctrl+b`.
+
pane commands:
```text
@@ -971,6 +1032,7 @@ herdr pane split <pane_id> --direction right|down [--cwd PATH] [--focus] [--no-f
herdr pane close <pane_id>
herdr pane send-text <pane_id> <text>
herdr pane send-keys <pane_id> <key> [key ...]
+herdr pane report-agent <pane_id> --source ID --agent LABEL --state idle|working|blocked|unknown [--message TEXT] [--custom-status TEXT] [--seq N]
herdr pane run <pane_id> <command>
```
@@ -994,11 +1056,16 @@ herdr wait agent-status <pane_id> --status <idle|working|blocked|done|unknown> [
- `tab create` without `--label` keeps the default numbered tab naming
- `tab create --label` applies the custom tab name immediately
- `tab create` returns `result.tab` and `result.root_pane`
+- `agent list` shows agent terminals, not arbitrary named panes
+- `agent wait --status idle` is the CLI completion wait; it treats the UI-only `done` attention state as satisfying idle
+- `agent focus` switches the TUI workspace/tab/pane focus; it is not direct terminal attach
+- `agent attach` attaches the current terminal directly to the agent terminal stream
+- `agent start <name> -- <argv...>` starts a named agent terminal and returns `result.agent` plus `result.argv`
- `pane split` keeps focus where it is by default; pass `--focus` to switch to the new pane
- `pane read` prints **text**, not json
- `pane read --format ansi` and `pane read --ansi` print a rendered ANSI snapshot with colors/styles preserved
- `pane read --source recent-unwrapped` returns recent terminal text with soft wraps joined back together
-- `pane send-text`, `pane send-keys`, and `pane run` print nothing on success
+- `pane send-text`, `pane send-keys`, `pane report-agent`, and `pane run` print nothing on success
- list/get/create/split/wait commands print json on success
- `pane run` is a convenience wrapper for `pane.send_input` with the command text followed by a real `Enter` keypress
- `wait agent-status` is a cli convenience built on top of event subscriptions
@@ -1017,7 +1084,13 @@ herdr pane run 1-2 "npm run dev"
herdr wait output 1-2 --match "ready" --timeout 30000
```
-wait for another agent to finish in the same user-facing sense the UI shows:
+wait for an agent by name to become idle for CLI automation:
+
+```bash
+herdr agent wait reviewer --status idle --timeout 60000
+```
+
+wait for a pane-level UI attention state:
```bash
herdr wait agent-status 1-1 --status done --timeout 60000
diff --git a/docs/next/CHANGELOG.md b/docs/next/CHANGELOG.md
index 32148d7b..e3435397 100644
--- a/docs/next/CHANGELOG.md
+++ b/docs/next/CHANGELOG.md
@@ -3,17 +3,25 @@
## Unreleased
### Added
-- Added `ui.prompt_new_tab_name = false` for creating new tabs immediately with generated names instead of opening the rename dialog.
-- Added optional `keys.edit_scrollback` to open the focused pane's retained scrollback in `$EDITOR` inside a temporary zoomed pane.
+- Added indexed keybind families under `[keys.indexed]` for jumping directly to workspace, tab, or visible agent positions 1-9.
+- Added hook-owned custom agent status labels, so integrations can show short visual states like `indexing` without changing semantic agent status.
+- Added terminal-backed agent commands and socket API methods for listing, reading, sending to, renaming, focusing, waiting on, attaching to, and starting agent terminals.
+- Added direct terminal attach with `herdr agent attach <target>` and `herdr terminal attach <terminal_id>`.
+- Added `ui.prompt_new_tab_name = false` for creating new tabs immediately with generated names instead of opening the rename dialog. (#123)
+- Added optional `keys.edit_scrollback` to open the focused pane's retained scrollback in `$EDITOR` inside a temporary zoomed pane. (#122)
### Changed
- Renamed the focused pane fullscreen keybinding to `keys.zoom`; `keys.fullscreen` remains supported as a legacy alias.
### Fixed
- Grok Build is now detected as `grok`, with basic working, blocked, and idle state detection. Conflicting known-agent hook labels are ignored once native foreground-process detection identifies a different known agent. (#133)
-- GitHub Copilot is now correctly detected when its process name is `copilot`.
-- Integration installs now respect `PI_CODING_AGENT_DIR`, `CLAUDE_CONFIG_DIR`, and `CODEX_HOME` when choosing Pi, Claude Code, and Codex config paths.
-- Split pane resize hit areas no longer overlap the first content column or row, making text selection work from the start of right and bottom panes.
+- Terminal cursor shapes now forward through attached clients. (#116)
+- Herdr now redraws immediately when the outer terminal regains focus.
+- GitHub Copilot is now correctly detected when its process name is `copilot`. (#118)
+- Integration installs now respect `PI_CODING_AGENT_DIR`, `CLAUDE_CONFIG_DIR`, and `CODEX_HOME` when choosing Pi, Claude Code, and Codex config paths. (#121)
+- Split pane resize hit areas no longer overlap the first content column or row, making text selection work from the start of right and bottom panes. (#120)
+- Dragging text selections near pane edges now autoscrolls into scrollback, and selection state now clears correctly when switching workspaces, tabs, or panes. (#128, #129, thanks @leeeanh)
+- Zoomed panes now keep their border visible in tabs that contain multiple panes. (#115)
## [0.5.9] - 2026-05-15
diff --git a/docs/next/SOCKET_API.md b/docs/next/SOCKET_API.md
index e639940e..7ffb276e 100644
--- a/docs/next/SOCKET_API.md
+++ b/docs/next/SOCKET_API.md
@@ -166,7 +166,6 @@ for backward compatibility, requests also accept the older positional forms like
"name": "reviewer",
"agent": "pi",
"agent_status": "working",
- "custom_status": "indexing",
"workspace_id": "w64e95948145ed1",
"tab_id": "w64e95948145ed1:1",
"pane_id": "w64e95948145ed1-1",
@@ -184,7 +183,7 @@ for backward compatibility, requests also accept the older positional forms like
- when a hook or plugin reports a custom agent through `pane.report_agent`, this can be any non-empty label like `hermes`
- when no agent identity is known, it is omitted
-`custom_status` is an optional hook-owned display label. It is present only while hook authority is active. It does not change `agent_status`, `done` semantics, waits, notifications, or workspace/tab rollup priority.
+`custom_status` on `pane_info` and agent-status events is an optional hook-owned display label. It is present only while hook authority is active. It does not change `agent_status`, `done` semantics, waits, notifications, or workspace/tab rollup priority.
`pane_read` responses contain objects like: