Skip to main content
CHANGELOG

What shipped

Released versions only, generated at build time from the product Keep a Changelog file. Unreleased work is not listed here: if it is not in a version heading, it has not shipped.

Latest released v3.23.4 · · 61 releases

Currently downloadable v3.25.2-preview.1 · preview channel

Those are two different questions. This page lists versions that have been released, from the product changelog. The download page serves whatever the live preview channel points at, which moves ahead of the last released version with every build. Download the current build from /download. Channel and exact version always come from the live release manifest.

Releases 21–25 of 61

Page 5 of 13

v3.10.1

Fixed

  • Channel dock and conversation no longer show raw i18n keys (#297). The channels dock and the conversation view are pure presentational components that fell back to an identity translator when one wasn't passed in, surfacing raw keys (CHANNELS.TITLE, the empty-state message) instead of translated copy. They now receive the live translator, so the dock header, empty state, and labels render correctly.

v3.10.0

Channels grow a human UI

Headline: the A2A channels that agents post into now have a place a human can read and join. v3.9.0 made channels multi-party with a server-verified sender; this release gives them a UI — a collapsible right-side dock that sits beside your terminals, a member roster to see who's in a room and join or leave it, and recent history that loads when you open a channel instead of a blank pane. Alongside the channel UI: copy/paste that survives a CJK IME, live channel delivery that survives a daemon reconnect, and a fail-closed gate on private-channel joins.

Added

  • Channels move into a right-side dock you can read beside your terminals (#287). A2A channels were agent-only plumbing; now there's a place for a human to watch and join them. The channel list and the active conversation live in a collapsible dock on the opposite edge from the workspace sidebar — a flex column that reflows the panes instead of the old overlay that floated over them, so opening a channel narrows the terminals rather than covering them. Toggle it from the StatusBar #, and it persists across restart. Decoupled from in-app Company mode, so it works without setting up a company first.

  • Channel member roster — see who's in a room, join and leave (#291). The conversation header shows a member count that opens a roster popover: the workspaces currently in the channel, a self-only leave (the ✕ next to your own row), and an add-a-workspace picker for public channels. Fully keyboard-accessible — no drag-only paths. Leaving the channel you're viewing returns you to the list.

  • Opening a channel loads its recent history (#293). Channels used to stay blank until a new message arrived in the current session — open a room with a backlog and you'd see nothing. Opening a channel now hydrates its recent messages from the daemon, and a daemon reconnect re-hydrates, so the conversation is there when you look.

  • Pane + surface lifecycle as MCP tools (#285). Five new first-class MCP tools — pane_split, pane_close, pane_focus, surface_new, surface_close — so an external/headless orchestrator (e.g. a Claude Code supervisor that spawns a worker pane per task and reaps it once committed) can manage its panes through the official MCP instead of dropping down to the raw daemon JSON-RPC. They mirror the workspace-scoped lifecycle RPCs hardened in the #236 family (#238/#256/#257): the create tools (pane_split/surface_new) take an optional workspaceId and default to the caller's own workspace (never the on-screen one), failing closed on an explicit unknown id; the address tools (pane_close/pane_focus/surface_close) take a globally-unique id resolved across all workspaces, and pane_focus is non-yank (it won't steal the user's screen). No new daemon RPC or capability — the methods existed; this surfaces them and grants them to the bundled MCP server's first-party allowlist. Requested by @zhenzoo.

Changed

  • Channel dock polish — one header, responsive width (#295). The dock shipped with a duplicate "Channels" title (its own header plus the list panel's section header) and a hard 320px width that crushed the terminals to per-character wrapping on narrow windows. The title now renders once with the collapse control merged into it, and the width clamps (248–320px) so the dock yields space when the window is small and grows back when there's room.

Fixed

  • Private channels are join-gated on the daemon (#292). A same-machine caller that knew a private channel's id could join it directly through the daemon and read its history — join() had no visibility check. It now fails closed: a non-member can't join (or read) a private channel it wasn't invited to. Same-machine, same-user only; never remotely reachable.

  • Live channel delivery survives a daemon reconnect (#290). A leaked rpc:invoke handler registration meant that after the daemon respawned or reconnected, the main process stopped teeing channel messages (and other daemon→main events) to the renderer until a manual reload — so a channel only updated when you reopened it. The handler is now removed correctly on reconnect, so messages keep flowing live.

  • Copy and paste survive a CJK IME (#294). With a Korean/Japanese/Chinese IME mid-composition, the key event reports keyCode 229 / key "Process", so Ctrl+C and Ctrl+V silently did nothing while composing. wmux now falls back to the physical key code (KeyC/KeyV), so copy and paste work regardless of IME state.

v3.9.0

Agent channels, with a verified sender on every message

Headline: A2A channels grow up. The multi-party half (U2) lands, so several agents in one workspace can talk in a shared, named room instead of only the one-to-one task messages A2A started with — and every channel message now carries a server-verified sender an agent cannot forge. Building on the channel domain types and persistence from U1 (#269), agents create, join, leave, post, and archive channels; the daemon pins each message's sender, each membership, and each channel's authorship to a workspace identity that the main process resolves from the actual sending pane rather than trusting a tag the caller put on the wire. A forged verifiedWorkspaceId is rejected outright — never attributed to the workspace it tried to impersonate — and private channels stay readable only to their members. Alongside it, the bundled CLI takes a stable identity so the legacy permission grandfather can start closing.

Added

  • A2A channels — multi-party rooms with a server-verified sender (U2 + D5, #280). Channels are Slack-style rooms for the agents in a workspace: a shared, named thread several agents post into, rather than the one-to-one task messages A2A began with. This release lands the multi-party operations on top of U1's domain types and persistence (#269) — create, join, leave, post, and archive — and makes caller identity server-verified end to end. Every mutating channel call is stamped with the workspace identity the main process resolves from the sender's real pane (senderPtyId), not a verifiedWorkspaceId the caller supplied:

    • the daemon's ChannelService pins the sender on each post, the member on each join/leave, and createdBy on each channel to that resolved identity, so a forged sender, member, or author is impossible;
    • the main process strips any client-supplied workspace tag and re-derives it from the owning pane, failing closed on a mutating call it can't attribute;
    • a forged verifiedWorkspaceId aimed at another workspace is rejected, never silently attributed to the victim;
    • channel reads are membership-scoped, so a non-member can't read a private channel's messages, and message bodies are length-clamped so an oversized post can't stall the pipe.

    Channel access stays gated behind the existing a2a.channel.read / a2a.channel.send capabilities, so this widens no trust boundary. Channels contributed by @AnandSundar; the verified caller-identity hardening (D5) by the wmux team.

Changed

  • The bundled wmux CLI now reports a stable client identity, so the legacy permission grandfather can begin closing (#282). The permission enforcer historically let any caller that sent no client name through unchecked (if (!clientName) allow) — a grandfather clause the bundled CLI, the one steady-state envelope-less caller, rode on. The CLI now identifies itself as wmux-cli, and the enforcer grants that identity exactly the narrow set of methods the CLI actually calls — a separate, tighter allowlist than the bundled MCP's first-party set, pinned by a source-level test so a new CLI command can't silently fall outside it. This is additive: nothing changes for callers today and the grandfather still admits envelope-less callers — it's the groundwork for a later release to close that grandfather behind the existing enforcementMode shadow→enforce switch.

v3.8.0

LanLink: local-first cross-PC agent messaging

Headline: LanLink lets two wmux machines on the same LAN pair once with a 6-digit PIN, then exchange read-only agent messages over an authenticated, encrypted channel — no cloud, no account, off by default. The epic is built so that running commands across machines is physically impossible: the background daemon imports none of the agent-spawning code, a remote message can only ever surface as a read-only card in the renderer (never pasted into a terminal), and every internal RPC now carries a required trust-origin so the execute path fails closed for anything not provably local. Also lands A2A channels U1 (the rooms half of a future cross-PC group chat), a Fleet View sort toggle, a quieter zoom-restore button, and a keyboard-focus self-heal.

Added

  • LanLink — local-first cross-PC agent messaging, off by default. Two machines on the same LAN pair with a 6-digit PIN and then exchange read-only text messages over a ChaCha20-Poly1305 channel with per-connection fresh keys. Built across five PRs, with execute excluded by construction at every layer:

    • Durable inbox + cursor-pull delivery (#271). A daemon-side append-only inbox persists inbound remote messages and survives a renderer or main crash; the renderer pulls by cursor on reconnect, so nothing is lost and nothing replays twice. A dedicated IPC channel keeps a remote message structurally unable to reach the terminal-paste path.
    • Control plane + Settings (#272). An enable toggle and NIC picker in Settings, config persisted across daemon restarts, with the NIC stored as a name+MAC identity (re-resolved to a live IP at bind time, never a stale address). No listener yet — this is the network-0 control surface.
    • LanLinkServer core (#273). The network surface: an isolated net.Server bound only to a real external IPv4 on the chosen NIC (fail-closed bind guard, Windows Private-profile firewall), PIN-EKE pairing (X25519 + scrypt over the PIN, which never travels on the wire; ≤2-minute window; fail-burn after 5 wrong attempts), the AEAD channel, an allow-list router that admits only text/state messages (never execute/spawn), an ingress sanitizer, and a fail-closed per-peer store with live revoke. Per-peer random UUIDs and long-term secrets under an owner-only DACL.
    • Renderer + pairing UX (#275). A read-only remote-peer card in a new Fleet View Remote tab — untrusted off-machine text rendered as plain React text, never a terminal escape — plus a Settings pairing section (generate a PIN with a live countdown, join another machine, list and revoke peers), and the main-process bridge that exposes the daemon's pairing RPCs to the UI with the daemon itself untouched.
    • Review follow-ups. The pairing screen shows this machine's host:port next to the PIN so a peer can join from one screen (#277).
  • A2A channels — domain types + persistence (U1, #269). The first half of channels — Slack-style rooms for agents: the channel domain types and a durable persistence layer, contributed by @AnandSundar. Converges with LanLink at a shared delivery seam toward a local-first cross-PC group chat.

  • Fleet View situational sort toggle (#268). The cockpit grid can now toggle between attention-first (blocked agents float to the top) and pure workspace order.

Changed

  • The A2A execute path is hardened against off-machine callers (#270). Every internal RPC now carries a required trust-origin tag (local vs remote), and the agent-spawning a2a.task.send path only runs when the call provably came from this machine — a positive-allow gate that fails closed for anything else, pinned by a source-level test that the background daemon can never even import the code that spawns agents. Nothing changes for same-machine multi-agent use; this is the foundation that makes cross-PC execute impossible.
  • system.capabilities advertises only methods that are actually callable over the wire (#276). Control-pipe-only RPCs (daemon.*, lanlink.*) are dispatched by the daemon pipe and never registered on the RPC router, so they're no longer listed — a wire client gets an honest capability list instead of methods that would just return unknown-method.
  • The zoom restore button is now a quiet, minimal control that matches the maximize button (#274). When a pane is zoomed, the toggle that returns it to the grid was a bold red ZOOM badge — reusing the cursor accent, a strong red in several themes. It's now styled identically to the hover-revealed ⤢ maximize button (neutral surface, subtle border) with a ⤡ restore glyph, so maximize and restore read as a matched pair. It still stays visible while zoomed so the way back out is always obvious (#258 follow-up).

Fixed

  • Self-heal orphaned keyboard focus (#267). Closing an overlay (search, palette, notifications, toolbar) could drop DOM focus to <body>, leaving the terminal unable to receive input until you opened multiview. A central guard now detects orphaned focus and reasserts it onto the active pane, so input keeps working after any overlay closes.

Documentation

  • The README foregrounds the A2A multi-agent moat (#260), and the contributor onramp gained issue templates plus an honest i18n status (#261).

v3.7.0

A2A execute approval hardened, and remote RPC that lands in the right workspace

Headline: the A2A execute gate — the path that lets a remote agent spawn a bypassPermissions Claude CLI in your workspace — is reworked into a renderer-driven approval flow with execute as its own dedicated capability (no longer bundled with ordinary send), an executeApproved receipt the worker can't forge, fail-closed YOLO hydration, and a queue so concurrent requests don't clobber each other. Alongside it, the #236 RPC workspace-scoping sweep is finished: surface.new, pane.close, pane.focus, and surface.focus now all act on the workspace the caller names instead of whatever happens to be on screen — so a multi-agent orchestrator's "do this in MY workspace" finally lands where it should. Plus a per-pane activity line on Fleet View cards, and a browser-pane keyboard-focus fix.

Added

  • Fleet View terminal cards now show a per-pane activity line — what each agent is doing right now, at zero extra API cost (#251). wmux already receives a PostToolUse (agent.activity) hook payload for every tool an agent runs, and was discarding it at the emit-kind early-return. That payload is now summarized into a short, scannable line per pane — ✎ file for an edit, → file for a read, $ cmd for a bash run, ⌕ pattern for a search, srv:tool for an MCP call — and rendered as an accent line on the pane's Fleet card, with the raw scrollback tail kept as the fallback when there's no activity (the awaiting_input affordance still takes priority). It's derived through a pure, never-throwing helper that guards every field of the untrusted tool input, strips control characters, caps the raw input at 1 KB before any regex runs (so a multi-megabyte tool argument can't stall the main thread), and hard-truncates the result to 80 chars; delivery is a per-pane 3-second leading-edge throttle on the existing metadata funnel — no EventBus tee, no notification, no new daemon round-trip. The activity string is transient and never persisted. Now a glance at the cockpit tells you not just who is blocked but what everyone is doing.
  • A2A execute approval is now a renderer-driven gate with execute as its own dedicated capability (#254). The A2A execute path — a2a_task_send with execute:true, which spawns a bypassPermissions background Claude worker in the target workspace, i.e. remote code execution — was reworked into a stronger approval flow. execute is now a separate capability (a2a.execute) resolved per-call: a task send requires a2a.execute only when execute:true and the ordinary a2a.send otherwise, so granting an agent the ability to message you no longer implicitly grants it the ability to run code in your workspace. The worker is spawned only when the renderer returns executeApproved===true with a resolved target workspace — a receipt the caller cannot forge, replacing the old main-side confirm round-trip — and a denied request creates no task, pastes nothing, and emits no event. Concurrent execute requests are held in a keyed approval queue (each its own dialog, the inbox owning exactly one visible surface) so two agents asking at once can't clobber each other's prompt. A persisted "YOLO" auto-approve flag is available for trusted setups but hydrates fail-closed — only an explicit boolean true enables it, so a malformed persisted value (e.g. the string "false") can never silently turn on bypassPermissions auto-approval — and the approval label is localized. Internal follow-up cleanup (#255) removed the now-dead confirm-execute plumbing and extracted the approval gate into a standalone, unit-tested module (YOLO short-circuit, approve, deny, 30 s auto-deny, and concurrent-request independence all covered).
  • A pane now has a discoverable maximize button (#258). Hovering an un-zoomed pane reveals a quiet ⤢ button in its top-right corner; clicking it zooms that pane to fill the window — the same toggle as the tmux-style prefix + z, which was previously keyboard-only and undocumented. The keyboard cheat sheet (? in prefix mode) gained a Maximize pane entry. Surfaced after Reddit feedback that there was no visible fullscreen/maximize control to find (#182 follow-up).

Fixed

  • The #236 RPC workspace-scoping sweep is complete — surface.new, pane.close, pane.focus, and surface.focus all act on the workspace the caller names, not the one on screen (#256, #257). After #238 made pane.split honor an explicit workspaceId, its sibling RPCs still didn't, so a multi-agent orchestrator working in a background workspace couldn't reliably operate on its own panes. surface.new dropped all of its params main-side and pinned the renderer to the active workspace, so "open a terminal in my workspace" always landed in whichever workspace the user was viewing; it now forwards workspaceId/shell/cwd, honors the target, fails closed on an explicit-but-unknown id (no active-workspace fallback), and eager-spawns the PTY into the target workspace. pane.close is a new RPC (panes carry globally-unique ids, so it's resolved across all workspaces like surface.close, disposing every PTY under the pane), filling the gap that left a worker pane created via pane.split with no way to be cleaned up — and it rejects root/non-leaf targets, since closing the root pane is a no-op that would otherwise orphan live surfaces with dead PTYs. pane.focus/surface.focus acted only on the on-screen workspace — pane.focus silently no-op'd while returning a false {ok:true}, and surface.focus errored "not found" — so a background-workspace agent couldn't focus its own pane; a dedicated focusPaneSurface store action now resolves the workspace by explicit id (no self-search, no active fallback), rejects non-leaf panes, sets the active pane and surface in one transaction, emits pane.focused honestly for a background or multiview workspace (events stay workspace-scoped, no cross-workspace leak), and surfaces a real {error} on a miss instead of the false success. Bringing a workspace on-screen remains the separate, opt-in workspace.focus RPC — these handlers never yank the user's screen.
  • A browser pane now takes keyboard focus when its own pane is active, so typing into it works (#252, #253). The embedded browser webview wasn't being focused when its pane became active, so keystrokes had nowhere to land and the browser pane felt dead to the keyboard. The webview is now focused whenever its pane is the active one.
  • Ctrl+Enter now inserts a newline instead of submitting, inside in-pane TUIs like Claude Code and codex (#258). xterm sends a bare carriage return for Ctrl+Enter — byte-identical to plain Enter — so a TUI couldn't tell the two apart and treated Ctrl+Enter as submit. wmux now emits a line feed for the Ctrl+Enter chord, matching the existing Shift+Enter and Ctrl+J newline keys. Surfaced after Reddit feedback.