Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions .testcoverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,22 @@ exclude:
threshold:
file: 0
package: 0
total: 40
total: 65

override:
# The join. Everything else is presentation or plumbing around the table it
# produces, so this is the number that matters most.
- path: ^internal/resolve$
threshold: 85
threshold: 88
- path: ^internal/ui$
threshold: 40
threshold: 68
- path: ^internal/mru$
threshold: 80
# The agent event mapping: pure functions over hook payloads, and the place a
# wrong mapping would be invisible until an agent sat blocked with no badge.
- path: ^internal/agent$
threshold: 95
# The satellite naming rules. A session is tied to its base by name alone, so
# a bug here orphans tabs or lists a grouped session's windows twice.
- path: ^internal/model$
threshold: 90
16 changes: 14 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ a ratchet set just under the current numbers: if a change drops
clients and windows on the **tty**. Everything else is presentation or plumbing
around the table it produces. Read [docs/design.md](docs/design.md) first.

`internal/agent` is the second, smaller story: Claude Code / Devin CLI publish
their state into a tmux pane option via `kaku-tab hook`, and it rides back in on
the `list-panes` query resolve already makes. See [docs/agents.md](docs/agents.md).

## Invariants (all previously broken here; tests pin them)

- Measure **display cells**, not bytes or runes. Use `ansi.StringWidth` for
Expand All @@ -33,11 +37,19 @@ around the table it produces. Read [docs/design.md](docs/design.md) first.
ambiguous once a grouped session shares the window.
- Never `set-hook -g` — it replaces the user's hooks. Use `-ga`.
- Never key on `$WEZTERM_PANE`; it goes stale. Join on the tty.
- Agent state (`@kt_agent`, `@kt_agent_msg`) is set with `set-option -p` only.
tmux pane options inherit from window options, so one window-scoped write has
every agent-free pane in that window report an agent. The rollup is a separate
option name for the same reason.
- `kaku-tab hook` must never print to stdout or exit non-zero. On
`PermissionRequest` and `PreToolUse` both are decision channels, so a status
reporter that got either wrong would silently veto the user's own tool calls.

## Verifying changes

`kaku-tab resolve` prints the join. To see the TUI without a real popup, run it
inside a detached tmux session and capture the pane:
`kaku-tab resolve` prints the join, agent column included. `kaku-tab agents`
lists agent panes without a tmux server of its own. To see the TUI without a
real popup, run it inside a detached tmux session and capture the pane:

```sh
tmux new-session -d -s ui -x 150 -y 30 "$PWD/bin/kaku-tab pick '' ui"
Expand Down
47 changes: 47 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ and press <kbd>Alt</kbd>+<kbd>L</kbd>.
| <kbd>Shift</kbd>+<kbd>Tab</kbd> | fold or unfold every session |
| <kbd>Ctrl</kbd>+<kbd>P</kbd> | toggle window ⇄ pane rows |
| <kbd>Ctrl</kbd>+<kbd>E</kbd> | hide/show detached sessions — leaves only what's on screen |
| <kbd>Ctrl</kbd>+<kbd>A</kbd> | show only windows where an agent is waiting on you |
| <kbd>Ctrl</kbd>+<kbd>/</kbd> | show/hide the preview — the popup resizes with it |
| <kbd>Ctrl</kbd>+<kbd>R</kbd> | rename: the **window** on a child row, the **session** on a header |
| <kbd>Ctrl</kbd>+<kbd>X</kbd> | kill window |
Expand Down Expand Up @@ -180,6 +181,48 @@ switched to, and the window you are in now is pushed one place down — so
<kbd>Alt</kbd>+<kbd>L</kbd> <kbd>Enter</kbd> toggles back to where you just
were, alt-tab style.

## Agents

Claude Code and Devin CLI sessions show up as a column in the picker — one glyph
for which agent, one for what it wants: working, blocked on a permission prompt,
asking you something, finished, or failed. The tmux status bar gets two counters:
how many agents want you, and how many are open.

```
󰂚 1 󰚩 3
```

```sh
kaku-tab install-hooks # one block in ~/.claude/settings.json, both CLIs
```

```tmux
set -g @kaku-tab-agent-key 'M-a' # jump to whatever wants you; again for the next
set -g @kaku-tab-agent-notify 'on' # notify on the transition into waiting
```

```tmux
set -g @kaku-tab-agents 'on'
set -g status-interval 5
```

That appends the pills to the end of `status-right`, i.e. the far right of the
bar. To place them anywhere else, leave the option off and put
`#(kaku-tab agents --format tmux)` where you want it.

Moving onto a row with an agent opens a box below the list saying what it is
doing — the prompt it is working on, the command it wants permission for, the
reply that ended its turn.

The agents report themselves: each CLI's lifecycle hooks run `kaku-tab hook`,
which records the state on the pane it inherited via `$TMUX_PANE`. Nothing is
guessed from the process table — `#{pane_current_command}` says `node` for
Claude Code, and no process name can tell "thinking" from "waiting on you".

Because the state lives in a tmux pane option, it rides in on the `list-panes`
query the picker already makes, and it disappears with the pane. See
[docs/agents.md](docs/agents.md).

## Scrollback search

Set `@kaku-tab-search-key` to get a live grep over every pane's scrollback in
Expand All @@ -196,6 +239,9 @@ kaku-tab resolve # print the window ⇄ tab join (debugging)
kaku-tab restore [--windows] # open a tab per detached session
kaku-tab prune # reap orphaned satellite sessions
kaku-tab titles [--dry-run] # retitle tabs after their tmux window
kaku-tab agents # which pane each Claude Code / Devin session is in
kaku-tab go-agent # jump to the agent that wants you
kaku-tab install-hooks # register the agent hooks with both CLIs
```

`restore` pairs well with
Expand All @@ -205,6 +251,7 @@ brings the sessions back, this brings the tabs back.
## Docs

- [Design](docs/design.md) — how the join works, and why grouped sessions
- [Agents](docs/agents.md) — Claude Code / Devin CLI state in the picker and status bar
- [Configuration](docs/configuration.md) — every option
- [Troubleshooting](docs/troubleshooting.md)

Expand Down
Loading
Loading