Remote servers - #4
Open
BowgartField wants to merge 25 commits into
Open
BowgartField wants to merge 25 commits into
BowgartField wants to merge 25 commits into
Conversation
Add multi-backend transport layer so native app can route project-scoped commands to a remote Jean server over SSH tunnel instead of local Tauri IPC. - WsTransport accepts baseUrl + fixedToken for remote instances - Remote transport registry with listen() fan-out to active remotes; handlers registered before connect() are retroactively applied to new remote transports as they come online - invoke() routes via _backendHandle arg to the matching remote transport - listen() fans out to all active remote transports in native mode (covers streaming events with no call-site changes) - registerRemoteTransport / unregisterRemoteTransport called automatically on connect/disconnect in useConnectRemoteServer / useDisconnectRemoteServer - Project.server_id (TS + Rust) marks which remote server owns a project - useProjectBackendHandle + useBackendHandleForWorktree resolve server_id from TanStack Query cache for hooks - useWorktrees passes _backendHandle when project is remote - terminal-instances: all invoke calls thread backendHandle from PersistentTerminal - TerminalView + useTerminal thread backendHandle from worktree context
- SSH agent forwarding (-o ForwardAgent=yes) on exec connections so git can authenticate on the remote using the local SSH agent; no credentials stored on the server - RemoteClone type (Rust + TS): tracks server_id + remote_path per project - clone_project_to_remote command: SSH git clone (or fetch if already cloned), idempotent, defaults remote path to ~/jean/<project-name>, registered in both native handler and WS dispatch - useAutoConnectRemoteServers: silent startup hook that connects all provisioned servers and calls registerRemoteTransport on each - Sidebar Local/Remote toggle: shown only when a provisioned server exists; Remote view grays out projects not yet cloned on any server - "Clone to remote" context menu on projects: submenu per connected server, toast feedback, invalidates project cache on success - SessionRemoteBackendPicker: chip button in chat toolbar (hidden when no remote available) to choose where the session runs — Local or a server
- resolve_ssh_url_aliases(): runs ssh -G on the git remote host before cloning so local ~/.ssh/config Host aliases (e.g. github.com-myaccount) are rewritten to real hostnames the remote server can actually resolve - Add GIT_SSH_COMMAND='ssh -o StrictHostKeyChecking=accept-new' so the remote server accepts github.com host key on first connect without a known_hosts entry - In-memory passphrase cache in keychain.rs: macOS Keychain is only accessed once per app session; subsequent SSH invocations (including ControlMaster-multiplexed ones) read from cache with no system prompt
ssh -G <alias> now also extracts IdentityFile from the local SSH config. Before cloning, ssh-add is called on that file (with --apple-use-keychain on macOS to load passphrase from Keychain silently). This ensures agent forwarding carries the right key to the remote, fixing "Permission denied (publickey)" when the user's SSH config uses a host alias with a custom identity file that isn't pre-loaded in the agent.
feat(doc): update
Owner
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.





Summary
This PR adds end-to-end remote server support to Jean. A desktop client can
register and provision a Linux server, connect to it through an SSH tunnel,
clone projects and create worktrees on it, then run Jean chat sessions and
terminals against the remote backend while local work remains available.
Jean reuses its existing headless HTTP/WebSocket command surface:
The remote backend binds to
127.0.0.1and is only reachable through theauthenticated SSH tunnel.
Completed
Server management and provisioning
config aliases, identity loading, agent forwarding, and password auth.
the matching signed Jean AppImage, and a managed systemd service.
before accepting a connection.
Remote transport and routing
WsTransportper remote server while preserving native IPC forlocal work.
_backendHandleand retain the originating server onremote events.
operations to the owning backend.
backend.
Projects, worktrees, and sessions
caches and persisted UI state.
Recovery and restart behavior
visible after restarting Jean.
How to test
Full walkthrough (with recovery and cleanup) lives in
docs/developer/remote-servers.md.Automated paths:
bun run test:remote-tunnel— Docker SSH transport + tunnel/replay recovery.bun run test:remote-provision— full provisioning path against a Lima LinuxVM (macOS; needs
brew install limaand a debugjeanbinary).Manual UI walkthrough (macOS + Lima):
bun run test:remote-provision:spinup— boots thejean-remote-provision-testLima VM and prints its SSH host/port/user/identity file.
bun run tauri:dev).choose SSH key auth, point at the printed identity file. The card verifies
SSH and reports the detected OS/arch.
step timeline + live logs; success only after
/api/authresponds.projects/CLIs appear on the card.
no key copied to the server).
cancel / close / archive / resume all route to the remote backend.
pkill -f "ssh -N -L"or restart the app — the tunnelrecreates on a new local port and remote worktrees reappear without being
recreated.
limactl delete --force jean-remote-provision-test.Validation
provision/connect, remote clone, remote worktree creation, remote Claude
login, session creation, prompt/response, Jean restart, worktree discovery,
and chat resume.
33focused frontend tests pass for transport bootstrap, reconnect cacheinvalidation, remote Claude auth, remote server settings, and chat routing.
credential values are committed.
Known constraints and follow-ups
package manager.
mode. A true GUI-free server binary is separate work.
recommended until an encrypted cross-platform password vault is added.
local-and-remote concurrency, other AI backends, supported Linux
distributions/architectures, and non-macOS desktop clients.
outside this PR.