Files
OpenTerminal/src/shared/ipc.ts
T
Bill beeb32a76b 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 个终端。
2026-09-14 11:29:56 +08:00

238 lines
6.6 KiB
TypeScript

/**
* IPC channel names + payload contracts shared between main / preload / renderer.
* DO NOT rename channels or change payload shapes without updating all consumers.
*/
export const Ipc = {
// ---- PTY session lifecycle ----
// NOTE: since M2 these channels are session-generic: `id` addresses both
// local pty sessions and ssh sessions; data plane is identical.
PTY_CREATE: 'pty:create',
PTY_WRITE: 'pty:write',
PTY_RESIZE: 'pty:resize',
PTY_KILL: 'pty:kill',
/** main -> renderer broadcast: { id, data } */
PTY_DATA: 'pty:data',
/** main -> renderer broadcast: { id, exitCode } */
PTY_EXIT: 'pty:exit',
// ---- settings ----
SETTINGS_GET: 'settings:get',
SETTINGS_SET: 'settings:set',
/** main -> renderer broadcast after any save */
SETTINGS_CHANGED: 'settings:changed',
// ---- fonts ----
FONTS_LIST: 'fonts:list',
// ---- layouts ----
LAYOUTS_LIST: 'layouts:list',
LAYOUTS_GET: 'layouts:get',
LAYOUTS_SAVE: 'layouts:save',
LAYOUTS_DELETE: 'layouts:delete',
// ---- sessions (openSession unifies local pty and ssh) ----
SESSION_OPEN: 'session:open',
/** main keeps a short replay buffer per session so late subscribers catch up */
SESSION_REPLAY: 'session:replay',
// ---- ssh connections (bookmarks) ----
CONNECTIONS_LIST: 'connections:list',
CONNECTIONS_SAVE: 'connections:save',
CONNECTIONS_DELETE: 'connections:delete',
// ---- host key verification ----
/** main -> renderer: needs user decision before connect proceeds */
HOSTKEY_PROMPT: 'hostkey:prompt',
HOSTKEY_RESPOND: 'hostkey:respond',
// ---- sysinfo (M3) ----
SYSINFO_START: 'sysinfo:start',
SYSINFO_STOP: 'sysinfo:stop',
/** main -> renderer broadcast: { id, meta } (once per session) */
SYSINFO_META: 'sysinfo:meta',
/** main -> renderer broadcast: { id, sample } (poll interval) */
SYSINFO_SAMPLE: 'sysinfo:sample',
// ---- sftp (M4) ----
SFTP_LIST: 'sftp:list',
SFTP_MKDIR: 'sftp:mkdir',
SFTP_RENAME: 'sftp:rename',
SFTP_DELETE: 'sftp:delete',
SFTP_CHMOD: 'sftp:chmod',
SFTP_CHOWN: 'sftp:chown',
SFTP_UPLOAD: 'sftp:upload',
SFTP_DOWNLOAD: 'sftp:download',
TRANSFER_CANCEL: 'transfer:cancel',
/** main -> renderer broadcast: TransferProgressEvent */
TRANSFER_PROGRESS: 'transfer:progress',
DIALOG_PICK_FILES: 'dialog:pickFiles',
DIALOG_PICK_DIR: 'dialog:pickDirectory',
// ---- commands + logs (M5) ----
COMMANDS_RECORD: 'commands:record',
COMMANDS_HISTORY_LIST: 'commands:historyList',
COMMANDS_HISTORY_CLEAR: 'commands:historyClear',
COMMANDS_LIBRARY_LIST: 'commands:libraryList',
COMMANDS_LIBRARY_SAVE: 'commands:librarySave',
COMMANDS_LIBRARY_DELETE: 'commands:libraryDelete',
LOG_START: 'log:start',
LOG_STOP: 'log:stop',
LOG_LIST: 'log:list',
LOG_OPEN_DIR: 'log:openDir',
// ---- zmodem (M6: main-process engine, ssh sessions only) ----
/** main -> renderer: remote ran rz (mode=send) or sz (mode=receive); renderer must ask the user */
ZMODEM_OFFER: 'zmodem:offer',
/** renderer -> main: user answered the zmodem offer */
ZMODEM_RESPOND: 'zmodem:respond',
/** main -> renderer: zmodem session finished (ok, cancelled or failed) */
ZMODEM_DONE: 'zmodem:done',
// ---- misc ----
APP_INFO: 'app:info',
// ---- updater (check/download/install + state broadcast) ----
UPDATE_CHECK: 'update:check',
UPDATE_DOWNLOAD: 'update:download',
UPDATE_INSTALL: 'update:install',
UPDATE_CHANGELOG: 'update:changelog',
/** main -> renderer broadcast: UpdateState */
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>
}
export interface PtyCreateResult {
id: string
shell: string
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
}
export interface PtyExitEvent {
id: string
exitCode: number
}
export interface AppInfo {
platform: NodeJS.Platform | string
appVersion: string
homeDir: string
}
// ---- updater ----
export type UpdateStatus =
| 'idle'
| 'checking'
| 'available'
| 'latest'
| 'downloading'
| 'downloaded'
| 'error'
/** dev builds have no update channel */
| 'dev'
export interface UpdateState {
status: UpdateStatus
/** running app version */
currentVersion: string
/** newer version when available/downloading/downloaded */
version?: string
/** 0-100 while downloading */
percent?: number
error?: string
/** which feed served the last check: domestic Gitea first, GitHub fallback */
feed?: 'gitea' | 'github'
}
export interface ReleaseNote {
version: string
date: string
body: string
}
export interface LayoutMeta {
id: string
name: string
createdAt: number
}
export interface SessionOpenResult {
id: string
}
export interface HostKeyResponse {
promptId: string
action: 'accept' | 'reject'
}
// ---- zmodem (M6) ----
/** 'send' = remote ran `rz`, we upload files; 'receive' = remote ran `sz`, we download */
export interface ZmodemOfferEvent {
id: string
mode: 'send' | 'receive'
}
export interface ZmodemResponse {
id: string
cancelled: boolean
/** local files to upload (mode=send) */
paths?: string[]
/** local directory to save into (mode=receive) */
dir?: string
}
export interface ZmodemDoneEvent {
id: string
ok: boolean
/** human-readable failure reason when ok=false */
message?: string
}