Skip to content

Releases: amirlehmam/wmux

wmux v0.46.0

Choose a tag to compare

@github-actions github-actions released this 06 Aug 04:24

Community translation files

The second half of #147. 0.45.0 added Korean; this adds the way to supply or correct a translation without waiting for a release.

Drop a JSON file into ~/.wmux/locales/, next to config.toml:

~/.wmux/locales/
  de.json     # adds Deutsch to Settings → General → Interface language
  ko.json     # overrides individual bundled Korean strings
{ "label": "Deutsch", "strings": { "settings.title": "Einstellungen" } }
  • A code matching a bundled language overrides only the keys it lists — fix one string without restating the rest.
  • A new code adds a language, falling back to English for anything it omits.
  • Deleting the file undoes it. The file is the whole state.
  • wmux locales shows what loaded and why any file was rejected; wmux reload-config applies edits live.

Full reference: docs/config.md → UI translations.

Full Changelog: v0.45.0...v0.46.0

wmux v0.45.0

Choose a tag to compare

@github-actions github-actions released this 06 Aug 04:02

Korean UI translation (한국어)

wmux now ships a sixth interface language, complete at 463/463 strings — Settings, command palette, tutorial, notifications, confirm dialogs and error messages. Requested in #147.

Set it in Settings → General → Interface language → 한국어. If your Windows display language is Korean and you never manually picked a language, wmux selects it for you on launch.

Corrections welcome — the strings are one file: src/renderer/i18n/locales/ko.ts.

Full Changelog: v0.44.0...v0.45.0

wmux v0.44.0

Choose a tag to compare

@github-actions github-actions released this 05 Aug 11:12

Full Changelog: v0.43.0...v0.44.0

wmux v0.43.0

Choose a tag to compare

@github-actions github-actions released this 04 Aug 13:05

What's Changed

  • fix(cli): scope V2 commands to the caller's workspace, reject unknown flags (#143) by @amirlehmam in #144

Full Changelog: v0.42.0...v0.43.0

wmux v0.42.0

Choose a tag to compare

@github-actions github-actions released this 03 Aug 12:00

Two independent causes of runaway resource use in 0.41.0, both reported with
process-level measurements that made them findable.

Typing lag from the diff pane (#141)

If your repo was big enough that git diff HEAD --numstat took a few hundred
ms, an open diff surface spawned 10–42 git.exe per second while completely
idle
, pinning the main process at 79–106% of a core. The main process is also
the PTY relay, so it showed up as keystroke latency in every terminal pane.

The 2s poll interval was never running. useT() returned a fresh closure each
render, invalidating the polling effect's dependency array — and the effect
body starts a poll immediately, so its setTimeout was cancelled before it
could ever fire. Each completed poll triggered a render, which restarted the
effect, which started another poll, while the "cancelled" poll's git process
kept running to completion.

  • useT() is now referentially stable. It was mis-invalidating dependency
    arrays at 42 call sites, not just the diff pane.
  • The poll depends only on [cwd, surfaceId] and cannot be restarted by a
    render; every setState is guarded on an actual change.
  • The git path coalesces per directory (the snapshot path already did), and the
    rev-parse repo probe — a third of the spawned processes — is cached.
  • A diff surface in a background tab or window no longer polls at all.

Closing the tab is now durable. The Edit/Write hook used to re-open it
minutes later with no user action, which is why restarting didn't help either.
A dismissed diff tab stays dismissed until you open one yourself.

Orphaned process trees after a crash (#139)

will-quit was the only teardown path, so a crash or an abnormal exit left
whole process trees alive with no parent to reap them — MCP node.exe children
accumulating across a crash-loop to 30% CPU / 3.3 GB / 251 processes. A PTY
ledger now persists live trees so a fresh start reaps whatever the previous run
orphaned. Its scan also got a timeout generous enough to survive a cold
PowerShell start, since the first scan after a crash is when the machine is
coldest.

CLI state commands (#141, issue 2)

These disagreed with each other and made a scripted cleanup silently no-op:

  • wmux tree --workspace <id> parsed the flag and dropped it, always reporting
    the active workspace. list-surfaces ignored --workspace the same way, and
    --pane was never applied.
  • Both resolved their target as getAllWindows()[0], an order Electron does
    not guarantee, so consecutive calls could describe different windows. V2
    commands now carry the caller's WMUX_SURFACE_ID and answer about the window
    that owns the calling shell.

Also

  • The snapshot (non-git) diff walk is bounded and its new-file discovery runs
    on a slower cadence (#133, thanks @capad-xyz).

Thanks to @Ray0483 for #141 and #139 — including two follow-up corrections
retracting a lead that would have sent me into the wrong subsystem entirely.

Install: download wmux-0.42.0-setup.exe, or the portable
wmux-0.42.0-win-x64.zip. Windows may flag the download (unsigned) — for the
zip, right-click → Properties → Unblock before extracting.

Full Changelog: v0.41.0...v0.42.0

v0.41.0 — wmux answers back

Choose a tag to compare

@github-actions github-actions released this 31 Jul 16:24

The other half of the declared agent-state protocol (#128).

0.39.0 taught wmux to see which of ten panes is parked on you. It could see it and do nothing about it — every method in the protocol was report_*, so the multiplexer observed and could not talk back. This release answers.

Answer a blocked pane without switching to it

An agent that stops to ask a question can now declare its answers along with the block:

wmux report-agent --blocked "Run the migration against prod?" --choices '[
  {"id":"yes","label":"Yes, run it","key":"1"},
  {"id":"no","label":"No, stop","key":"2","isDefault":true}
]'

The sidebar renders those as buttons under the "Needs you" line. Click one and wmux relays it into that pane while you stay in yours. From anywhere:

wmux agent-state                                  # which panes are blocked, and their choices
wmux answer-agent --surface surf-… --choice yes   # answer without going there

The agent owns the keystroke

Each choice carries an id, a label, and exactly what to sendkey (the surface.send_key vocabulary) or literal text. wmux does not know how to answer a Claude Code permission prompt, an OpenCode menu, or your own TUI, and it never guesses. Interpreting another program's interface means depending on that interface never changing, and the failure would land silently on you months later.

A choice carrying neither is dropped, and the reply says how many were kept — a bad declaration is caught at the source rather than by a user clicking a dead button.

Three rules, because this one writes

Everything else in the protocol records; pane.answer_agent pushes bytes into a live terminal.

  1. Only a pane that is currently blocked can be answered. A pane the agent has moved on from may have you typing in it, so a stale click on a not-yet-repainted button must not reach your shell. It refuses, with a message.
  2. Only the agent's declared payload is sent. Free-form text into a pane is still surface.send_text.
  3. Answering does not clear blocked. The agent must confirm, exactly as if you had typed the answer. If it declared the wrong key, a pane that keeps saying "needs you" is annoying — one that goes quiet while the agent is still stuck is a bug you find hours later. The sidebar shows "Sent — waiting…" meanwhile.

The declared answers are cleared on any transition of the blocked state: unblocking ends the prompt they belong to, and a fresh block starts a different one.

Honest limits

Claude Code cannot declare choices yet. Its Notification hook tells wmux the agent wants you, but carries no structured prompt data — no options, no keys. So out of the box you get exactly what 0.39.0 gave you: the "Needs you" signal, without buttons. The buttons appear for any agent that adopts the protocol, and the auto-injected agent instructions now teach it.

Hardcoding Claude Code's permission keys would have made this demo prettier and broken on the first reword — the same brittleness that made the TUI scraper worth replacing.

Also

  • pane.answer_agent requires the pipe auth token, like every non-discovery method. Pinned with a test, since the guard is an absence.
  • 663 tests passing (up from 624), including a case with a synchronously-throwing writer that caught a real bug in the dispatcher's own error handling.

Install

wmux-0.41.0-setup.exe (installer, auto-updates) or wmux-0.41.0-win-x64.zip (portable). Windows may flag the download — wmux is not signed by a chain-trusted certificate yet (#71). For the zip: right-click → Properties → Unblock, or Get-ChildItem -Recurse | Unblock-File.

Full Changelog: v0.40.0...v0.41.0

v0.40.0 — wmux asks first

Choose a tag to compare

@github-actions github-actions released this 31 Jul 16:03

Four community PRs and four issues. The theme this time is wmux being honest about what it does to your machine.

wmux now asks before touching ~/.claude (#132)

wmux integrated with Claude Code and OpenCode by editing files in your home directory — a block in CLAUDE.md and AGENTS.md, four hooks in settings.json, an MCP redirect, and the orchestrator plugin — silently, on every launch. Deleting any of it was futile, because the next launch put it straight back.

The functions were written to be idempotent, and that is exactly what made them unrefusable: idempotence and consent pull in opposite directions, and there was nowhere for a "no" to live.

  • First launch asks, naming the exact files. Enable / Not now / Never. "Not now" really is not-now — you get asked again rather than quietly counted as a yes.
  • Every write gained an inverse. Declining removes what earlier versions wrote instead of merely stopping future writes. Removal is conservative: your own hooks survive, a chrome-devtools entry you re-pointed survives, an unmarked opencode plugin survives, and a CLAUDE.md with your own content keeps all of it.
  • Settings → General → Agent integration has per-feature toggles — instructions, status hooks, orchestrator plugin, browser panel for MCP — each showing the paths it touches. Turning one off cleans up after itself.
  • A corrupt consent record falls back to asking, never to granting.

Thanks to @Modestas for the report, the diagnosis, and the fork with a fix already in it.

A diff pane on a non-git folder no longer freezes the app (#133, PR #135)

Opening a diff pane in a directory that is not a git repo — your home folder, say, which is the default cwd for a new workspace — pinned the main process at 100% of a core, froze the window, and killed the named pipe. A ~3s synchronous full-tree scan ran against a 2s poll, so work queued faster than it drained, permanently.

@capad-xyz filed it with a CPU profile, a measured before/after table, and then the PR: polls can no longer overlap, the snapshot path is fully async, and files are stat-ed before being read (removing ~26 MB of re-reads per poll in the steady state). Concurrent scans of the same directory now coalesce.

Saved sessions keep each terminal's own directory (#134)

A terminal carries two directories — where it was told to start, and where the shell last reported it. Only the first was persisted, and for an ordinary tab it is undefined, so every restored terminal fell back to one workspace-level value written by whichever pane printed a prompt last. Worktrees on D:\ came back on C:\.

Each tab's live directory is now frozen into the saved session, per tab, for both named saves and the 30-second auto-save.

The old icon is gone (#137)

src/renderer/assets/logo.png was a copy of the app icon that the 0.37.0 rebrand never updated, so wmux showed its own previous logo in the titlebar. The copy was the bug, so the copy is deleted — the titlebar now imports the shared SVG (which also drops 452 KB from the bundle).

The window icon also prefers the multi-size .ico over a single 512px PNG, so Windows uses the purpose-drawn 16/24/32px entries instead of downsampling, and the installer refreshes the shell icon cache — which is what kept pinned shortcuts on the old artwork across an in-place update.

If a pinned taskbar button still shows the old icon after updating, unpin and re-pin it once.

Everything else

  • Full renderer i18n (PR #136, @simplyBarbe) — every remaining hardcoded string translated into Spanish, French, Italian and Chinese: Settings, Tutorial, Sidebar, CopyMode, the Diff pane, notifications. Includes a real bug fix: markdown file errors always carried a truthy English string, so the translated fallback was structurally unreachable.
  • Sidebar drag coalesced to one update per frame (PR #131, @ConnorRobinson13) — a drag was firing ~60 width updates a second, each one relayouting every terminal and making ConPTY re-emit its screen.
  • The orchestrator test suite runs on Windows again (PR #130, @ConnorRobinson13) — five tests had been failing on a clean checkout of the only platform wmux ships for, because a Windows path handed to bash lost its separators to escape parsing.
  • agent-state follow-up (#128) — a dead PTY no longer leaves a pane rendering as "Needs you" for a process that has exited.

624 tests passing on native Windows, up from 598.

Install

Grab wmux-0.40.0-setup.exe (installer, auto-updates) or wmux-0.40.0-win-x64.zip (portable). Windows may flag the download as unrecognised — wmux is not code-signed by a chain-trusted certificate yet (#71). For the zip, right-click → Properties → Unblock, or Get-ChildItem -Recurse | Unblock-File.

Full Changelog: v0.39.1...v0.40.0

v0.39.1 — no ghost panes after a PTY dies

Choose a tag to compare

@github-actions github-actions released this 29 Jul 15:03

Patch on top of v0.39.0. If you haven't read those notes, start there — 0.39.0 is where panes learned to tell you when they need you.

No ghost panes after a PTY dies

The new agent-state module cleaned up on PTY exit by deleting its record — but it deleted it silently.

The renderer keeps its own copy of the last state it was sent, so a silent delete changed nothing on screen. A pane whose shell had exited kept rendering as working, or worse as "Needs you" — demanding attention on behalf of a process that no longer existed. A shell can exit while its tab stays open, so the pane is still there to render.

That's precisely the ghost the feature exists to prevent, reachable through the feature's own cleanup path.

PTY exit now announces unknown the same way release_agent does, and both share one forget helper so a future third caller can't reintroduce the silent path.

unknown rather than idle, deliberately: idle is a claim, and the sidebar gives a declared state precedence over its own inference. A forgotten pane has made no claim at all, so it has to fall back to the heuristic instead of being pinned idle.


Install: grab wmux-0.39.1-setup.exe, or the portable wmux-0.39.1-win-x64.zip.
After downloading the zip, right-click → Unblock (or Get-ChildItem -Recurse | Unblock-File) to clear the Mark of the Web.

v0.39.0 — panes tell you when they need you

Choose a tag to compare

@github-actions github-actions released this 29 Jul 14:58

Panes tell you when they need you

wmux used to guess whether an agent was busy: it scraped the Claude Code TUI with regexes, and called a pane "working" while a hook signal was younger than 5 seconds.

That guess had a hole it could never close. An agent parked on a permission prompt goes silent. After five seconds it decayed to Idle — indistinguishable from a finished turn. With ten panes open, the one pane that needed you looked exactly like the nine that didn't.

Agents now declare their state and wmux stores it verbatim. A parked pane reads "Needs you" in violet, ranked above every running summary in the sidebar — because everything else describes work that proceeds on its own, and this describes work that has stopped until you act.

It works with nothing to install. wmux already registers four Claude Code hooks, and those now drive the state directly.

Two facts, three statesawaitingHuman → blocked, runDepth > 0 → working, else idle:

  • runDepth is a refcount, so a subagent finishing can't clear the outer run
  • a monotonic seq drops replays — the classic "finished but still says working" ghost
  • release_agent removes the record entirely instead of leaving a ghost
  • blocked never expires (a waiting agent emits nothing while it waits), but a stale working claim decays to unknown rather than lying forever
  • metadata carries a TTL, so a crashed process stops lying

Any agent can drive it over the pipe — WMUX_SURFACE_ID is already in its environment, so it never has to discover which pane it's in:

wmux report-agent --blocked "permission: Bash"
wmux report-agent --unblocked
wmux report-agent --run-start | --run-end | --run-depth N [--seq N]
wmux report-metadata --model opus --tokens 82.9k --context-pct 41
wmux report-session <id> | release-agent
wmux agent-state                  # every pane + the blocked list

New V2 methods: pane.report_agent, pane.report_agent_session, pane.report_metadata, pane.release_agent, pane.agent_state.

Thanks to @arnonuem for the issue that shaped this — the "single signal, one process-wide choke-point" framing is what made it obviously worth building. (#128)

The progress bar has never worked. Now it does.

Two bugs, one root cause.

@AxelM-MSK found that wmux fired a notification on every shell prompt redraw. OSC 9 is overloaded: iTerm2 says OSC 9;<text> is a notification, but ConEmu and Windows Terminal use OSC 9;<n>;<args> as numeric subcommands — and wmux's own cmd integration embeds 9;9;$P in the prompt. So every Enter raised a bogus notification reading 9;C:\path. (#127)

Verifying that fix surfaced something older. xterm dispatches OSC handlers newest-first, stopping at the first one that returns true. ProgressAddon registers on OSC 9 too, but it's loaded before the notification handler — so the notification handler has been swallowing every OSC 9 since it was written, including 9;4.

The OSC 9;4 progress bar — tab strip, sidebar, Windows taskbar — has been fed nothing since it shipped in 0.23.0. Declining the sequence instead of consuming it fixes the spam and lights up progress for the first time.

Spanish

wmux now ships in Español, complete at 107/107 keys, and a fresh profile on a Spanish-display Windows starts in Spanish. Thanks to @alejandrocastrib. (#129)

Shipped languages: English · Español · Français · Italiano · 中文


Install: grab wmux-0.39.0-setup.exe, or the portable wmux-0.39.0-win-x64.zip.
After downloading the zip, right-click → Unblock (or Get-ChildItem -Recurse | Unblock-File) to clear the Mark of the Web.

wmux v0.38.0

Choose a tag to compare

@github-actions github-actions released this 28 Jul 21:17

Full Changelog: v0.37.0...v0.38.0