Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
6 changes: 6 additions & 0 deletions .changeset/agent-log-becomes-daemon-log.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@internal/node": patch
"@internal/desktop-client": patch
---

The node's own log file is `logs/daemon.log` where it was `logs/agent.log`, the name matching the word. Nothing migrates the file: every reader (the plane's log view, the machine's loopback dashboard, `subshell status`, reset, the desktop app) falls back to the old name until the daemon has written the new one, so an upgraded node shows its history from the first poll rather than after its first line.
8 changes: 8 additions & 0 deletions .changeset/agent-means-the-ai-agent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@internal/server": patch
"@internal/node": patch
"@internal/desktop-client": patch
"@internal/desktop-server": patch
---

The word "agent" now means the AI agent a subshell pane drives, and nothing else. The node's background program is the **daemon** (or node daemon), everywhere a person reads about it: the Nodes table's `Agent` column is **Version**, the update card says the daemon restarts itself, a refused version reads "The server will not accept this machine's Subshell daemon version. You can still update it from here.", `subshell setup` asks "Run the Subshell daemon in the background and start it at login?", and the daemon's own refusal lines, probe warnings, update errors and both desktop apps' descriptions and reset confirmations say daemon too. Status sentences were rewritten from the reader's seat: what is true of their machine, then what they can still do, with no protocol verbs. The rule and its worked example live in `apps/docs/STYLE.md`.
13 changes: 13 additions & 0 deletions .changeset/clearer-product-labels.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
"@internal/server": patch
"@internal/node": patch
"@internal/desktop-server": patch
"@internal/desktop-client": patch
"@internal/docs": patch
---

Clarify lockdown's limits, use “Ready for your next message” for completed agent turns, and rename the sidebar group to “Created by agents” with a simpler tooltip. Use “Insert prompt” consistently in the interface and guide, change attention notifications to “Waiting for you”, and explain that maintenance may not stop every subshell or restart stopped ones. Rename the native server menu entry to “Open Server Controls” and explain the term “subshell” in the documentation.

Clarify that a prompt stack combines instructions into one prompt, and simplify setup-key revocation and removal confirmations.

Keep confirmation titles fixed and move names, addresses, counts, and versions into the body. Clarify restart interruptions, node re-registration timing, prompt deletion, plugin trust, and update recovery.
8 changes: 8 additions & 0 deletions .changeset/conversational-backend-errors.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@internal/server": patch
"@internal/node": patch
"@internal/desktop-server": patch
"@internal/desktop-client": patch
---

Clarify backend error messages shown in apps and API responses. Replace internal jargon with plain language, add useful next steps, and correct the server log navigation hint while keeping error codes and HTTP statuses unchanged.
14 changes: 14 additions & 0 deletions .changeset/conversational-cli-and-tool-copy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
"@internal/server": patch
"@internal/node": patch
"@internal/desktop-server": patch
"@internal/desktop-client": patch
"@internal/docs": patch
"@internal/website": patch
"@subshell-ai/plugin-api": patch
"@subshell-ai/plugin-headscale": patch
"@subshell-ai/plugin-netbird": patch
"@subshell-ai/plugin-cloudflare-tunnel": patch
---

Make CLI help, installer messages, desktop menus and dialogs, notifications, and MCP tool descriptions easier to follow. Clarify command timeouts and agent cleanup, refresh README wording, and align documentation with current interface labels.
7 changes: 7 additions & 0 deletions .changeset/conversational-writing-guidance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@internal/docs": patch
---

Expand the documentation contributor guide with guidance on conversational writing, active voice, natural contractions, and examples that also apply to app copy and code comments.

Document fixed UI titles, precise action wording, and consequence-first confirmations, with examples and a flexible length guideline.
5 changes: 5 additions & 0 deletions .changeset/invisible-node-reveals-nothing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@internal/server": patch
---

A sidebar group headed "node you can't see" no longer spells that machine's full id on hover. The id was a leftover reveal from the retired node pill, meant for telling machines apart; for a node the registry has already refused to name it discloses nothing a reader can act on (ruling 2026-10-10). A cold registry still reveals the full id under a short one, because there the machine is the reader's own, merely not loaded yet.
16 changes: 16 additions & 0 deletions .changeset/natural-product-copy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
"@internal/server": patch
"@internal/node": patch
"@internal/desktop-client": patch
"@internal/desktop-server": patch
"@internal/docs": patch
"@internal/website": patch
"@subshell-ai/plugin-cloudflare-tunnel": patch
"@subshell-ai/plugin-headscale": patch
"@subshell-ai/plugin-netbird": patch
"@subshell-ai/plugin-tailscale": patch
---

Make setup, settings, status messages, and website documentation clearer and more conversational. Simplify technical explanations, use active voice and natural contractions, and update outdated navigation references while preserving security warnings and operational details.

Align public maintenance, restart, node recovery, and prompt-stack guides with the current interface, and clarify website sharing and prompt explanations.
8 changes: 8 additions & 0 deletions .changeset/shared-sidebar-group.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@internal/server": patch
"@internal/desktop-server": patch
---

Group subshells shared with you under “Shared with you” when their node isn't visible, keeping sessions on visible nodes in their node groups.

Refresh sharing labels and permissions after grant changes, even when a subshell has already received live updates.
7 changes: 7 additions & 0 deletions .claude/rules/code-style.md
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,13 @@ as precedent for inline schemas elsewhere.

## JSDoc Comments

Follow [the repository's writing voice](../../AGENTS.md#writing-voice) in
comments and JSDoc. Explain the reason or constraint in natural, active English,
using contractions where they fit. Keep useful technical detail, but avoid
shouted emphasis, elaborate metaphors, and accounts of who requested a change.
Describe the behavior a maintainer needs to preserve. For example:
"Fall back to agent.log until the daemon writes its first log under the new name."

All public classes, methods, and functions should have JSDoc comments that describe:
- What the class/function does
- Parameters and their purpose
Expand Down
25 changes: 20 additions & 5 deletions .claude/rules/design-system.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@
The authoritative reference is [`docs/design-system.md`](../../docs/design-system.md);
`bun run lint:design` enforces it and fails the build on a violation.

App copy follows [the repository's writing voice](../../AGENTS.md#writing-voice):
write as you'd speak to a peer, with active verbs, natural contractions, and
varied sentence structure. Explain what's happening and what the reader can
do next. Keep explanations brief without squeezing them into long sentences.
Review tone by reading it aloud; the linter can't tell whether prose sounds
natural.

**Pick a role, never a number.** Six type roles (`display`, `heading`,
`label`, `body`, `detail`) at two weights, `strong` (600) and
`regular` (400):
Expand All @@ -19,17 +26,25 @@ Spacing on the 4px grid. A `text-[13px]`, a `font-size: 14.5px`, a
`fontSize: 17` or a hex outside a token file is refused by the check; if
the system lacks what you need, add the token to every surface in one change.

**Every UI explanation is at most two sentences, and UI copy uses no em dashes** (operator ruling 2026-09-21); surplus becomes docs or a disclosure, never help text. A lone em dash in a table or detail cell is an absence placeholder, not copy (the 2026-09-25 shipped-copy sweep kept the placeholders and rewrote only strings carrying words).
**Aim for one or two short sentences per UI explanation.** Use more when
consequences or limits need it. Keep warnings at the decision point; move
optional background to docs or a disclosure. UI copy uses no em dashes;
an absence placeholder in a table isn't prose.

**Agent** means the AI agent in a pane. Call the node background program the **daemon** or **node daemon**; a compact version column can say **Version**. See `apps/docs/STYLE.md` for examples.

Line items are `label` over `detail`, differing by weight and colour. **Every
explanation a control gives about itself is `detail`** (its help text, a
"set by the environment" note, a saved-vs-running line, a validation error),
all at one size, the same `detail` that carries metadata (timestamps,
versions, counts); `body` is running text not attached to a control.
**A dialog title is static: never interpolate user data into it unless a
spec says so** - long names wrap the heading and shove the content down;
name the act ("Send prompt to agent?") and let the body carry the data
(ruling 2026-09-30).
**Keep UI titles short and fixed.** Name the action ("Insert this prompt?",
"Close this subshell?") and put names, counts, versions, URLs, and other
variable details in the body. Apply this to dialogs, confirmations, cards,
and action headings. Object names can still appear in lists and detail
fields. Name-length limits and historical specs aren't exceptions. Follow
the [content guide](../../apps/docs/STYLE.md#product-copy-and-confirmations)
for action labels, consequences, and examples.
**A required input takes a gold `*` at rest and a gold caption once the caret
leaves it empty; a hard error (a refused value, a server refusal) stays
`text-destructive` red** (ruling 2026-09-30: gold says "nothing typed yet",
Expand Down
4 changes: 2 additions & 2 deletions .claude/rules/security-context.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,14 +94,14 @@ Node mechanics depth (link close codes and pin lifecycle, §6; setup-key revocat
- **Maintenance is a routing preference, never a quarantine:** enrolled, answers everything, takes no new subshells; turning it ON **terminates every subshell running there**, whoever owns them; retroactive by design, and affected owners learn by push. Owner-only (admin on `local`): the re-share gate `canManage`, NOT `nodeCanConfigure` and NOT the wider `canRetire` gate delete/re-register sit on; the machine's own `subshell maintenance on|off` sets the same flag; newer stamp wins, ties to the plane, so the node's mirror check is defence in depth, not a second switch. Containment is deleting the node, rotating its key, or clearing shares, all cookie-gated. Audited.
- **Service control** (signed command; cookie only, owner or `edit`, `local` refused, audited `{verb, forced}`): the agent refuses unless the manager reports this very pid, and when the definition would take its panes down without `force`; **`stop`/`uninstall` are owner-only and structurally one-way** (they end the socket that would carry the undo); `force` is refused outright on `start`/`install`.
- **Repointing** (signed `set_server_url`) is **owner-only and a real widening**: the machine dials the named host with the node key, valid on THIS plane; loopback refused outright; addresses validated by component, stored canonicalized; audited naming the NEW value only; takes effect next start.
- **Disclosure surfaces:** the runtime report (supervision, paths, tmux) attaches only for an ONLINE agent node, viewer owner or `edit`, never `local`, never `view`. `ready` discloses host facts (agentVersion, os/arch, hostname, dataDir, capabilities, selfInvoke prefix, homeDir); `detect` answers ONLY the binaries/env names the plane asked (union of `subshell.hostEnv` across enabled manifests), never scans, so a plugin install (admin) decides what env names every node may be asked. `held` is disclosed to every viewer; `runtime` is not. A node view carries `ownerLabel` (whose machine) ONLY to a cookie-admin, the `?all=1` setup-key precedent (ruling 2026-10-10); every other viewer's payload lacks the field. Agent log over HTTP (owner/`edit`) never carries pane content or argv. `PUT /api/nodes/:id/logging` (an `edit` act, cookie only, `local` refused) is refused while `SUBSHELL_DEBUG_LOGGING` forces it on: a write the next read would mask is not a success.
- **Disclosure surfaces:** the runtime report (supervision, paths, tmux) attaches only for an ONLINE agent node, viewer owner or `edit`, never `local`, never `view`. `ready` discloses host facts (agentVersion, os/arch, hostname, dataDir, capabilities, selfInvoke prefix, homeDir); `detect` answers ONLY the binaries/env names the plane asked (union of `subshell.hostEnv` across enabled manifests), never scans, so a plugin install (admin) decides what env names every node may be asked. `held` is disclosed to every viewer; `runtime` is not. A node view carries `ownerLabel` (whose machine) ONLY to a cookie-admin, the `?all=1` setup-key precedent (ruling 2026-10-10); every other viewer's payload lacks the field. Daemon log over HTTP (owner/`edit`) never carries pane content or argv. `PUT /api/nodes/:id/logging` (an `edit` act, cookie only, `local` refused) is refused while `SUBSHELL_DEBUG_LOGGING` forces it on: a write the next read would mask is not a success.
- **Refused agents are HELD, not closed** (§11.12): offline for every purpose but `update` (frames dropped after envelope parse, before dispatch); the `update` command's wire shape is FROZEN across protocol bumps for this.
- **Re-registration keys**: cookie retire-gated (owner or any admin, `canRetire`) on agent nodes; the mint is stored under the node's owner so redemption's ownership check holds; `POST /api/nodes/:id/reregister` mints a single-use 24 h setup key bound to the existing row. Redemption through enroll preserves node relationships and settings, atomically swaps credentials and public identities, revokes the old bearer and unused sibling recovery keys, and evicts live/held links. Minting disconnects nothing. Deleting the target cascades recovery keys. Recovery adds no row, so `allow_node_enrollment` gates only new-node keys. Audits `node.reregister_key|reregister` carry ids, never secrets.
- **Setup keys**: single-use, 24 h, plaintext-stored to the key's OWNER only (the future node owner; for an admin-minted recovery key that is the node's owner, not the admin who triggered the mint) (cookie-only; a machine token 403s the list); `setup_key.create` carries no metadata: the key text never enters the trail. Admin may revoke ANY row (`setup_key.revoke`, `{foreign: true}` metadata), `?all=1` is admin-only (deliberate 403, never a silently-narrowed list). The node names itself (`--name`, `SUBSHELL_NODE_NAME`; `enroll` requires it) via `normalizeNodeName`, the same rule rename applies; a RECOVERY redemption validates the supplied name but does not apply it, the existing row keeps its own. Minting is gated by `allow_node_enrollment` (absent = TRUE; enforced at the mint ONLY (the key IS the credential); bounds the future, never revokes what is outstanding). `GET /install.sh` renders identically for absent/unknown/spent/expired keys (never a binary oracle); a VALID key bound to an existing node additionally names the node it re-registers and pre-supplies the wizard's name answer (the recovery path ignores any name; the row keeps its own), which discriminates between two LIVE keys only, and either one's holder can already redeem to control the node; enroll answers three distinct 401s by design. The key rides the install URL → shell history and access logs: the accepted posture of enrollment links.
- **Agent artifacts are never anonymous**: cookie, or valid unconsumed setup key, or one-time `?update_token=` (unlocks exactly one file at one digest; never reaches the `.sha256` routes). `install.sh`'s `server=<origin>` must be exact membership of the live trusted-origin registry; the rendered script digest-verifies before the first `chmod +x`.
- **Lazy release fetch**: every release-source fetch passes `releaseFetchAllowed` (configured origin ∪ GitHub release hosts; refused **by name before any connection**, egress destination pinned); no binary bytes until a machine is served (since 2026-10-09 the tiny index/manifest reads behind the serve decision run per authenticated download, TTL-cached with a one-minute failure backoff); hashed against the digest from the **signed manifest's `assets` map** (sidecar never fetched); bounded by `MAX_ARTIFACT_BYTES` (300 MB); only manifests declaring THIS `NODE_PROTOCOL_VERSION` are offered; only files this instance fetched (`.fetched.json`) are ever superseded or deleted; the shelf is reconciled AT SERVE TIME (2026-10-09: the copy is run `<file> version`, the newest server-supportable build wins, a hand-published copy speaking this protocol with a higher semver stays truth, a wrong-protocol copy 404s instead of installing a never-connectable agent, and `.sha256` announces the SAME decision the installer's skip depends on), an update download is served release-coherently else 409 naming the remedy. Empty `SUBSHELL_RELEASE_URL` = air-gapped (the shelf is the only source and answers for itself the same way: connectable and self-consistent, or refused).
- **CLI-side**: `subshell configure --server` is unprivileged, non-destructive, one-plane-two-names. It does disclose the node key to the host named (treat it as naming a host you trust). `configure --key` refuses an `nsk_` setup key by name. Re-enrolling in Subshell Client IS confirmation-gated (it overwrites `config.json` and discards the old node key).
- **The node's loopback dashboard** (§6): 127.0.0.1:3090 with **no login by design**: the listen address plus the OS user IS the access control (`Host` loopback; `Origin` when present loopback; mutations need `content-type: application/json`). The multi-user-host gap is ACCEPTED and is a widening (a second local user can maintenance-kill, stop, or repoint); `SUBSHELL_DASHBOARD=0` is the remedy. No audit rows here, the agent log is the record; `maintenance on` here kills panes, node-CLI semantics verbatim.
- **The node's loopback dashboard** (§6): 127.0.0.1:3090 with **no login by design**: the listen address plus the OS user IS the access control (`Host` loopback; `Origin` when present loopback; mutations need `content-type: application/json`). The multi-user-host gap is ACCEPTED and is a widening (a second local user can maintenance-kill, stop, or repoint); `SUBSHELL_DASHBOARD=0` is the remedy. No audit rows here, the daemon log is the record; `maintenance on` here kills panes, node-CLI semantics verbatim.
## Plugins & installers

Plugin shape and runtime facts (loaded from disk, `PluginHost`-only, JSON identity in `package.json`, static built-ins + the one sanctioned `await import()` in `plugin-runtime.ts`, plane-built argv) live in **`AGENTS.md` ("Plugins are packages…")**; that section is the source. The security half:
Expand Down
Loading
Loading