Original WaveTerm README: README.upstream.md
This is a fork of the amazing WaveTerm that adds cmux-style agent notifications and tmux keybindings, combining best of both worlds on Mac, Linux, and Windows (untested). Please note this is a heavily iterated vibe-coded prototype not ready for any upstream PRs.
A collapsible panel on the left side of the workspace aggregates notifications from AI coding agents running in terminal panes. It surfaces completions, errors, and questions without requiring you to watch each terminal.
- Notifications carry a status-coloured unread background: green (completion), yellow (question), red (error), blue (info).
- Each entry shows the tab name (tinted with the tab's flag colour), home-relative workdir, git branch, and worktree branch when in a linked worktree.
- While an agent is actively running tools the message area shows a pulsing orange dot with the current tool and its key argument (e.g.
Bash: ls -la,Read: ~/project/foo.go). The final message appears when the turn completes. - Clicking a notification focuses the originating block and switches workspace if needed. A stored
pendingBlockFlashkey causes the block border to double-flash when the renderer loads. - When a notification arrives and its block is visible in the current tab the block border triple-flashes to draw attention.
- Read state is persisted to
localStorageand synced across renderers via storage events. - The backend suppresses a completion notification that would overwrite a recent error (within 10 s).
WaveMux can also send Agent panel notifications for regular shell commands when shell integration is active. Successful commands show up as completion; non-zero exits show up as error.
- Notifications are only emitted for commands that run longer than
agent:shellnotificationthresholdms(10 seconds by default). - Ignoring is based on the command's first executable token, so
nvim foo.txtis matched asnvim. - Per-terminal or global ignores can be set with
term:ignoredprocesses.
Example:
{
"agent:shellnotificationthresholdms": 10000,
"term:ignoredprocesses": ["nvim", "less", "watch", "npm"]
}The default ignored list already includes common full-screen or long-running tools such as vi, vim, nvim, nano, less, more, man, top, htop, btop, and watch.
Manual / scripted notifications:
wsh agentnotify "Message text" \
--agent claude \
--status completion|error|question|info \
--branch "$(git branch --show-current)" \
--workdir "$PWD" \
--worktree "$(git rev-parse --show-toplevel)" \
--notifyid "my-stable-id" \
--beep--notifyid causes an upsert — subsequent calls with the same ID update the existing entry rather than appending a new one.
File: ~/.claude/settings.json
{
"hooks": {
"Stop": [
{
"hooks": [
{ "type": "command", "command": "wsh agenthook claude stop" }
]
}
],
"StopFailure": [
{
"hooks": [
{ "type": "command", "command": "wsh agenthook claude stopfailure" }
]
}
],
"SessionStart": [
{
"hooks": [
{ "type": "command", "command": "wsh agenthook claude sessionstart" }
]
}
],
"PreToolUse": [
{
"hooks": [
{ "type": "command", "command": "wsh agenthook claude pretooluse" }
]
},
{
"matcher": "AskUserQuestion",
"hooks": [
{ "type": "command", "command": "wsh agenthook claude notification" }
]
}
],
"Notification": [
{
"matcher": "permission_prompt",
"hooks": [
{ "type": "command", "command": "wsh agenthook claude notification" }
]
},
{
"matcher": "elicitation_dialog",
"hooks": [
{ "type": "command", "command": "wsh agenthook claude notification" }
]
}
],
"PostToolUseFailure": [
{
"hooks": [
{ "type": "command", "command": "wsh agenthook claude posttooluse" }
]
}
],
"UserPromptSubmit": [
{
"hooks": [
{ "type": "command", "command": "wsh agenthook claude userpromptsubmit" }
]
}
]
}
}Also add a shell wrapper to your ~/.zshrc so the badge is cleared when Claude exits:
claude() { command claude "$@"; wsh agenthook claude terminate }wsh agenthook claude reads the Claude Code hook JSON from stdin and extracts structured fields — the last assistant message for completions, question text for approval prompts, and real error messages from PostToolUseFailure / StopFailure payloads. No shell or jq required. It uses the originating block's ORef as a stable notify ID so each terminal pane has exactly one panel slot. Tool errors are stored as intermediate state and only surface if the turn ultimately stops in error.
New hooks since initial release:
SessionStart— posts a green "Ready" badge when Claude starts or resumes a session.UserPromptSubmit— sends a "Thinking…" intermediate immediately when you submit a prompt, before the first tool call, so the progress indicator appears without delay.PreToolUse(no matcher) — sends a live intermediate update showing the tool name and its key argument before each tool call, powering the pulsing green dot in the badge.terminate(via shell wrapper) — clears the badge when the Claude process exits.
opencode uses a native plugin API rather than shell hooks. The plugin file is included in this repo at integrations/opencode/waveterm.js.
Step 1 — copy the plugin file:
mkdir -p ~/.config/opencode/plugins
cp integrations/opencode/waveterm.js ~/.config/opencode/plugins/waveterm.jsOr symlink it so it stays in sync with the repo:
ln -s "$(pwd)/integrations/opencode/waveterm.js" ~/.config/opencode/plugins/waveterm.jsOpenCode automatically loads JavaScript and TypeScript files in
~/.config/opencode/plugins/; no plugin entry is needed in its config.
Restart OpenCode after copying or updating the plugin.
The plugin sends a question notification (with a beep) when opencode requests an answer or permission, an error notification when a tool fails or the session errors, and a completion notification when the primary session goes idle. All notifications for a given project collapse onto a single Agent panel entry keyed by worktree path.
Codex hooks are behind a feature flag. First enable them:
File: ~/.codex/config.toml
[features]
codex_hooks = trueThen wire up the hook handlers:
File: ~/.codex/hooks.json
{
"hooks": {
"Notification": [
{
"hooks": [
{ "type": "command", "command": "wsh agenthook codex notification" }
]
}
],
"Stop": [
{
"hooks": [
{ "type": "command", "command": "wsh agenthook codex stop" }
]
}
],
"PostToolUse": [
{
"matcher": "Bash",
"hooks": [
{ "type": "command", "command": "wsh agenthook codex posttooluse" }
]
}
],
"UserPromptSubmit": [
{
"hooks": [
{ "type": "command", "command": "wsh agenthook codex userpromptsubmit" }
]
}
]
}
}What each hook does:
Notification— sends aquestionnotification with a beep when Codex needs user approval (command execution, file patches, user input prompts, MCP elicitation).Stop— sends acompletionorerrornotification for the Codex turn based on the final message and any accumulated tool errors.PostToolUse(Bash matcher) — records high-confidence Bash failures as intermediate state so they only surface if the turn ultimately stops in error.UserPromptSubmit— clears the active notification when you respond, so stalequestion/errorstates do not persist.
Notification hooks require the headsupanalytics/codex fork, which adds the Notification hook event to the Codex CLI (matching Claude Code's hook). The upstream OpenAI Codex CLI does not support this hook.
Single-key shortcuts using the Alt (Option on macOS) modifier, designed to mirror tmux's key assignments. The same letters and symbols as tmux are used — % splits right, " splits below, x closes, {/} swap panes — so muscle memory transfers naturally.
| Key | Action |
|---|---|
Alt+Shift+% |
Split pane right |
Alt+Shift+" |
Split pane below |
Alt+X |
Close pane |
Alt+Shift+{ / Alt+Shift+} |
Swap pane with previous / next |
Alt+F / Alt+Shift+F |
New file browser pane right / below |
Alt+B / Alt+Shift+B |
New browser pane right / below |
Alt+I |
New sysinfo (CPU + Mem) pane right |
Alt+W |
Toggle widget panel |
Alt+A |
Toggle Wave AI panel |
Alt+Shift+I |
Toggle Agent notification panel |
Alt+U |
Jump to oldest unread agent notification |
Alt+; |
Return to previously focused pane (across tabs/workspaces) |
Alt+Shift+N |
New workspace |
Alt+Shift+? |
Open URL prompt in a new browser pane |
Alt+Shift+: |
Enter wsh command via bottom bar |
A BottomBar input appears for prompted commands (Alt+Shift+:, Alt+Shift+?).
macOS note: Option key combinations may produce special characters (e.g. Option+Z → Ω). WaveTerm matches on the physical key code rather than the produced character, so these shortcuts work regardless.
This makes Alt+U followed by Alt+; a quick round-trip for checking an unread notification and then returning to where you were.
The focused block border and resize handles now use a dedicated --block-border-color CSS variable (previously shared with accent-color), keeping the focus indicator visually distinct.
- Vim mode in the Monaco code editor — the built-in file / diff viewer supports vim keybindings. Toggle it from the editor toolbar.
- Sysinfo widget: disk usage and network throughput — the system-info pane now shows disk I/O and network throughput alongside CPU and memory.
- Auto-theme for remote connections — preview and sysinfo blocks automatically adopt the remote host's colour theme when connected over SSH.
- Opening a new browser block automatically focuses the URL input field so you can type an address immediately without an extra click.
- A new
app:hidewidgetpanelsetting (also toggleable from the tab bar context menu or viaAlt+W) lets you permanently hide the right-side widget panel:{ "app:hidewidgetpanel": true } - tmux-like workspace picker (
Alt+Shift+Nto create, workspace switcher available from the tab bar) - The focused block border color is configurable. Default is green; to use something like xmonad-style red:
Any CSS color value is accepted (
{ "app:blockbordercolor": "rgb(160, 30, 30)" }"#ff0000","red","hsl(0,100%,50%)", etc.). - Shell command notifications are auto-cleared 1 minute after being read. AI agent notifications (Claude, Codex, etc.) remain until explicitly dismissed. Both the shell auto-clear delay and the shell suppression threshold are configurable:
{ "agent:clearreadafterms": 60000, "agent:shellnotificationthresholdms": 10000 }
