From bf9cc201244946496b4d950046bf7edcc4892994 Mon Sep 17 00:00:00 2001 From: Ogulcan Celik Date: Thu, 28 May 2026 04:11:56 +0300 Subject: fix: preserve live handoff terminal state --- src/persist/restore.rs | 79 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 54 insertions(+), 25 deletions(-) (limited to 'src/persist/restore.rs') diff --git a/src/persist/restore.rs b/src/persist/restore.rs index 9c65d40d..26949cdd 100644 --- a/src/persist/restore.rs +++ b/src/persist/restore.rs @@ -22,17 +22,6 @@ use super::{ WorkspaceSnapshot, }; -#[cfg(unix)] -pub struct ImportedPaneRuntime { - pub master_fd: std::os::fd::RawFd, - pub child_pid: u32, - pub rows: u16, - pub cols: u16, - pub cell_width_px: u32, - pub cell_height_px: u32, - pub initial_history_ansi: Option, -} - struct AgentRestoreState<'a> { enabled: bool, resumed_sessions: &'a mut HashSet, @@ -105,7 +94,7 @@ pub fn restore_handoff( snapshot: &SessionSnapshot, scrollback_limit_bytes: usize, default_shell: &str, - imports: &mut HashMap, + imports: &mut HashMap, events: mpsc::Sender, render_notify: Arc, render_dirty: Arc, @@ -125,6 +114,44 @@ pub fn restore_handoff( ) } +#[cfg(unix)] +pub fn handoff_pane_aliases( + snapshot: &SessionSnapshot, + workspaces: &[Workspace], +) -> HashMap { + let mut aliases = HashMap::new(); + for (ws_snap, workspace) in snapshot.workspaces.iter().zip(workspaces) { + for (tab_snap, tab) in ws_snap.tabs.iter().zip(&workspace.tabs) { + let old_ids = collect_snapshot_pane_ids(&tab_snap.layout); + let new_ids = tab.layout.pane_ids(); + for (old_id, new_id) in old_ids.into_iter().zip(new_ids) { + if old_id != new_id.raw() { + aliases.insert(old_id, new_id); + } + } + } + } + aliases +} + +#[cfg(unix)] +fn collect_snapshot_pane_ids(node: &LayoutSnapshot) -> Vec { + let mut ids = Vec::new(); + collect_snapshot_ids_inner(node, &mut ids); + ids +} + +#[cfg(unix)] +fn collect_snapshot_ids_inner(node: &LayoutSnapshot, ids: &mut Vec) { + match node { + LayoutSnapshot::Pane(id) => ids.push(*id), + LayoutSnapshot::Split { first, second, .. } => { + collect_snapshot_ids_inner(first, ids); + collect_snapshot_ids_inner(second, ids); + } + } +} + #[cfg(unix)] fn restore_with_imports_strict( snapshot: &SessionSnapshot, @@ -134,7 +161,7 @@ fn restore_with_imports_strict( scrollback_limit_bytes: usize, default_shell: &str, resume_agents_on_restore: bool, - imported_panes: &mut HashMap, + imported_panes: &mut HashMap, events: mpsc::Sender, render_notify: Arc, render_dirty: Arc, @@ -174,7 +201,7 @@ fn restore_with_imports( scrollback_limit_bytes: usize, default_shell: &str, resume_agents_on_restore: bool, - imported_panes: &mut HashMap, + imported_panes: &mut HashMap, events: mpsc::Sender, render_notify: Arc, render_dirty: Arc, @@ -203,7 +230,7 @@ fn restore_with_imports_and_failures( scrollback_limit_bytes: usize, default_shell: &str, resume_agents_on_restore: bool, - imported_panes: &mut HashMap, + imported_panes: &mut HashMap, events: mpsc::Sender, render_notify: Arc, render_dirty: Arc, @@ -250,7 +277,7 @@ fn restore_workspace( cols: u16, runtime_context: &RestoreRuntimeContext<'_>, resumed_agent_sessions: &mut HashSet, - imported_panes: &mut HashMap, + imported_panes: &mut HashMap, ) -> RestoreFailures> { let mut tabs = Vec::new(); let mut terminals = Vec::new(); @@ -331,7 +358,7 @@ fn restore_tab( cols: u16, runtime_context: &RestoreRuntimeContext<'_>, resumed_agent_sessions: &mut HashSet, - imported_panes: &mut HashMap, + imported_panes: &mut HashMap, ) -> RestoreFailures> { let (node, id_map) = restore_node_remapped(&snap.layout); let reverse_id_map: HashMap = id_map @@ -370,6 +397,7 @@ fn restore_tab( let saved_label = saved_pane.and_then(|p| p.label.clone()); let saved_agent_name = saved_pane.and_then(|p| p.agent_name.clone()); + let saved_launch_argv = saved_pane.and_then(|p| p.launch_argv.clone()); let saved_agent_session = saved_pane.and_then(|p| p.agent_session.as_ref()); let saved_history = old_id.and_then(|old_id| history.and_then(|history| history.panes.get(old_id))); @@ -390,15 +418,9 @@ fn restore_tab( let was_imported = imported_runtime.is_some(); let runtime_result = if let Some(imported) = imported_runtime { TerminalRuntime::from_handoff_fd( - crate::pane::PaneRuntimeImport { - pane_id: *id, + crate::handoff_runtime::ImportedHandoffRuntime { master_fd: imported.master_fd, - child_pid: imported.child_pid, - rows: imported.rows, - cols: imported.cols, - cell_width_px: imported.cell_width_px, - cell_height_px: imported.cell_height_px, - initial_history_ansi: imported.initial_history_ansi, + state: imported.state.with_pane_id(*id), }, runtime_context.scrollback_limit_bytes, crate::terminal_theme::TerminalTheme::default(), @@ -443,6 +465,11 @@ fn restore_tab( Ok(runtime) => { let terminal_id = TerminalId::alloc(); let mut terminal = TerminalState::new(terminal_id.clone(), cwd.clone()); + if was_imported { + if let Some(argv) = saved_launch_argv { + terminal = terminal.with_launch_argv(argv).with_respawn_shell_on_exit(); + } + } if let Some(label) = saved_label { terminal.set_manual_label(label); } @@ -974,6 +1001,7 @@ mod tests { kind: crate::agent_resume::AgentSessionRefKind::Id, value: "opencode-session".into(), }), + launch_argv: None, }, )]), zoomed: false, @@ -1100,6 +1128,7 @@ mod tests { label: None, agent_name: None, agent_session: None, + launch_argv: None, }, ); let history = SessionHistorySnapshot { -- cgit v1.2.3