feat(terminal): Xshell-style Ctrl+A+D session detach with background keep-alive - #76
Open
GOODBOY008 wants to merge 1 commit into
Open
feat(terminal): Xshell-style Ctrl+A+D session detach with background keep-alive#76GOODBOY008 wants to merge 1 commit into
GOODBOY008 wants to merge 1 commit into
Conversation
…und keep-alive Implements Ctrl+A+D to detach a terminal session into the background while the SSH connection and PTY channel stay alive (Xshell detach behavior). Backend (Rust): - Add a detached-session registry to ConnectionManager with detach/reattach methods and a background output-drain task so the remote process never stalls while detached. - Add a generation-guarded Detach WebSocket message; StartPty now re-attaches an existing detached session instead of spawning a fresh shell. - Separate per-reader cancellation tokens so detaching stops streaming without killing the underlying SSH/PTY session. - Add list_detached_sessions / has_detached_session / close_detached_session Tauri commands. Frontend (React): - Treat Ctrl+A as a prefix key: Ctrl+A then D detaches; any other key forwards the buffered Ctrl+A (\x01) to the remote so tmux/screen users are unaffected (Cmd+A still selects-all on macOS). - Show detached sessions in the sidebar with reattach/terminate actions. - Add Detach entries to the terminal and tab-bar context menus. - Add i18n keys (en/zh-CN).
Contributor
There was a problem hiding this comment.
Pull request overview
Adds Xshell-style Ctrl+A → D detach for SSH PTY terminals, keeping the backend PTY + SSH session alive while the UI tab is removed, and enabling later re-attach/termination via the sidebar.
Changes:
- Backend: introduce a detached-session registry, a WS
Detachmessage, and new Tauri commands to query/close detached sessions. - Frontend: implement Ctrl+A prefix handling in
PtyTerminal, add tab/sidebar actions for detach/reattach/close, and bridge tab-bar detach to the mounted terminal via a registry. - i18n: add strings for detach/reattach/close flows in
enandzh-CN.
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| src/locales/zh-CN.json | Adds Chinese strings for detached session toasts/sidebar/context menu. |
| src/locales/en.json | Adds English strings for detached session toasts/sidebar/context menu. |
| src/lib/terminal-detach-registry.ts | New registry to let non-terminal UI (tab bar) trigger the terminal’s WS detach handshake. |
| src/lib/terminal-callbacks-context.tsx | Adds an onDetachTab callback to the terminal callbacks interface. |
| src/components/terminal/terminal-tab-portals.tsx | Wires onDetach into PtyTerminal instances rendered via portals. |
| src/components/terminal/terminal-group-view.tsx | Passes onDetachTab down to the tab bar. |
| src/components/terminal/terminal-context-menu.tsx | Adds “Detach Session” item to terminal context menu. |
| src/components/terminal/group-tab-bar.tsx | Adds “Detach Session” item to tab-bar context menu. |
| src/components/pty-terminal.tsx | Implements Ctrl+A prefix handling, WS Detach send, and cleanup rules for detached sessions. |
| src/components/connection-manager.tsx | Adds “Detached Sessions” sidebar section with reattach/terminate actions. |
| src/App.tsx | Tracks detached sessions in state and implements detach/reattach/close handlers. |
| src-tauri/src/websocket_server.rs | Adds Detach WS message and per-reader cancellation tokens to stop streaming without killing the PTY session. |
| src-tauri/src/lib.rs | Registers new detached-session Tauri commands. |
| src-tauri/src/connection_manager.rs | Implements detached-session registry + drain task and adds unit tests. |
| src-tauri/src/commands.rs | Adds Tauri commands: list/has/close detached sessions. |
Comment on lines
+674
to
+684
| tracing::info!( | ||
| "Detaching PTY connection: {} (gen: {:?})", | ||
| connection_id, | ||
| generation | ||
| ); | ||
| // Stop the reader task so it no longer streams to this WS. | ||
| // The session itself is NOT cancelled — it stays alive in the | ||
| // backend so a later StartPty can re-attach to it. | ||
| if let Some(reader_cancel) = reader_tokens.lock().await.get(&connection_id) { | ||
| reader_cancel.cancel(); | ||
| } |
Comment on lines
+1079
to
+1089
| const handleCloseDetachedSession = useCallback(async (connectionId: string) => { | ||
| setDetachedSessions(prev => prev.filter(s => s.connectionId !== connectionId)); | ||
| try { | ||
| await invoke('close_detached_session', { connection_id: connectionId }); | ||
| toast.success(t('app.detachedSessionClosed')); | ||
| } catch (error) { | ||
| toast.error(t('app.detachedSessionCloseFailed'), { | ||
| description: error instanceof Error ? error.message : String(error), | ||
| }); | ||
| } | ||
| }, [t]); |
Comment on lines
+1011
to
+1014
| // If the tab-bar context menu initiated this, the PtyTerminal itself hasn't | ||
| // sent the Detach WS message yet — ask the mounted terminal to do so. | ||
| requestDetach(tabId); | ||
|
|
Comment on lines
+346
to
+350
| {/* Detach into background (SSH terminal tabs only) */} | ||
| {onDetachTab && tab.tabType !== 'file-browser' && tab.tabType !== 'desktop' && tab.tabType !== 'editor' && ( | ||
| <> | ||
| <ContextMenuItem onClick={() => onDetachTab(tab.id)}> | ||
| <MonitorOff className="mr-2 h-4 w-4" /> |
Comment on lines
+707
to
+712
| let session = crate::ssh::PtySession { | ||
| input_tx: tx, | ||
| output_rx: Arc::new(tokio::sync::Mutex::new(mpsc::channel::<Vec<u8>>(1).1)), | ||
| channel_id: unsafe { std::mem::transmute(0u32) }, | ||
| resize_tx: rtx, | ||
| cancel: CancellationToken::new(), |
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
Implements Xshell-style Ctrl+A+D to detach a terminal session into the background while the SSH connection and PTY channel stay alive. The remote shell keeps running and can be re-attached later, or terminated from the sidebar.
Closes #75
What changed
Backend (Rust)
connection_manager.rs: Added adetached_sessionsregistry (DetachSession { session, generation, drain_cancel }).detach_pty_connection()moves a live PTY into the registry (SSH + PTY stay alive).start_pty_connection()re-attaches an existing detached session instead of spawning a fresh shell.close_detached_session(),has_detached_session(),list_detached_sessions().websocket_server.rs: New generation-guardedDetachWS message. Per-readerCancellationTokenlets detach stop streaming without killing the session;StartPtyre-attaches.commands.rs/lib.rs: Registeredlist_detached_sessions,has_detached_session,close_detached_session.Frontend (React)
pty-terminal.tsx: Ctrl+A is now a prefix key — Ctrl+A → D detaches; Ctrl+A → any other key forwards the buffered\x01to the remote (tmux/screen friendly); Cmd+A still selects-all on macOS. On detach it sends the WSDetach, skipsCloseon unmount, and suppresses auto-reconnect.App.tsx:handleDetachTab/handleReattachSession/handleCloseDetachedSessionmanage the detached session list.connection-manager.tsx: "Detached Sessions" section in the sidebar with reattach (click) and terminate (trash) actions.terminal-detach-registry.ts(new): bridges tab-bar detach to the mounted terminal's WebSocket.en.jsonandzh-CN.json.Testing
cargo test: 111 passed (5 new tests for the detached registry).pnpm test: 510 passed.tsc --noEmit: clean.pnpm i18n:check: passes.pnpm lint: no new issues vs. base (3 pre-existing errors untouched).Notes