Skip to content

Releases: subshell-ai/subshell

desktop-server-v1.0.1

Choose a tag to compare

@github-actions github-actions released this 25 Sep 11:54
1023f14

Patch Changes

  • #197 cec0f3a Thanks @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

Choose a tag to compare

@github-actions github-actions released this 25 Sep 09:09
25e98b9

Major Changes

  • #184 711b5fa Thanks @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 b28726a Thanks @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 db5951a Thanks @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 6955725 Thanks @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

Choose a tag to compare

@github-actions github-actions released this 25 Sep 09:09
25e98b9

Major Changes

  • #184 711b5fa Thanks @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 b28726a Thanks @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 6955725 Thanks @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

Choose a tag to compare

@github-actions github-actions released this 25 Sep 09:28
bfc7d39

Patch Changes

  • #185 cafda7d Thanks @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,
    and docs/security.md names them.

cli-server-v1.0.0

Choose a tag to compare

@github-actions github-actions released this 25 Sep 09:09
25e98b9

Major Changes

  • #184 711b5fa Thanks @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 c60dfe7 Thanks @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 766d4a7 Thanks @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 9c2db01 Thanks @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 f9286b9 Thanks @theogravity! - The MCP DX wave: an agent in one pane can now act like a colleague at the keyboard. subshell mcp gains list_nodes (which machines can run subshells, online and launchable, with the harnesses each holds), a node argument on create_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) and send_to_subshell (type into a running sibling, Enter optional), while get_subshell takes a name as well as an id and restart_subshell takes 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 names list_nodes, an offline machine says so, a stopped pane says to start it with restart_subshell. The pane's tool list also lost one: channel_members, whose roster post_channel and read_channel already implied. Under the doors sit two new REST surfaces the tools ride: GET /api/nodes answers a machine token with exactly its owner's own nodes (a read; the detail route and every write stay browser-only), and POST /api/subshells/:id/input types into a running pane as an edit act, so steering a sibling is exactly as allowed as typing into it in the browser.

  • #175 e7d872c Thanks @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 f77b8a0 Thanks @theogravity! - The node ↔ control-plane link is now encrypted end to end. Every /ws/node connection negotiates a fresh key with libsodium's crypto_kx (each side authenticates against the long-term identity it pinned at pairing), and after that the socket carries only ratcheted crypto_secretstream ciphertext, never resynced. A server on plain http:// 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 b28726a Thanks @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, and auth.sign_in gains the oidc:<door> method with exactly one row per real sign-in and none per refusal.

  • #176 db5951a Thanks @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 older subshell binary rejects resumed as 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 ac9ec22 Thanks @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=1 is now the cookie-admin view (every key, each labeled with its creator), and DELETE closes any row, audited setup_key.revoke with { foreign: true, ownerUserId } metadata (ids only, never the key text). A non-admin asking for all=1 gets 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 (assertNodePathId in services/nodes/node-path-id.ts, called by the RemoteLauncher path members and the launch-side planRemoteSubshellMcp), so "a hostile ../../../../x never reaches path interpolation" is true on both sides of the l...

Read more

cli-node-v1.0.0

Choose a tag to compare

@github-actions github-actions released this 25 Sep 09:09
25e98b9

Major Changes

  • #184 711b5fa Thanks @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 f77b8a0 Thanks @theogravity! - The node ↔ control-plane link is now encrypted end to end. Every /ws/node connection negotiates a fresh key with libsodium's crypto_kx (each side authenticates against the long-term identity it pinned at pairing), and after that the socket carries only ratcheted crypto_secretstream ciphertext, never resynced. A server on plain http:// 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 ac9ec22 Thanks @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 matching logRetentionDays / logRetentionHours fields of config.json, else a default of one day, deliberately shorter than the server's 30, because a node is not where transcripts should accrete. 0 days and 0 hours 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 to config.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 ac9ec22 Thanks @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 answered unknown ("nobody could read the definition") got the CERTAIN sentence ("would close every subshell"). The resolved report's paneSafety now rides to the wording, exactly as the update route's proof already does; a kills answer keeps the certain sentence, and a liveRuntime test seam pins both halves.

  • #180 b28726a Thanks @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). The cli-node bump covers only the smaller copy icon, which the node dashboard shares.

  • #173 766d4a7 Thanks @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 ac9ec22 Thanks @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 shared normalizeLabel instead 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). normalizeLabel itself 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-attach ws attach journal 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 same accountDisabled check every REST surface already applies.

  • #169 78fd431 Thanks @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...

Read more

@subshell-ai/plugin-terminal@1.0.0

Choose a tag to compare

@github-actions github-actions released this 25 Sep 08:56
25e98b9

Major Changes

  • #184 711b5fa Thanks @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

Choose a tag to compare

@github-actions github-actions released this 25 Sep 08:56
25e98b9

Major Changes

  • #184 711b5fa Thanks @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 6955725 Thanks @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

Choose a tag to compare

@github-actions github-actions released this 25 Sep 08:56
25e98b9

Major Changes

  • #184 711b5fa Thanks @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

Choose a tag to compare

@github-actions github-actions released this 25 Sep 08:56
25e98b9

Major Changes

  • #184 711b5fa Thanks @theogravity! - Marked 1.0.0. Every harness and network plugin still below 1.0 joins the
    product line's first stable release.