fix(terminals): decouple PTY lifecycle from view lifecycle (no kill on navigation)
Navigating (layout/tab switch) tore the xterm view down and called handle.close(), killing the backend PTY and cutting off running AIs. Now the view's cleanup only detaches; only an explicit user action kills a PTY. Backend: - PortablePtyAdapter: per-session scrollback ring buffer (~100KB, most recent) + re-subscribable fan-out broadcast replacing the single-take output_rx. Reader thread feeds both the ring buffer and current subscribers; on EOF it closes subscribers (streams end) while keeping scrollback for late re-attach. - PtyPort: new scrollback() method; subscribe_output is now re-subscribable (all impls + test fakes updated). - reattach_terminal IPC command: returns scrollback and re-wires a fresh output channel on the live session without re-spawning. - CloseRequested hook kills all live PTYs cleanly on app shutdown. - TerminalSessions::handles() to enumerate live sessions at shutdown. Frontend: - TerminalHandle.detach(); TerminalGateway/AgentGateway.reattach() + mocks. - TerminalView cleanup detaches (never close); on mount it re-attaches to a persisted session (repainting scrollback) instead of opening a new PTY. - LayoutGrid persists the cell's session id via setSession; AgentsPanel tracks per-agent session ids — both drive reattach-vs-open. Tests: ring buffer bounds to 100KB keeping newest bytes; scrollback retained; re-subscription delivers post-reattach output; TerminalView detaches (not closes) on unmount and reattaches with a known session; mock detach/reattach. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -334,10 +334,24 @@ pub trait PtyPort: Send + Sync {
|
||||
|
||||
/// Subscribes to the PTY's byte output stream.
|
||||
///
|
||||
/// Re-subscribable: each call returns a fresh stream that receives every
|
||||
/// chunk produced **from now on**. Combined with [`scrollback`](Self::scrollback)
|
||||
/// this lets the presentation layer *re-attach* a view to a still-living PTY
|
||||
/// after a navigation/layout change tore the previous view down — without
|
||||
/// re-spawning the process.
|
||||
///
|
||||
/// # Errors
|
||||
/// [`PtyError`] if the handle is unknown.
|
||||
fn subscribe_output(&self, handle: &PtyHandle) -> Result<OutputStream, PtyError>;
|
||||
|
||||
/// Returns the recent output retained for the session (a bounded scrollback
|
||||
/// ring buffer, the most recent bytes). Used to repaint a view that
|
||||
/// re-attaches to a live PTY so the terminal isn't blank.
|
||||
///
|
||||
/// # Errors
|
||||
/// [`PtyError`] if the handle is unknown.
|
||||
fn scrollback(&self, handle: &PtyHandle) -> Result<Vec<u8>, PtyError>;
|
||||
|
||||
/// Kills the PTY's process, returning its exit status.
|
||||
///
|
||||
/// # Errors
|
||||
|
||||
Reference in New Issue
Block a user