Skip to content

Add SSH presets alongside agent presets - #386

Merged
theogravity merged 9 commits into
mainfrom
feat/ssh-presets
Oct 15, 2026
Merged

theogravity merged 9 commits into
mainfrom
feat/ssh-presets

Conversation

@theogravity

Copy link
Copy Markdown
Contributor

Replace saved SSH destinations and Settings → SSH with named SSH presets alongside agent presets. Users can create either type from the New preset menu, manage SSH presets in their own tab, and apply a preset without starting a connection.

SSH presets retain the connecting node, resolved destination, authentication preference, and selected remote key fingerprints. Local keys and passwords are supplied when creating a subshell and are never stored in the preset. Original SSH aliases retain their routing settings, with server-side checks preventing a changed alias from silently redirecting a saved destination. Saving during launch survives connection failures without duplicating the preset on retry.

The migration discards explicitly saved destinations, preserves unsaved recent destinations, and leaves existing agent presets intact. The legacy SSH settings URL redirects to the SSH preset tab. Includes updated documentation, the SSH website section, and release notes.

Validation:

  • Full repository test suite, type checks, lint, and prose checks.
  • Fresh frontend build and documentation/website builds.
  • All 22 SSH and agent-preset Playwright tests passed, including real SSH password/key login, cross-node key relay, and encrypted browser-key signing.
  • Regression tests for destination binding, credential exclusion, migration, failed-launch retries, and delayed preset loading.

Backend: drop the dead recent-destination remove(), keep stored alias and
description on an omitted PUT field (null still clears) and pin it with a
route test, share the fingerprint grammar with the protocol package, name
the authentication union, document the preset tables and repository, add
the write routes' OpenAPI error arms, move the save pipeline into
services/ssh-presets.service.ts, and mark migration 0054's destructive
down().

Frontend: gold requirement tone for empty-blur captions with a RequiredMark
on the name and destination labels, a plain-words 422 refusal in the preset
editor with a save-failure heading that names the act, the SSH preset table
on the shared row grammar with progressive columns, the clone suffix budget
off one length constant, the maintenance-node tooltip reason, an extracted
AgentPresetLibrary so the route is composition, and per-property docs on
SshPreset.

Docs and e2e: sync the security rules summary and MCP tools reference to the
shipped model, drop the orphaned ssh-connection.png capture, and tighten the
preset-route interception predicate.
The update transform picked its key set from the bare presence of
authentication, so a partial SSH body fell through to the all-optional
agent schema and renamed the wrong kind of row with a 200. SSH intent
must now carry the complete discriminator fields or the transform
refuses it naming the gap.

A node switch that kept the canonical destination and omitted the
resolution token re-resolved the canonical STRING as a token, letting a
second machine rewrite the row to nonsense. The stored alias and the
expectedDestination guard now key off the destination TEXT, so a
machine switch reproduces the saved address or is refused.

Add the missing PUT coverage (cross-kind refusals, the node-switch
guard), sync the stale saved-host doc blocks to the shipped model, and
merge the split fingerprint-grammar doc block in the protocol.
The presets page turned New preset into a two-item menu (agent vs SSH);
this spec still expected the old direct-to-dialog button and timed out
waiting for a dialog that never opened.
Sync the use-ssh test header to the renamed cache key and drop the
saved-hosts claim from the apiFetch 204 test (no route answers 204
today; the helper contract stands on its own). The SSH preset table now
says a node's name only once the nodes read has answered, so a cold
deep link is not a wall of 'Node unavailable', and the SSH empty state
mirrors the agent tab's dialog guard. Pin the save-before-launch 422:
the heading names the save, the body is the mapped cause, and no launch
rode the attempt.
Restore the audits-nothing pin that the saved-hosts removal took with
it: one test counts auditEvents across preset create, update, and
delete. The node-switch test gains the server-derived guard's missing
arm: a new node that answers accepted but resolves elsewhere than the
stored address is a 400 CONFIG_INVALID with the row unmoved. Trim the
alias like the name (a whitespace-only alias clears instead of storing
whitespace), document that a null resolutionAlias falls back to the
destination instead of clearing, drop the dead unchangedDestination
fact, and declare the GET /api/presets 403 the bearer refusal already
sends.
…ions

The round-3 response map gave GET /api/presets its 403 but left out the
401 the guard emits on every unauthenticated read, which POST and PUT
already declare. The resolutionAlias description's 'falls back to the
destination' is exact only when the destination changed; on a PUT that
keeps the destination, the fallback is the stored token itself, so say
both arms. And since whitespace-only aliases now clear like nulls, the
alias description says so.
The tool only ever returns agent presets (the server refuses the SSH
kind to machine actors), and the docs table already promised that. Say
it in the description agents read, so nobody hunts for SSH presets in
the wrong door.
@theogravity
theogravity merged commit 2013aed into main Oct 15, 2026
21 checks passed
@theogravity
theogravity deleted the feat/ssh-presets branch October 15, 2026 19:46
@github-actions github-actions Bot locked and limited conversation to collaborators Oct 15, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant