feat(expose): macOS-style Exposé TUI (Ctrl-A g) - #122
Conversation
Add `expose.forceGlobalScope` (default false) to AimuxConfig and a
non-UI core that resolves Exposé scope and lists agent windows:
- `listSwitchableAgentItems` gains a `scope` option ("all" | "worktree",
default "worktree" so switcher/next/prev are unchanged); "all" bypasses
the worktree-path filter while still excluding team + dashboard windows.
- `src/tmux/expose-model.ts`: `resolveExposeScope` (forced-global or
dashboard/unidentified window => all; inside an agent => worktree) and
`listExposeAgentItems`.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add `src/tmux/expose.ts` (`runTmuxExpose`), a switcher-style popup that
tiles live `capture-pane` previews of the scoped agent windows in a grid:
- Scope + agent list from `listExposeAgentItems` (config-aware).
- Bordered tiles with index badge, label, worktree (in global scope), and
last-N plain preview lines; selected tile highlighted.
- ~1s interval re-captures into a cache and re-renders; tiny-terminal
widths are clamped so borders never under/overflow.
- Keys: 1-9 jump, arrows/hjkl/n/p move, Enter open, q/Esc close. Switch
reuses tmux-control.sh `window`.
Register `.command("expose")` in main.ts mirroring `switcher`.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wire the Exposé TUI to the tmux leader prefix: - scripts/tmux-control.sh: new `expose` action that opens a near-fullscreen centered `display-popup` running `aimux expose ...` (mirrors the switcher popup), threaded through arg-parse, path map, fallback list, and fallback_local_control. - runtime-manager.ts: unbind + bind `Ctrl-A g` in the prefix table to run the control script's `expose` action (mirrors `Ctrl-A s`). - Tests for the binding and the popup invocation; README hotkey docs. `Ctrl-A g` works on the dashboard (all worktrees) and inside an agent (that worktree), honoring expose.forceGlobalScope. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Review limit reached
More reviews will be available in 1 minute and 51 seconds. Learn how PR review limits work. Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file). ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (7)
📝 WalkthroughWalkthroughAdds a new "Exposé" feature to aimux: an interactive tiled-grid terminal UI ( ChangesExposé Grid Feature
Sequence DiagramsequenceDiagram
participant User
participant tmux as tmux prefix g
participant controlScript as tmux-control.sh
participant CLI as aimux expose (main.ts)
participant runTmuxExpose as runTmuxExpose (expose.ts)
participant exposeModel as listExposeAgentItems
participant tmuxCapture as tmux capture-pane
User->>tmux: Ctrl+A g
tmux->>controlScript: expose action + context
controlScript->>controlScript: show_local_expose → display-popup
controlScript->>CLI: aimux expose --project-root ... --current-window ...
CLI->>runTmuxExpose: TmuxExposeOptions
runTmuxExpose->>exposeModel: context + config
exposeModel-->>runTmuxExpose: { scope: "worktree"|"all", items[] }
runTmuxExpose->>runTmuxExpose: enter raw + alternate screen
loop periodic refresh
runTmuxExpose->>tmuxCapture: capture each agent window pane
tmuxCapture-->>runTmuxExpose: preview lines
runTmuxExpose->>User: render tiled grid with live previews
end
User->>runTmuxExpose: press 1–9 or Enter to select
runTmuxExpose->>controlScript: switch action + target window id
controlScript-->>runTmuxExpose: exit status
runTmuxExpose->>runTmuxExpose: restore terminal state
runTmuxExpose-->>CLI: exit code
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
src/tmux/control-script.test.ts (1)
1270-1275: ⚡ Quick winAssert that no HTTP fallback is attempted in the “locally” expose path.
This test validates popup dispatch, but it doesn’t verify that
curlwas never called. Add acurlLogassertion so regressions in local-vs-remote routing are caught.As per coding guidelines, “For live-pane, latency-sensitive navigation, prefer tmux-local metadata and tmux bindings over Node-side session lists; inspect
src/tmux/control-script.test.tsas source of truth.”Suggested patch
const log = readLog(envRoot); + const curlLog = readCurlLog(envRoot); expect( log.some((entry) => entry.includes("display-popup -c /dev/live -T aimux exposé -x C -y C -w 90% -h 90% -E exec")), ).toBe(true); expect(log.some((entry) => entry.includes("expose --project-root"))).toBe(true); + expect(curlLog).toEqual([]); });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/tmux/control-script.test.ts` around lines 1270 - 1275, The test currently validates that the popup dispatch and expose command are logged correctly, but it does not verify that curl was not invoked, which means regressions in local-vs-remote routing could go undetected. Add a curlLog assertion after the existing expect statements to ensure that the local expose path does not fall back to HTTP by verifying that curl was never called. This assertion should check that the curlLog either remains empty or does not contain entries related to curl invocation.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/fast-control.ts`:
- Around line 154-159: The conditional check order in the fast-control.ts file
is causing force-global scope to be bypassed by teammate window restrictions.
Move the check for `scope === "all"` to execute before the `if
(teammateParentSessionId)` check so that force-global visibility takes
precedence and returns true immediately, preventing the teammate-parent branch
from short-circuiting the logic. Additionally, add a regression test case in
expose-model.test.ts that verifies the behavior when forceGlobalScope=true is
set and the current window metadata contains a team.parentSessionId, ensuring
the force-global scope correctly overrides teammate window restrictions.
In `@src/tmux/expose.ts`:
- Around line 163-169: The exit function properly restores terminal state, but
unexpected exceptions in the setInterval callback (interval refresh) and input
parsing sections can bypass this exit path, leaving the terminal in raw mode.
Wrap the main interactive loop containing the interval setup and input parsing
logic in a try/finally block where the finally clause calls the exit function
(or performs equivalent terminal restoration). This ensures terminal state is
restored even when unexpected errors occur in the callback handlers, preventing
the terminal from becoming unusable.
---
Nitpick comments:
In `@src/tmux/control-script.test.ts`:
- Around line 1270-1275: The test currently validates that the popup dispatch
and expose command are logged correctly, but it does not verify that curl was
not invoked, which means regressions in local-vs-remote routing could go
undetected. Add a curlLog assertion after the existing expect statements to
ensure that the local expose path does not fall back to HTTP by verifying that
curl was never called. This assertion should check that the curlLog either
remains empty or does not contain entries related to curl invocation.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 66cd07bf-c00f-411e-a1d8-0d5c4f50b8a6
📒 Files selected for processing (12)
README.mdscripts/tmux-control.shsrc/config.test.tssrc/config.tssrc/fast-control.tssrc/main.tssrc/tmux/control-script.test.tssrc/tmux/expose-model.test.tssrc/tmux/expose-model.tssrc/tmux/expose.tssrc/tmux/runtime-manager.test.tssrc/tmux/runtime-manager.ts
… sanitize
- fast-control: force-global ("all") scope no longer narrowed by the
teammate-window branch, so Exposé shows all agents as intended; add a
regression test in expose-model.test.ts.
- expose.ts: route interval/keypress callback errors and SIGINT/SIGTERM
through the shutdown path so the terminal is always restored; sanitize
captured pane output (strip escapes + control bytes) to prevent escape
injection / tile misalignment; clamp grid height to real terminal rows.
- control-script.test: assert no HTTP fallback (curlLog empty) in the
local expose popup path.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Note (independent review finding, by design): the all-worktrees Exposé scope intentionally still excludes teammate sub-agent windows ( |
# Conflicts: # src/config.test.ts
Summary
Adds a macOS-Exposé-style TUI popup to aimux, opened with the tmux leader combo
Ctrl-A g. It tiles livecapture-panepreviews of agent windows across the screen with1–9quick-jump.Built on the existing
switcher/inbox-popuppopup pattern (adisplay-popuprunning a Node TUI), keeping it in the tmux control layer per the navigation-layer rule inAGENTS.md— not a new in-process layer.Behavior
worktreePath).1–9jump to an agent; arrows/hjkl/n/pmove,Enteropens,q/Esccloses.switch-clients to that window via the existing tmux-control path.expose.forceGlobalScope(defaultfalse) in.aimux/config.json/~/.aimux/config.json; whentrue, Exposé always shows all agents regardless of where it's invoked.Ctrl-A ewas already bound toteam, so Exposé usesCtrl-A g("grid").Changes
934fbf6):expose.forceGlobalScopeconfig flag +src/tmux/expose-model.ts(scope resolution + agent listing).listSwitchableAgentItemsgains ascopeoption (default"worktree", so switcher/next/prev are unchanged).922cdec):src/tmux/expose.ts(runTmuxExpose) tiled TUI +aimux exposeCLI command.707cd29):exposeaction inscripts/tmux-control.sh,Ctrl-A gbinding inruntime-manager.ts, tests + README hotkey docs.Testing
yarn buildall green.🤖 Generated with Claude Code
Summary by CodeRabbit
Ctrl+A gto see active agents with live previews in an interactive tiled layout. Jump to agents using keys 1–9, navigate with arrow keys or vi-style keys (h/j/k/l), and open with Enter. Configure agent scope (global or worktree-only) via the newexpose.forceGlobalScopesetting.