feat(terminal): refactor runtime and add mobile workspace (#2280)

Replace the legacy terminal flow with a shared authenticated WebSocket
runtime used across web, desktop, relay, and mobile surfaces.

- introduce the v3 terminal protocol with scoped attachments, snapshots,
  ordered output, bounded replay history, reconnects, and explicit lifecycle
- harden PTY creation, restart, resize, close, force-kill, idle cleanup,
  shell selection, login mode, environment sanitization, and appearance sync
- add runtime-aware terminal APIs with relay authentication and Electron parity
- add a fullscreen mobile terminal workspace with touch scrolling,
  long-press selection, safe-area controls, quick keys, and Ctrl/Alt input
- add terminal selection attachments, preview detection, project actions,
  shell settings, and localized UI
- harden Ghostty rendering, resize recovery, Unicode handling, block
  characters, line height, and stale-row behavior
- remove the obsolete terminal SSE path and update reverse-proxy guidance
- expand terminal runtime, transport, input, selection, and store coverage
- avoid duplicate web builds when preparing mobile assets in root CI builds
This commit is contained in:
Bohdan Triapitsyn
2026-07-17 13:17:21 +03:00
committed by GitHub
parent f5b4a267c0
commit d4a8c4d2e1
103 changed files with 4085 additions and 4496 deletions
+31 -30
View File
@@ -18,35 +18,27 @@ interface Subscription {
close: () => void;
}
interface RetryPolicy {
maxRetries: number;
initialDelayMs: number;
maxDelayMs: number;
}
interface TerminalTransportCapability {
preferred?: 'ws' | 'http' | 'sse';
transports?: Array<'ws' | 'http' | 'sse'>;
ws?: {
path: string;
v?: number;
enc?: string;
};
}
export interface TerminalSession {
sessionId: string;
cols: number;
rows: number;
capabilities?: {
input?: TerminalTransportCapability;
stream?: TerminalTransportCapability;
};
status: 'running' | 'exited' | 'error';
}
export type TerminalShell = 'auto' | 'bash' | 'zsh' | 'sh' | 'fish' | 'pwsh' | 'powershell' | 'cmd' | 'dash' | 'ksh' | 'nu';
export interface TerminalShellOption {
id: TerminalShell;
name: string;
supportsLogin: boolean;
}
export interface TerminalStreamEvent {
type: 'connected' | 'data' | 'exit' | 'reconnecting';
type: 'snapshot' | 'data' | 'exit' | 'reconnecting';
sequence?: number;
data?: string;
replayData?: string;
status?: 'running' | 'exited' | 'error';
exitCode?: number;
signal?: number | null;
attempt?: number;
@@ -56,15 +48,20 @@ export interface TerminalStreamEvent {
ptyBackend?: string;
}
export interface CreateTerminalOptions {
cwd: string;
cols?: number;
rows?: number;
export interface TerminalError extends Error {
code?: string;
}
export interface TerminalStreamOptions {
retry?: Partial<RetryPolicy>;
connectionTimeoutMs?: number;
export interface CreateTerminalOptions {
cwd: string;
sessionId?: string;
cols?: number;
rows?: number;
themeMode?: 'light' | 'dark';
terminalBackground?: string;
terminalForeground?: string;
shell?: TerminalShell;
loginShell?: boolean;
}
export interface ResizeTerminalPayload {
@@ -75,7 +72,7 @@ export interface ResizeTerminalPayload {
export interface TerminalHandlers {
onEvent: (event: TerminalStreamEvent) => void;
onError?: (error: Error, fatal?: boolean) => void;
onError?: (error: TerminalError, fatal?: boolean) => void;
}
export interface ForceKillOptions {
@@ -84,10 +81,12 @@ export interface ForceKillOptions {
}
export interface TerminalAPI {
listShells?(): Promise<TerminalShellOption[]>;
createSession(options: CreateTerminalOptions): Promise<TerminalSession>;
connect(sessionId: string, handlers: TerminalHandlers, options?: TerminalStreamOptions): Subscription;
connect(sessionId: string, handlers: TerminalHandlers): Subscription;
sendInput(sessionId: string, input: string): Promise<void>;
resize(payload: ResizeTerminalPayload): Promise<void>;
updateAppearance?(sessionId: string, appearance: Pick<CreateTerminalOptions, 'themeMode' | 'terminalBackground' | 'terminalForeground'>): Promise<void>;
close(sessionId: string): Promise<void>;
restartSession?(currentSessionId: string, options: CreateTerminalOptions): Promise<TerminalSession>;
forceKill?(options: ForceKillOptions): Promise<void>;
@@ -657,6 +656,8 @@ export interface SettingsPayload {
showSplitAssistantMessageActions?: boolean;
fontSize?: number;
terminalFontSize?: number;
terminalShell?: TerminalShell;
terminalLoginShells?: TerminalShell[];
editorFontSize?: number;
uiFont?: string;
monoFont?: string;