Releases: subshell-ai/subshell
Release list
desktop-server-v1.0.1
Patch Changes
- #197
cec0f3aThanks @theogravity! - Four assistant fixes from the first-run report. The tmux gate now says "Waiting for tmux to be installed". On a Mac with no package manager, the Homebrew install steps show by default instead of hiding until the button is pressed. The Photos permission prompt is fixed: it never appeared on the installed app because PhotoKit's consent request was made off the main thread, and it is now dispatched to main. The permission buttons are short ("Allow", "Open Settings") with the row named in each button's accessible label.
desktop-server-v1.0.0
Major Changes
- #184
711b5faThanks @theogravity! - Marked 1.0.0. The control plane, the node daemon, and both desktop apps
all mark their first stable release. This entry changes no behavior; it
records the milestone, and the entries below it are what the milestone is
made of.
Minor Changes
- #180
b28726aThanks @theogravity! - Text size on Linux now answers Ctrl+= / Ctrl+- / Ctrl+0 in every window of both desktop apps. Until now those keys lived only on the macOS menu bar (a GTK menu bar is per-window chrome, so Linux carries none), leaving the tray's Text Size submenu as the only door, and no door at all on a session with no tray host. The tray submenu and the shared zoom ladder are unchanged.
Patch Changes
-
#176
db5951aThanks @theogravity! - Subshells: state reads as one dot everywhere. The cards' corner chips and the list's STATUS column are gone; both draw the status dot (with the unseen-notification bell) beside the title. Green is now the ALIVE family: bright for printing, dim for quiet-but-running, with the printing dot blinking like Claude Code's in-progress work (still under reduced-motion), and gray means not-running only. An unreachable node is red, not amber. The tile view segments by machine on the sidebar's own grouping and labels, the card's machine pill retired with the chip, and the page grew a machine filter that narrows both views (default All). The Server assistant's setup wizard joins the same motion rule: its in-progress checklist spinner now sits behind the reduced-motion gate, like every other animation. -
#182
6955725Thanks @theogravity! - The no-em-dash voice rule applied to shipped copy: every string a person reads on a screen or in a terminal now carries its breath with a comma, colon, parentheses, or a full stop. The tray update item reads "Update available: Subshell Server 0.8.0" (both apps), the node window title "Subshell Client: Node", network plugin hints, both CLIs' refusals and prompts, and the browser-rendered error messages lose their dashes, and so do the /docs endpoint descriptions, the shared MCP tool descriptions, and the desktop apps' permission prose. No wire name, error code, id, or log line changed.
desktop-client-v1.0.0
Major Changes
- #184
711b5faThanks @theogravity! - Marked 1.0.0. The control plane, the node daemon, and both desktop apps
all mark their first stable release. This entry changes no behavior; it
records the milestone, and the entries below it are what the milestone is
made of.
Minor Changes
- #180
b28726aThanks @theogravity! - Text size on Linux now answers Ctrl+= / Ctrl+- / Ctrl+0 in every window of both desktop apps. Until now those keys lived only on the macOS menu bar (a GTK menu bar is per-window chrome, so Linux carries none), leaving the tray's Text Size submenu as the only door, and no door at all on a session with no tray host. The tray submenu and the shared zoom ladder are unchanged.
Patch Changes
- #182
6955725Thanks @theogravity! - The no-em-dash voice rule applied to shipped copy: every string a person reads on a screen or in a terminal now carries its breath with a comma, colon, parentheses, or a full stop. The tray update item reads "Update available: Subshell Server 0.8.0" (both apps), the node window title "Subshell Client: Node", network plugin hints, both CLIs' refusals and prompts, and the browser-rendered error messages lose their dashes, and so do the /docs endpoint descriptions, the shared MCP tool descriptions, and the desktop apps' permission prose. No wire name, error code, id, or log line changed.
cli-server-v1.0.1
Patch Changes
- #185
cafda7dThanks @theogravity! - The plane can fetch from GitHub again. The egress pin's host list carried
GitHub's old release-asset hostname, so the renamed host every release
download now redirects to was refused by name: a fresh server answered
"could not provide a linux-x64 node binary" and refused to verify a
release's signature (the 1.0.0 post-cut proof measured the failing hop and
this patch is its fix). Both spellings are allowed now, the test pins both,
anddocs/security.mdnames them.
cli-server-v1.0.0
Major Changes
- #184
711b5faThanks @theogravity! - Marked 1.0.0. The control plane, the node daemon, and both desktop apps
all mark their first stable release. This entry changes no behavior; it
records the milestone, and the entries below it are what the milestone is
made of.
Minor Changes
-
#171
c60dfe7Thanks @theogravity! - Presets: the row action menu gains Clone preset. It opens the create dialog prefilled from the source (same agent, its env, flags, and restart policy) with the next free "(2)" name suggested. -
#173
766d4a7Thanks @theogravity! - Notifications get quieter. A Stop hook no longer rings "Done, waiting for you" while the session is parked on background work; approval pushes fire only for the notification types that genuinely need a human; a pane pushes at most once until its owner opens it, escalation excepted; and the sidebar dot becomes a bell for exactly as long as a push sits unanswered. -
#179
9c2db01Thanks @theogravity! - Instance lockdown, and a Server that can decline to host subshells. Settings → General grows a Lockdown card: one button in each direction, and both open a dialog that lights its confirm only when the admin types the machine's name. Turning it ON stops every running subshell on every machine and refuses every new launch and restart (admins included) until it ends, which it does by the same typed ask; a pane that could not be stopped is reported to the admin rather than passed off as quiet, and everyone signed in sees the amber banner for as long as it holds. One switch up, the Server can now be switched off as a place to run subshells: running panes finish on their own, the machine's row stays visible and manageable, and every picker and refusal follows that one reading. On Nodes, "Add node" is always drawn and disabled with a tooltip naming who holds the switch (the same sentence is the empty state's line, so touch users are not left hovering for it), and the home page tells a viewer with nowhere to launch what to do about it: add a machine, or ask an admin. -
#183
f9286b9Thanks @theogravity! - The MCP DX wave: an agent in one pane can now act like a colleague at the keyboard.subshell mcpgainslist_nodes(which machines can run subshells, online and launchable, with the harnesses each holds), anodeargument oncreate_subshell(launch on a named machine, by id or display name),read_subshell_log(a sibling's captured output, so you can see what it printed and why it exited) andsend_to_subshell(type into a running sibling, Enter optional), whileget_subshelltakes a name as well as an id andrestart_subshelltakes a prompt to re-type a task into the revived pane. Refusals come back as next moves now: a launch that must pick a machine nameslist_nodes, an offline machine says so, a stopped pane says to start it withrestart_subshell. The pane's tool list also lost one:channel_members, whose rosterpost_channelandread_channelalready implied. Under the doors sit two new REST surfaces the tools ride:GET /api/nodesanswers a machine token with exactly its owner's own nodes (a read; the detail route and every write stay browser-only), andPOST /api/subshells/:id/inputtypes into a running pane as aneditact, so steering a sibling is exactly as allowed as typing into it in the browser. -
#175
e7d872cThanks @theogravity! - The subshell list has a new "Needs Attention" section, in the sidebar above the machine groups and on the home page above Running. It gathers the panes that pushed and have not been opened since: only your own, and only while a push sits unanswered. Opening the pane clears it; when nothing is unseen the section is gone entirely. -
#177
f77b8a0Thanks @theogravity! - The node ↔ control-plane link is now encrypted end to end. Every/ws/nodeconnection negotiates a fresh key with libsodium'scrypto_kx(each side authenticates against the long-term identity it pinned at pairing), and after that the socket carries only ratchetedcrypto_secretstreamciphertext, never resynced. A server on plainhttp://no longer puts launch commands, pane bytes, or bearer tokens on the network in the clear; whoever can see the node's network can no longer read it.This is a hard protocol cutover (13 → 14, minimum node version 0.17.0) with no plaintext fallback. Deploy order: server first, then nodes. Nodes enrolled before the link existed pair themselves on their first connect after updating (the agent mints its keypair and registers it over the socket its bearer key already authenticates), and rotating a node's key re-provisions the link identity through the same self-heal. A node that skips or fails the handshake is refused with close code 4410 and retries with its backoff, never silently degraded to plaintext.
-
#180
b28726aThanks @theogravity! - OIDC sign-in with approval (spec 2026-09-24). Sign in through external identity providers under an admin-managed list: a new Server Settings → Auth page adds doors (Google preset or generic OIDC) with per-door sign-in, registration, approval and domain rules, and the login page paints a button per open door (labeled by the door's name; many doors of one kind are legal). An OIDC identity links to the existing account when the provider asserts the email as verified, and a door can require approval: first arrivals queue on a pending screen that admins Approve or Reject from the Users page's new Pending tab, stale pendings expire on an admin-set window, and rejections look identical from the visitor's side. E-mail becomes a provider row too, so its toggles replace the global registration switch (Settings → General loses it). Guards that hold the ground: adding a provider is an admin-only trust decision priced in the security docs, the last open sign-in door cannot be closed (409), an OIDC arrival can never become first admin, andauth.sign_ingains theoidc:<door>method with exactly one row per real sign-in and none per refusal. -
#176
db5951aThanks @theogravity! - "Waiting for you" now clears on agent nodes. The only alive-path clearer was the plane's idle watcher, which can only observe a log on the plane's own disk, so a pane running on a node stayed amber from its last Stop or approval until the process died, however hard it worked. Claude Code's hooks now report a third attention kind,resumed(prompt submitted, or a tool starting after an approval), and the pane's own report clears the stamp from wherever it runs; it never reads the hook payload and never rings. Rollout: update the nodes FIRST, then the Server. The Server ships the new hook, and an oldersubshellbinary rejectsresumedas an unknown argument with exit 2, which Claude Code reads as a blocking error on every prompt and tool, so a node left behind stalls its panes until it updates. An updated node against an older Server is harmless: the report is a silent no-op there. -
#170
ac9ec22Thanks @theogravity! - Three security fixes from the 2026-09 documentation sweep, operator-approved.Admins can now see and revoke every setup key on the instance (audit item 4). An outstanding key that its creator never used used to be an enrollment door no admin could close before its 24 h expiry: the list was owner-scoped and the delete owner-filtered.
GET /api/nodes/setup-keys?all=1is now the cookie-admin view (every key, each labeled with its creator), andDELETEcloses any row, auditedsetup_key.revokewith{ foreign: true, ownerUserId }metadata (ids only, never the key text). A non-admin asking forall=1gets a 403 rather than a silently-narrowed list. The Setup keys card on the Nodes page gained the matching switch, and each foreign row shows who created it.The backend gates subshell ids before interpolating them into node-side paths (audit item 7). The agent has always checked
isNodeSubshellId; the plane now checks the same guard at every composition site (assertNodePathIdinservices/nodes/node-path-id.ts, called by theRemoteLauncherpath members and the launch-sideplanRemoteSubshellMcp), so "a hostile../../../../xnever reaches path interpolation" is true on both sides of the l...
cli-node-v1.0.0
Major Changes
- #184
711b5faThanks @theogravity! - Marked 1.0.0. The control plane, the node daemon, and both desktop apps
all mark their first stable release. This entry changes no behavior; it
records the milestone, and the entries below it are what the milestone is
made of.
Minor Changes
-
#177
f77b8a0Thanks @theogravity! - The node ↔ control-plane link is now encrypted end to end. Every/ws/nodeconnection negotiates a fresh key with libsodium'scrypto_kx(each side authenticates against the long-term identity it pinned at pairing), and after that the socket carries only ratchetedcrypto_secretstreamciphertext, never resynced. A server on plainhttp://no longer puts launch commands, pane bytes, or bearer tokens on the network in the clear; whoever can see the node's network can no longer read it.This is a hard protocol cutover (13 → 14, minimum node version 0.17.0) with no plaintext fallback. Deploy order: server first, then nodes. Nodes enrolled before the link existed pair themselves on their first connect after updating (the agent mints its keypair and registers it over the socket its bearer key already authenticates), and rotating a node's key re-provisions the link identity through the same self-heal. A node that skips or fails the handshake is refused with close code 4410 and retries with its backoff, never silently degraded to plaintext.
-
#170
ac9ec22Thanks @theogravity! - Nodes now age out their own pane logs. A subshell's transcript (every byte the terminal rendered, typed secrets and pasted tokens included) lives in a log file on the machine that ran the pane, and until now the only node-side deletion was the control plane commanding it at delete time: a node that was offline for the delete kept the transcript indefinitely, and a terminated-but-kept subshell kept it for the life of the machine. The agent sweeps its own disk instead (one pass at boot, an hourly pass after it, with no dependence on the plane being reachable), deleting the log of any subshell whose pane its tmux census does not find live, older than a window configured on the node itself:SUBSHELL_LOG_RETENTION_DAYS/SUBSHELL_LOG_RETENTION_HOURS(the environment wins), else the matchinglogRetentionDays/logRetentionHoursfields ofconfig.json, else a default of one day, deliberately shorter than the server's 30, because a node is not where transcripts should accrete.0days and0hours together keep everything forever. A running pane's log is never swept (it is the live replay buffer), a liveness probe that cannot answer counts the pane running, and only pane-log name shapes inside the data dir (never a symlink) are ever touched. This is a tightening on upgrade: an unconfigured node deletes non-running transcripts older than a day. The window now has a setter on the machine itself: the node's loopback dashboard shows the effective days and hours with the layer each came from (environment,config.json, or the default) and writes changes toconfig.json, which the next hourly sweep picks up without a restart. A field the environment forces is read-only there, named by its variable, the same rule the debug-logging switch follows; the plane has no counterpart route, because the policy belongs to the machine whose disk it ages. The boot sweep is started before the agent's first connection to the plane but no longer waits for it, and the liveness census probes concurrently, so a wedged tmux can slow a sweep but can no longer stall a node's connection. Every refusal at that setter, a validation 400 as much as an env-forcing 409, writes a line to the agent's own log, because there is no audit row on this surface and the log IS the machine's record of its own decisions.
Patch Changes
-
#170
ac9ec22Thanks @theogravity! - An update started from the node's own loopback dashboard can no longer be refused by a stale fact. During the daemon's boot window the page re-proves supervision with one live service-manager query and now carries that answer into the executor, instead of the executor re-reading the same boot-time report, still null, and answering "not supervised" to a node the page had just proved supervised. A caller with no proof, which is every plane-commanded update, keeps the old refusal exactly.The force option now states its own limit: a downgrade the control plane will not accept leaves the node held offline for about ten minutes and is then reversed automatically, so the card says that beside the checkbox instead of letting the success line imply a durable downgrade.
The same threading now covers the service card. With no daemon in the process (the standalone dashboard), the service route decided pane safety from a fresh manager read but used to word its refusal from the daemon report the route had just fallen back past, so a machine whose fresh read answeredunknown("nobody could read the definition") got the CERTAIN sentence ("would close every subshell"). The resolved report'spaneSafetynow rides to the wording, exactly as the update route's proof already does; akillsanswer keeps the certain sentence, and aliveRuntimetest seam pins both halves. -
#180
b28726aThanks @theogravity! - Dialog and tab-strip polish, and the provider admin's sharper edges. Dialog actions stay in one right-aligned row and dialog headers stay left, always: shadcn's viewport breakpoints (stack under 640px, center until 640px) tested the WINDOW, so page zoom or a narrow shell re-stacked buttons and centered text inside a comfortably wide dialog. Page tab strips (Users, Logs, Nodes) are content-sized now instead of stretching two labels across the page; the equal-share switch stays for in-row controls. The copy icon shrank to sit inside value rows. On Settings → Auth: the provider form shows its slug id live under the name, the remove confirmation lists its effects and names the exact row (slug id and issuer) and notes that re-adding the same slug id restores the accounts, close-capable toggles are DISABLED with a tooltip when a provider is the last open one (the 409 remains the enforcement, it just stops being the introduction). Thecli-nodebump covers only the smaller copy icon, which the node dashboard shares. -
#173
766d4a7Thanks @theogravity! - Notifications get quieter. A Stop hook no longer rings "Done, waiting for you" while the session is parked on background work; approval pushes fire only for the notification types that genuinely need a human; a pane pushes at most once until its owner opens it, escalation excepted; and the sidebar dot becomes a bell for exactly as long as a push sits unanswered. -
#170
ac9ec22Thanks @theogravity! - Every name is normalized the same way, and the attach journal line can no longer be forged. Manual subshell renames, named creates and both workspace name doors now run the human-typed name through the sharednormalizeLabelinstead of a bare trim; a hand-typed name no longer carries escape bytes into the restart journal line or another user's sidebar, and a name that is nothing but invisible characters is refused (subshells, workspaces) or becomes an unnamed create (a create never 400ed on a blank name and still does not).normalizeLabelitself hardened: NFC first (so two spellings of one name compose to one string and the cap counts characters of the canonical form), Unicode format characters dropped (bidi overrides, zero-width joiners and spaces, soft hyphen, BOM, emoji tag characters: the invisible half of what CR/LF do to a log line), the emoji presentation selectors dropped, and unpaired surrogates dropped, which nothing downstream could render or compare anyway. Node names and device labels inherit all of it (the node patch is that inheritance: a name typed on the machine is now stored the same rule the plane applies). Separately, the per-attachws attachjournal line clamps its User-Agent to the characters real UAs actually contain BEFORE slicing to 90 (a client can no longer close the quoted field, break the line, or mint a second record in the forensics the operator greps), and a disabled account's session cookie is refused on the no-token WS attach path, the sameaccountDisabledcheck every REST surface already applies. -
#169
78fd431Thanks @theogravity! - Fix the fresh-terminal replay so typing lands on the visible prompt.The attach replay ended with the client's cursor at the bottom of the grid
while the pane's cursor sat at the prompt near the top, so every live byte
(echo included) p...
@subshell-ai/plugin-terminal@1.0.0
Major Changes
- #184
711b5faThanks @theogravity! - Marked 1.0.0. Every harness and network plugin still below 1.0 joins the
product line's first stable release.
@subshell-ai/plugin-tailscale@1.0.0
Major Changes
- #184
711b5faThanks @theogravity! - Marked 1.0.0. Every harness and network plugin still below 1.0 joins the
product line's first stable release.
Patch Changes
- #182
6955725Thanks @theogravity! - The no-em-dash voice rule applied to shipped copy: every string a person reads on a screen or in a terminal now carries its breath with a comma, colon, parentheses, or a full stop. The tray update item reads "Update available: Subshell Server 0.8.0" (both apps), the node window title "Subshell Client: Node", network plugin hints, both CLIs' refusals and prompts, and the browser-rendered error messages lose their dashes, and so do the /docs endpoint descriptions, the shared MCP tool descriptions, and the desktop apps' permission prose. No wire name, error code, id, or log line changed.
@subshell-ai/plugin-pi@1.0.0
Major Changes
- #184
711b5faThanks @theogravity! - Marked 1.0.0. Every harness and network plugin still below 1.0 joins the
product line's first stable release.
@subshell-ai/plugin-opencode@1.0.0
Major Changes
- #184
711b5faThanks @theogravity! - Marked 1.0.0. Every harness and network plugin still below 1.0 joins the
product line's first stable release.