fix(session): keep panel ids distinct from session ids so restore rebinds panes

面板 id 曾直接复用 pty 会话 id(`addPanel({ id: sessionId })`),而
`updateParameters` 只能换会话、不能改面板 id。于是恢复时面板 id 指向一个
已不存在的会话:分屏少一个、剩下的 pane 背后没有 pty,界面看起来是空白,
也无法输入。

- 新增 `panelId()`,面板 id 与会话 id 彻底分离,并用于所有建面板处
  (新建终端、SSH 连接、分屏复制)
- 新增 `breakIdCoincidence()`:旧快照仍带 `panelId === sessionId`,
  在 `fromJSON` 之前重写 grid/panels/views/activeView 中的 id,
  返回 old→new 映射
- `rebindSessionPanels` 用该映射回查快照,恢复每个 pane 自己的目录
  (否则新 id 查不到记录,cwd 会静默退回 home)

验证(重启恢复 E2E,legacy 快照 + 2 分屏):
  2/2 pane 挂载、2 个独立存活会话、输入可回环、
  cwd 分别回到 D:/AIGC/OpenTerminal 与 C:/Windows、回写快照无 id 冲突;
  restoreSession=false 时正确忽略快照只建 1 个终端。
This commit is contained in:
Bill committed 2026-09-14 11:29:56 +08:00
1 parent 8eacd34338
commit beeb32a76b
17 files changed
+917 -37

No files matched your search

+10 -1
View File
@@ -5,7 +5,8 @@ import type {
PtyCreateResult,
PtyDataEvent,
PtyExitEvent,
SessionOpenResult
SessionOpenResult,
SessionSnapshot
} from './ipc'
import type { AppSettings } from './settings'
import type { ReleaseNote, UpdateState } from './ipc'
@@ -113,4 +114,12 @@ export interface AppApi {
updateInstall(): Promise<void>
updateChangelog(): Promise<ReleaseNote[]>
onUpdateState(cb: (s: UpdateState) => void): () => void
// ---- session snapshot (restore last layout + per-pane cwd) ----
getSessionState(): Promise<SessionSnapshot | null>
saveSessionState(snapshot: SessionSnapshot): Promise<SessionSnapshot | null>
/** resolve a cd-style argument against the current cwd (platform-aware) */
resolveCwd(current: string | undefined, arg: string): Promise<string | null>
/** normalize a shell-reported cwd payload (OSC 7 / OSC 9;9) */
reportCwd(payload: string): Promise<string | null>
}
+38 -1
View File
@@ -98,13 +98,23 @@ export const Ipc = {
UPDATE_INSTALL: 'update:install',
UPDATE_CHANGELOG: 'update:changelog',
/** main -> renderer broadcast: UpdateState */
UPDATE_STATE: 'update:state'
UPDATE_STATE: 'update:state',
// ---- session snapshot (last layout + per-pane cwd) ----
SESSION_STATE_GET: 'session:stateGet',
SESSION_STATE_SET: 'session:stateSet',
/** resolve a cd-style argument against the current cwd, platform-aware */
CWD_RESOLVE: 'session:cwdResolve',
/** normalize a cwd reported by the shell (OSC 7 / OSC 9;9) */
CWD_REPORT: 'session:cwdReport'
} as const
export interface PtyCreateOptions {
cwd?: string
/** executable; omit for platform default shell */
shell?: string
/** extra argv for the shell (shell-integration adapters use this) */
shellArgs?: string[]
env?: Record<string, string>
}
@@ -114,6 +124,33 @@ export interface PtyCreateResult {
cwd: string
}
/** One dockview panel as remembered between runs. */
export interface SessionPanelState {
panelId: string
title: string
kind: 'local' | 'ssh'
/** last known working directory (local panels; also recorded for ssh panes
* so a reconnected pane starts somewhere sensible) */
cwd?: string
/** ssh only: bookmark to offer a reconnect for */
connectionId?: string
hostLabel?: string
}
/**
* The automatic "pick up where you left off" snapshot: the dockview layouts
* plus a per-panel record. Distinct from the user-managed layout templates.
*/
export interface SessionSnapshot {
version: 1
savedAt: number
mode: 'terminal' | 'ssh'
layouts: { terminal?: unknown; ssh?: unknown }
panels: SessionPanelState[]
/** newest cwd among local panes — seeds brand-new terminals */
lastLocalCwd?: string
}
export interface PtyDataEvent {
id: string
data: string
+12 -1
View File
@@ -51,6 +51,17 @@ export interface SystemSettings {
closeAction?: 'ask' | 'tray' | 'exit'
/** check for updates shortly after startup (manual check always available) */
autoCheckUpdate?: boolean
/**
* Restore the previous layout and each pane's working directory on launch
* (see the session snapshot). On by default — that is the point of it.
*/
restoreSession?: boolean
/**
* Let the shell announce its cwd over OSC 7 (shell integration). Exact, but
* it wraps the user's prompt, so it is opt-in; without it the app falls back
* to parsing typed `cd` commands.
*/
shellIntegration?: boolean
}
export interface AppSettings {
@@ -107,5 +118,5 @@ export const DEFAULT_SETTINGS: AppSettings = {
},
customThemes: [],
highlightRules: DEFAULT_HIGHLIGHT_RULES,
system: { launchAtLogin: false, preventSleep: false, globalShowHide: '', closeAction: 'tray', autoCheckUpdate: true }
system: { launchAtLogin: false, preventSleep: false, globalShowHide: '', closeAction: 'tray', autoCheckUpdate: true, restoreSession: true, shellIntegration: false }
}