Skip to content

Terminals: an interactive shell on the App Server's computer - #594

Merged
OldKrab merged 10 commits into
shushakov/ssh-computersfrom
shushakov/terminals
Oct 23, 2026
Merged

OldKrab merged 10 commits into
shushakov/ssh-computersfrom
shushakov/terminals

Conversation

@OldKrab

@OldKrab OldKrab commented Oct 23, 2026

Copy link
Copy Markdown
Owner

Adds Terminals (ADR-0071): the user's own interactive shell on the App Server's computer, reachable from every App Shell.

Stacked on #592. It needs that branch's base and should merge after it.

What it does

  • App Server owns each login shell, keeps about 10,000 lines of replay in memory, and ends a Terminal when it is closed, its shell exits, or App Server stops. Terminals do not keep App Server running.
  • Protocol 2.3 adds terminals/open, terminals/attach, terminals/close, the terminals subscription scope, and openTerminals in the replacement impact.
  • Byte stream: one WebSocket per viewer, ending in /terminal, admitted by a single-use attach token valid for 30 s.
    • The server sends a text ping every 15 s, busy or quiet, and the client answers pong. Each side treats 45 s of silence as a lost connection.
    • A viewer that falls behind is dropped and reattaches from the replay.
  • Frontend: a Terminal page opened from a Project's menu (starts in the Project root) or the Task header (starts in the Task Workspace).
    • Tabs carry state dots. Repeating titles are numbered.
    • Closing a Terminal that runs a command asks first.
    • A reconnecting strip appears after a lost connection, and the replay restores the screen on reconnect.
    • Phones get chip tabs and a key row that keeps the soft keyboard up.
  • Transports: the Web dev server, the Android gateway, and the Remote Client gateway forward the /terminal upgrade. A browser WebSocket cannot send headers, so the attach token is the only credential on that route.
  • Desktop:
    • Quit (the macOS menu, and the window close on Windows) asks "N terminals will close." in a native dialog before the 5 s exit fallback starts. It asks only when this Desktop owns its App Server.
    • Settings also warns before an update closes Terminals.
  • VS Code opens its own integrated terminal at the folder instead of rendering one.
  • Busy detection:
    • Linux reads the command and folder from /proc.
    • Windows treats a child process of the shell as a running command.
    • macOS detects busy but not the command name or folder (TODO).
  • Compatibility: clients stop advertising the unread openTerminal shell capability. The variant still deserializes for 2.2 clients.
  • Logging is metadata-only on every layer: ids, attempts, close codes, byte counts, durations, and reason codes.
  • Also formats the android-ssh crate, which fails cargo fmt --check on the base branch.

Verification

  • cargo fmt --check, cargo clippy --workspace --all-targets -D warnings, and cargo test --workspace pass.
  • npm run check, npm run test:gate, and the tests of the Frontend, VS Code extension, Web, and Desktop packages pass.
  • Android testDebugUnitTest lintDebug assembleDebug assembleDebugAndroidTest passes.
  • The Web Shell was exercised in a browser at wide and phone widths:
    • opening from the Project menu, live output, a second tab, reattach with replay;
    • the busy dot and close confirmation;
    • the phone key row and ^C;
    • the reconnecting strip under a held socket.

Not verified

  • The Desktop Rust crate was type-checked with stubbed system libraries only. It was not built or run, and no PR job builds it.
  • The Windows busy detection was lint-checked in isolation only.
  • The Desktop app itself, macOS, Windows, the VS Code extension at runtime, a real Android device, and an SSH Computer end to end.

ADR-0071 decides what a Terminal is and what it is not; the plan records
the approved presentation and how each layer implements it.
Protocol 2.3 adds terminals/open, terminals/attach and terminals/close,
the terminals subscription scope, and the open-Terminal count in the
replacement impact. Clients stop advertising the unread openTerminal
shell capability, which still deserializes for 2.2 clients.
App Server owns each login shell, keeps about 10,000 lines of replay in
memory, and streams input and output on a WebSocket per viewer, admitted
by a single-use attach token. Timed text pings let both sides notice a
silent connection, and Windows marks a shell with a child process busy.
A browser WebSocket cannot carry the gateway token, so the gateway
forwards an exact GET /terminal upgrade and leaves admission to the
attach token.
Opens from a Project menu or the Task header, shows a Project's
Terminals as tabs, reattaches with replay after a lost connection, adds
the phone key row, and warns before an update closes Terminals.
Quit asks natively before its exit fallback starts, from the open count
the Frontend reports, and only when this Desktop owns its App Server.
The crate joined the workspace unformatted, which fails the Rust CI job.
@OldKrab
OldKrab merged commit af73d70 into shushakov/ssh-computers Oct 23, 2026
11 checks passed
@OldKrab
OldKrab deleted the shushakov/terminals branch October 23, 2026 13:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant