Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
142 commits
Select commit Hold shift + click to select a range
d8cb1fa
docs: SSH support design and multi-agent implementation plan
theogravity Oct 4, 2026
f4a9f1e
docs: drop em dashes from the SSH plan table for prose lint
theogravity Oct 5, 2026
1fc231d
feat(ssh): freeze Gate A shared SSH contracts
theogravity Oct 5, 2026
4cf7312
feat(ssh): address Gate A review (MCP shapes, unknown/255 rule, frame…
theogravity Oct 5, 2026
f5d03c1
feat(ssh): restore the MCP reference block that the fix-round commit …
theogravity Oct 5, 2026
f355b39
feat(ssh): land the MCP reference block (repro: biome assist strips t…
theogravity Oct 5, 2026
d02dc7d
feat(ssh): gateGrantedUse doc names the human arm instead of implying…
theogravity Oct 5, 2026
e935fed
feat(ssh): backend foundation - repos, deny-by-default policy, actor …
theogravity Oct 5, 2026
98ee497
feat(ssh): node adapter, connections/grants/runs/terminals services, …
theogravity Oct 5, 2026
a4b1adc
test(ssh): policy deny-by-default matrix (Gate A seam)
theogravity Oct 5, 2026
1eae829
test(ssh): connections/grants route suite; always carry the named ssh…
theogravity Oct 5, 2026
475d895
test(ssh): runs/terminals/revocation route suite with scripted node
theogravity Oct 5, 2026
4cb484a
test(ssh): retention sweep + granted-pane service projections (Gate B…
theogravity Oct 5, 2026
4832758
test(ssh): route-boot type fixes and docs/security ssh audit family
theogravity Oct 5, 2026
33d32ca
feat(ssh): refusal mapping and revocation cleanups
theogravity Oct 5, 2026
33c0212
feat(ssh): fix round 1: remoteDir revision-bearing, 255 confirmed-cla…
theogravity Oct 5, 2026
acf3132
feat(ssh): pane-runtime SSH config discovery, resolution, render poli…
theogravity Oct 5, 2026
81e1282
feat(ssh): node SSH command arms (discover, resolve, test, runs, term…
theogravity Oct 5, 2026
03f0be3
feat(ssh): process-group kills in run supervisor; discovery/render/re…
theogravity Oct 5, 2026
f98d20c
feat(ssh): durable-store and supervisor tests; honest 128-range exit …
theogravity Oct 5, 2026
8e9d60f
feat(ssh): agent-side dispatch tests for the nine ssh_* arms (gates, …
theogravity Oct 5, 2026
a50f62c
feat(ssh): terminal-log generation/rotation tests and the real-sshd t…
theogravity Oct 5, 2026
5e231ac
feat(ssh): biome formatting and import ordering for the ssh test files
theogravity Oct 5, 2026
435a34c
feat(ssh): satisfy tsc on the ssh test helpers (joined-argv string[],…
theogravity Oct 5, 2026
605aec6
feat(ssh): fix round 1 - uniform -tt terminal launch, aggregate cap i…
theogravity Oct 5, 2026
7af82cc
feat(ssh): Gate B wiring - sshRoutes mount, ssh token perm, boot+read…
theogravity Oct 5, 2026
a4af101
feat(ssh): node-side input generation fence with frozen stale refusal
theogravity Oct 5, 2026
1d3c527
feat(ssh): exec records with bounded late-marker observation + pane S…
theogravity Oct 5, 2026
b9a0b54
feat(ssh): exec status re-arm, takeover gate mapping, and attach_rede…
theogravity Oct 5, 2026
bdc0d03
feat(ssh): review round 1 fixes (I-1 summary census, I-4 surface cens…
theogravity Oct 5, 2026
405977a
test(ssh): drop the as-never cast in the nudge suite (the seeded fiel…
theogravity Oct 5, 2026
48b0511
test(ssh): give the exec-rearm test a real poll-budget timeout
theogravity Oct 5, 2026
860d883
test(ssh): refresh the machine-credential refusal for the human-only …
theogravity Oct 5, 2026
12f3ba8
feat(ssh): Gate B wiring - fence-store seam, boot policy+sweep, attac…
theogravity Oct 5, 2026
b832cfa
feat(ssh): register Gate B pane hooks - managed input/restart reach t…
theogravity Oct 5, 2026
b06f1d7
test(ssh): prove the real pane hooks fence, dispatch, and never fall …
theogravity Oct 5, 2026
4965607
feat(ssh): bump NODE_PROTOCOL_VERSION 15->16 for the SSH surface
theogravity Oct 5, 2026
6d8af84
feat(ssh): MCP SSH family - six grant-gated passthroughs with honest …
theogravity Oct 5, 2026
17e045f
test(ssh): pin the MCP SSH wire, refusal mapping, and the instruction…
theogravity Oct 5, 2026
3326801
docs(ssh): clarify ssh-pane-hooks unsupported=too-old-binary vs fail-…
theogravity Oct 5, 2026
23a4786
feat(ssh): SPA SSH connections settings, grant management, and manage…
theogravity Oct 5, 2026
bcc02f9
test(ssh): SPA coverage for the connections editor, grants, pane chro…
theogravity Oct 5, 2026
9ef1c4e
feat(ssh): review round 1 - inert takeover control while node offline…
theogravity Oct 5, 2026
fa571b4
test(ssh): adversarial cross-path suites + loopback sshd fixture (Wav…
theogravity Oct 5, 2026
5866727
docs(ssh): SSH connections page, MCP tool reference, security accounting
theogravity Oct 5, 2026
3d247b1
chore: changesets for the SSH feature (server + node minor, docs minor)
theogravity Oct 5, 2026
4bed963
test(ssh): prove the exec-records 404 ran the census route, not the r…
theogravity Oct 5, 2026
b8fe866
fix(ssh): R4 - map managed-pane sharing refusal to named 403, not 500
theogravity Oct 5, 2026
f3f7142
fix(ssh): revocation closes live streams and terminates the managed t…
theogravity Oct 5, 2026
8270e86
fix(ssh): make the terminal log-generation rotation contract real end…
theogravity Oct 5, 2026
b04c63b
fix(ssh): route the WS keystroke writer through the SSH input fence
theogravity Oct 5, 2026
cbb47d9
fix(ssh): the three Gate C minors - parked managed input, atomic run …
theogravity Oct 5, 2026
9b4866d
test(ssh): the remaining 6-matrix covers - parked managed input, capt…
theogravity Oct 5, 2026
5a6158a
fix(ssh): close managed streams the instant the plane's raise commits
theogravity Oct 5, 2026
98e79cf
fix(ssh): accept real OpenSSH -G default spellings and tilde identity…
theogravity Oct 5, 2026
23a6da4
fix(ssh): any running managed pane blocks connection deletion (C2)
theogravity Oct 5, 2026
a6d4ef2
fix(ssh): bearer restart requires the grant to pin the current revisi…
theogravity Oct 5, 2026
412bc51
fix(ssh): explicit origin check on the ssh-control takeover write (I2)
theogravity Oct 5, 2026
8dace7b
fix(ssh): refuse uploads to managed SSH panes at the server (I4)
theogravity Oct 5, 2026
e615ee9
fix(ssh): delete the dead second takeover act (I5)
theogravity Oct 5, 2026
a96eba2
fix(ssh): resolved terminal-exec rows now age out (I6)
theogravity Oct 5, 2026
6d78181
feat(ssh): run the ssh_* verbs in-process on the built-in local node …
theogravity Oct 5, 2026
b3c3dba
fix(ssh): the no-risk review minors (M2, M4, M5, M7, M8)
theogravity Oct 5, 2026
d821e3b
fix(ssh): sweep stranded probe configs; bound the built-in node's fil…
theogravity Oct 5, 2026
a1435ac
docs(ssh): pr330 fix-wave report (per-finding status, commit stats, v…
theogravity Oct 5, 2026
6626d1a
chore: drop agent report from branch (.sdd is local scratch)
theogravity Oct 5, 2026
8a00151
fix(ssh): reviewer round-2 minors - accurate C2 cascade comment, port…
theogravity Oct 5, 2026
9a18d26
fix(ssh): drop the removed KerberosAuthentication alias from the runt…
theogravity Oct 5, 2026
a69bf82
test(e2e): sshd fixture (node-port of the bun recipe) + startNode ext…
theogravity Oct 5, 2026
3d5e199
test(e2e): spec 21 SSH end-to-end (real browser + node agent + sshd) …
theogravity Oct 5, 2026
64b006b
test(e2e): green spec 21 — absolute UserKnownHostsFile, extraEnv spre…
theogravity Oct 5, 2026
39d026f
test(e2e): review wave - CI installs OpenSSH for spec 21, dead-port h…
theogravity Oct 5, 2026
ce86ffe
test(e2e): fixture ensures /run/sshd - the postinst does not in the C…
theogravity Oct 5, 2026
fb60af7
test(e2e): pin the fixture account NAME across runtimes - bun's userI…
theogravity Oct 5, 2026
c0892a2
docs: revise SSH plan to remote-runtime-over-SSH (review of PR #330 a…
theogravity Oct 5, 2026
7ab449d
docs(ssh): Gate A design freeze - session runtime over brokered SSH (…
theogravity Oct 5, 2026
9d4b565
feat(ssh): Gate A runtime sessions - start the Subshell runtime on th…
theogravity Oct 5, 2026
c416257
refactor(ssh): split the session wire family and the broker by concer…
theogravity Oct 6, 2026
1db164b
fix(ssh): review round 1 - the close door works, boot converges, laun…
theogravity Oct 6, 2026
e3cc567
fix(ssh): review round 2 - the reqId axis is bounded where it enters …
theogravity Oct 6, 2026
a76de97
test(ssh): pin the sweep's position after migrations, not just inside…
theogravity Oct 6, 2026
e691370
feat(ssh): task 25 wave - harness detect, per-pane callback doors, cr…
theogravity Oct 6, 2026
f8bc1f7
feat(ssh-runtime): personal Connect-over-SSH UX on the frozen session…
theogravity Oct 6, 2026
5b91f07
docs(ssh): journey-first SSH page; e2e spec 23 (sidebar journey as a …
theogravity Oct 6, 2026
aab542f
fix(ssh): finish the callback-doors rename in serve.ts; type the door…
theogravity Oct 6, 2026
1468bd0
feat(ssh-runtime): mount the harness verbs - sessionLaunchHarness, ro…
theogravity Oct 6, 2026
cea3f9a
fix(ssh-web): wave-2 UX review - shipped refusal sentences, two-sente…
theogravity Oct 6, 2026
95ec47a
docs(ssh): honest launch step and control-plane refusal note; Server …
theogravity Oct 6, 2026
1ec8acf
fix(ssh): review wave 2 - the throwing arm drops the door, the dialec…
theogravity Oct 6, 2026
d713d2b
test(server): the suite goes green with the live instance up - 0049 a…
theogravity Oct 6, 2026
032b857
test(ssh-web): pin the identity line's lost/closed qualification and …
theogravity Oct 6, 2026
0989f04
style(nodes): colon for the detectRowToEntry export note (review N-R1)
theogravity Oct 6, 2026
1608bbf
refactor(ssh-runtime): re-home the live discovery/resolve/caller seam…
theogravity Oct 6, 2026
428e887
feat(ssh): Workstream C - delete the destination-execution product fr…
theogravity Oct 6, 2026
847d73c
refactor(ssh): retire the destination run/terminal machinery on the node
theogravity Oct 6, 2026
79e40da
refactor(ssh): retire the MCP ssh family, the coarse ssh token scope,…
theogravity Oct 6, 2026
7538bbd
refactor(ssh): retire the admin SSH page and the managed-pane chrome …
theogravity Oct 6, 2026
2e23ee5
refactor(ssh): prune the retired arms from the node protocol, no vers…
theogravity Oct 6, 2026
0cc93a7
test(e2e): retire spec 21; ssh-support keeps only what 22 and 23 use
theogravity Oct 6, 2026
b5638e9
docs: retire the SSH destination product from the threat model and th…
theogravity Oct 6, 2026
636922d
docs(ssh): truth-comment the surviving input-generation fence
theogravity Oct 6, 2026
2330a7f
fix(deps): in-range bumps for the three October advisories; justified…
theogravity Oct 6, 2026
fc2586b
fix(ssh): 0050 deletes the ssh_panes-marked pane rows before the mark…
theogravity Oct 6, 2026
551b75e
docs(ssh): truth-comment the retirement seams (review wave M1, M2, N1…
theogravity Oct 6, 2026
8ad162c
test(ssh): pin the six guardrails the acceptance run measured without…
theogravity Oct 6, 2026
e5e565e
fix(ws): serve the live-terminal relay for SSH-runtime panes (F1)
theogravity Oct 6, 2026
f43b306
fix(ssh-runtime): refuse a launch into a missing destination director…
theogravity Oct 6, 2026
e51e8be
feat(ssh-runtime): re-adopt surviving destination panes on reopen (F2)
theogravity Oct 6, 2026
acbcd54
fix(agent): probe resolves the launch-recorded socket, not the deriva…
theogravity Oct 6, 2026
e3dc43b
test(e2e): live-ws attach, invalid-directory refusal, and reopen re-a…
theogravity Oct 6, 2026
f72ab71
refactor(ssh-runtime): the F3 directory gate moves to launch-dir.ts (…
theogravity Oct 6, 2026
0df3c14
fix(ssh-runtime): close the re-adopt race seams; truth-comments from …
theogravity Oct 6, 2026
4148e87
test(ssh): assert the rotated key identity on the death-during-rotati…
theogravity Oct 6, 2026
272c33f
fix(pane-runtime): seed the replay log from pane history after pipe-p…
theogravity Oct 6, 2026
677b70b
fix(pane-runtime): strip the mode preamble before the silent-pane gua…
theogravity Oct 6, 2026
c47652e
fix(ssh-runtime): size log/tail frames to the session codec (review C1)
theogravity Oct 6, 2026
ecbd979
fix(ssh-runtime): normalize the callback path before the allowlist ma…
theogravity Oct 6, 2026
f70d396
fix(nodes): isNodeOffline consults the runtime-session registry (revi…
theogravity Oct 6, 2026
2a3fae9
fix(ssh-runtime): settling trusts only ids the session issued (review…
theogravity Oct 6, 2026
b2fb8c9
fix(ssh-runtime): a bind refuses a LIVE door, so no second serve can …
theogravity Oct 6, 2026
dff650b
fix(ssh-runtime): a relative cwd answers dir_relative 409, not a sess…
theogravity Oct 6, 2026
1f0e59f
fix(ssh): the session_frame pump honors its own 192 KiB claim (review…
theogravity Oct 6, 2026
e05f449
docs(ssh-runtime): state the two accepted residuals the review named …
theogravity Oct 6, 2026
0fc640a
fix(ssh-runtime): dir_relative answers 409 like its dir_missing sibling
theogravity Oct 6, 2026
fe54a52
docs(ssh-runtime): truth the callback-sock steal note; no plane gate …
theogravity Oct 6, 2026
81348bc
fix(ssh): an occupied destination names session_in_use, not runtime_m…
theogravity Oct 6, 2026
0904c6a
fix(uploads): a runtime-kind pane refuses upload by name, no RPC comp…
theogravity Oct 6, 2026
12fe0d9
fix(ssh): a link flap reclaims brokered SSH children (review M1)
theogravity Oct 6, 2026
711104e
fix(ssh): every link-close ordering loses the sessions that rode it (…
theogravity Oct 6, 2026
c6f861a
fix(ssh): a link flap cannot stall the daemon or outlive a late-born …
theogravity Oct 6, 2026
d511d6a
docs(ssh): name the real reason for no plane-side second-session gate…
theogravity Oct 6, 2026
4f39486
fix(nodes): a forced eviction marks the sessions riding the link (rev…
theogravity Oct 6, 2026
bc42e75
fix(ssh): close() kills first, records best-effort (review round-4 MI…
theogravity Oct 6, 2026
4dfdd3a
docs(ssh): truth the drain guard's rationale to the kill-first close()
theogravity Oct 6, 2026
3ffbd5e
fix(ssh): integrate remote work into subshell and workspace launch
theogravity Oct 7, 2026
14acfc3
feat(ssh): add desktop connections and remote workspace recovery
theogravity Oct 7, 2026
808bf1a
fix(ssh): guide setup recovery and keep launch actions visible
theogravity Oct 7, 2026
279864b
fix(ssh): make setup and connection copy more approachable
theogravity Oct 7, 2026
152dd79
fix(web): group nodes presets and prompts under settings
theogravity Oct 7, 2026
5012517
fix(ui): name personal connections SSH Connections
theogravity Oct 7, 2026
d0f9ca0
feat(ssh-connect): primary-first launch, machine picker, direction-aw…
theogravity Oct 7, 2026
29fec35
docs(ssh): drop SSH-SUPPORT.md (superseded by the feat/ssh-anywhere d…
theogravity Oct 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/remote-work-launch-flow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@internal/server": patch
"@internal/desktop-server": patch
---

Choose an SSH host when creating a subshell or workspace pane, reuse existing connections, and reconnect from remote panes. Keep connection management secondary to starting work. Allow administrators to connect through the server machine's SSH account without enrolling another node, and deliver initial prompts to remote agents.
6 changes: 6 additions & 0 deletions .changeset/settings-navigation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@internal/server": patch
"@internal/desktop-server": patch
---

Move Nodes, Presets, and Prompts into the Settings navigation group.
8 changes: 8 additions & 0 deletions .changeset/ssh-connections-label.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@internal/server": patch
"@internal/desktop-server": patch
"@internal/desktop-client": patch
"@internal/docs": patch
---

Rename the personal Connections navigation item to SSH Connections and update setup instructions to match.
6 changes: 6 additions & 0 deletions .changeset/ssh-friendly-copy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@internal/server": patch
"@internal/desktop-server": patch
---

Make SSH setup, pairing, and connection errors clearer and more conversational, with plain-language next steps.
9 changes: 9 additions & 0 deletions .changeset/ssh-personal-connections.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@internal/server": minor
"@internal/desktop-server": minor
"@internal/node": minor
"@internal/desktop-client": minor
"@internal/docs": patch
---

Add personal SSH connections from Subshell Client without node enrollment, saved remote folders, actionable host setup, and in-place pane reconnection. Keep browser connections through owned nodes and authorized server accounts.
6 changes: 6 additions & 0 deletions .changeset/ssh-setup-guidance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@internal/server": patch
"@internal/desktop-server": patch
---

Show SSH setup help only when needed, with copyable configuration and recovery commands. Keep launch actions visible while forms scroll and hide launch buttons until SSH is connected.
5 changes: 5 additions & 0 deletions .changeset/ssh-support-docs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@internal/docs": minor
---

A new SSH connections section: human setup on a node, the per-pane grant model, route identity, and the honest no-replay, takeover-controls-input-not-secrecy, and output-is-data rules, plus what v1 does not do. The MCP tool reference gained the five SSH tools and get_terminal_execution, the exec_in_terminal entry teaches the durable executionId and its late-result recovery, terminal sessions and the MCP overview point at the managed SSH loop, and the security model documents the SSH controls as section 4b.
6 changes: 6 additions & 0 deletions .changeset/ssh-support.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@internal/server": minor
"@internal/node": minor
---

Agents can work on plain SSH machines: a human saves an SSH connection through an enrolled node (aliases discovered from the node's config, destination resolved and tested, nothing but an approved normalized snapshot stored), grants it to individual running panes, and the granted pane can run structured commands with bounded incremental output and honest lifecycle, cancel them, and open managed interactive SSH terminals whose input and output ride the ordinary pane tools. The five MCP SSH tools plus get_terminal_execution and the /api/ssh routes carry it; machine credentials cannot configure anything, every generic pane surface asks the same SSH policy, and sharing managed panes is refused in v1. Nothing is ever replayed after an ambiguous disconnect (a lost answer stays `accepted`, reconciliation asks, never restarts), exit 255 is never claimed as a confirmed remote result, and cancellation confirms only the local process stopped. Commands run under a mandatory every-hop policy (strict host checking, no forwarding, no escape characters, no control sockets, key/certificate auth only); credentials, config contents, command text, and output never reach the server, its logs, or the audit trail. The node protocol becomes 16 to carry the nine ssh_* commands, so update the server before the nodes: a lagging agent is held and crosses the bump on the update command.
13 changes: 11 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -928,15 +928,24 @@ jobs:
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"

# The session-lifecycle spec spawns real tmux sessions (stub harness).
- name: Ensure tmux and jq
# Spec 21's real-sshd composition needs ssh, ssh-keygen and sshd: the
# builder image carries none of them, so WITHOUT these the SSH specs
# loud-skip on every CI run - an honest skip, but a flagship suite that
# silently never runs is what e2e/AGENTS.md promises against. The
# fixture daemon's privilege-separation dir (/run/sshd) is NOT created
# by the postinst in this non-systemd container (measured on the exact
# image); the fixture itself ensures it before spawning.
- name: Ensure tmux, jq, and OpenSSH
run: |
set -euo pipefail
missing=()
for tool in tmux jq; do command -v "$tool" >/dev/null || missing+=("$tool"); done
{ command -v ssh >/dev/null && command -v ssh-keygen >/dev/null; } || missing+=(openssh-client)
command -v sshd >/dev/null || [ -x /usr/sbin/sshd ] || missing+=(openssh-server)
if [ ${#missing[@]} -gt 0 ]; then
apt-get update && apt-get install -y --no-install-recommends "${missing[@]}"
fi
tmux -V && jq --version
tmux -V && jq --version && ssh -V && ls -x /usr/sbin/sshd

# tsdown/rolldown run under NODE, not bun — their bin shebang is
# `#!/usr/bin/env node` — so the repo has always had a Node requirement
Expand Down
20 changes: 19 additions & 1 deletion apps/client/desktop/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,9 @@ plan parsed from the node's own `status --json` `paths` block AT PRESS TIME
with `config.json` deleted LAST so a half-run stays resumable
(`deletion_order` is a tested pure function), the shared guards in
`subshell_desktop_core::reset_guards`, and `Err` only for refusals before
the first mutation. `planeUrl` is KEPT. **Working on reset: read
the first mutation. Saved control planes are cleared. Desktop SSH children
are stopped and their saved credentials and connection metadata are removed
before deleting node config; failed cleanup remains retryable. **Working on reset: read
`apps/client/desktop/docs/reset.md` first.**

## Things that will bite
Expand Down Expand Up @@ -536,3 +538,19 @@ the first mutation. `planeUrl` is KEPT. **Working on reset: read
plum here, near-black for the server), which is also why the tray icon is
NOT `icon_as_template(true)` on macOS: a template icon is drawn from the alpha
channel alone and both would collapse to the same filled square.

## Desktop SSH connections

The bundled assistant's **SSH Connections** screen pairs this computer with a
signed-in server account without node enrollment. A first-run SSH choice reaches
it directly. The server-loaded main window still has exactly one command;
`node_ssh_*` permissions belong only to `capabilities/node.json`.

`src-tauri/src/ssh.rs` starts the bundled `subshell ssh-broker` child (resolved
CLI fallback in development), passes pairing codes over stdin, and retains that
pipe for the child's lifetime. EOF or a stop line drains the broker's SSH children.
The CLI holds private, origin-bound credentials; native metadata stores only the
server, broker id, and display name. Pairing also remembers the server bookmark,
so an SSH-only client remains configured after restart. Disconnected connections
are resumed explicitly from the bundled screen. See the public procedure at
`apps/docs/content/docs/ssh/index.mdx`.
13 changes: 10 additions & 3 deletions apps/client/desktop/docs/reset.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,16 @@ binary where its own data lived.
manage window and a zero-window moment quits it; resetting a node here
invalidates neither of this app's windows, and the page's own re-probe lands
it on Enroll.
- **`planeUrl` is KEPT.** The control plane this person watches is not what they
reset; making them retype its address to get their dashboard back would be
the reset reaching past what it promised.
- **Saved control planes are cleared**, including the last-open memory, so a
successful reset returns to first run (operator ruling 2026-09-22).
- **Desktop SSH access is removed before node config deletion.** Reset stops
every broker child, calls the CLI's idempotent `ssh-broker --forget` for each
remembered connection, and clears native connection metadata. A failure
keeps the metadata and node config so Retry can complete. The connection
lock remains held until reset returns, and concurrent pairing refuses.
Remote panes stay running; SSH keys and config are untouched. An SSH-only
client has no node delete plan: the dialog directs it to SSH Connections →
Disconnect → Forget instead.
- **Channel discipline:** `Err` only for refusals BEFORE the first mutation. A
half-run is `Ok(ActionResult { ok: false })` with the verbatim log and the
plan still stashed, so a Retry converges.
Expand Down
6 changes: 5 additions & 1 deletion apps/client/desktop/src-tauri/capabilities/node.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@
"allow-node-about",
"allow-node-open-web",
"allow-node-check-app-update",
"allow-node-install-app-update"
"allow-node-install-app-update",
"allow-node-ssh-connections",
"allow-node-ssh-connect",
"allow-node-ssh-disconnect",
"allow-node-ssh-forget"
]
}
21 changes: 21 additions & 0 deletions apps/client/desktop/src-tauri/permissions/desktop.toml
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,24 @@ commands.allow = ["node_install_app_update"]
identifier = "allow-desktop-open-in-browser"
description = "Open one page of the control plane this window is already on, in the system browser. `desktop_`, not `node_`, because it is the one permission in this file that is NOT a node verb: it is granted to `main` (the plane's own page) and to nothing else, which is the opposite of every rule above it. It takes a PATH and nothing else (no scheme, no protocol-relative '//host', no backslash, no whitespace or control characters; see crates/desktop-core's browser::browser_url) and joins it onto the origin windows.rs's PlanePin holds, so the page names the route and this side names the host. The tray and menu-bar routes to the same act need no permission at all: they run in Rust."
commands.allow = ["desktop_open_in_browser"]


[[permission]]
identifier = "allow-node-ssh-connections"
description = "Bundled page only: read this computer's SSH connections."
commands.allow = ["node_ssh_connections"]

[[permission]]
identifier = "allow-node-ssh-connect"
description = "Bundled page only: connect this computer to a reviewed server for SSH."
commands.allow = ["node_ssh_connect"]

[[permission]]
identifier = "allow-node-ssh-disconnect"
description = "Bundled page only: disconnect this computer's SSH transport."
commands.allow = ["node_ssh_disconnect"]

[[permission]]
identifier = "allow-node-ssh-forget"
description = "Bundled page only: remove a local SSH connection credential."
commands.allow = ["node_ssh_forget"]
7 changes: 7 additions & 0 deletions apps/client/desktop/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ mod app_update;
mod control;
mod node_bin;
mod reset;
mod ssh;
// macOS only: a GTK menu bar is per-window chrome rather than a system bar, so
// Linux has none — and a module compiled there would be entirely dead code.
// Gated at the MODULE, never at the call site, so `cargo clippy` on Linux sees
Expand Down Expand Up @@ -162,7 +163,12 @@ pub fn run() {
.manage(windows::PlanePin::new())
.manage(windows::PendingScreen::new())
.manage(reset::Stash::default())
.manage(ssh::SshState::default())
.invoke_handler(tauri::generate_handler![
ssh::node_ssh_connections,
ssh::node_ssh_connect,
ssh::node_ssh_disconnect,
ssh::node_ssh_forget,
control::node_probe,
control::node_install_cli,
control::node_install_tmux,
Expand Down Expand Up @@ -265,6 +271,7 @@ pub fn run() {
// from the Dock, only from the tray.
#[cfg(target_os = "macos")]
tauri::RunEvent::Reopen { .. } => windows::focus_any(app),
tauri::RunEvent::Exit => ssh::shutdown(app),
_ => {}
});
}
Expand Down
14 changes: 14 additions & 0 deletions apps/client/desktop/src-tauri/src/reset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,20 @@ pub fn node_reset(app: AppHandle, settings: State<'_, SettingsState>, typed: Str
),
});
}
// Desktop SSH is independent of node enrollment. Remove its authority
// before config.json, preserving the staged node plan for a failed retry.
// The guard prevents a new pairing until the entire reset has returned.
let _ssh_reset = match crate::ssh::reset_access(&app) {
Ok(guard) => guard,
Err(stderr) => {
return Ok(ActionResult {
ok: false,
stdout: log,
stderr,
})
}
};
log.push_str("disconnected desktop SSH and removed saved SSH credentials\n");
// 4. Delete, absence = done, config.json last. Every deletion's failure
// ends the chain: a surviving node key announced as a completed reset is
// worse than a refusal, because the hostname was typed against the promise
Expand Down
Loading
Loading