Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
173 lines
6.4 KiB
TypeScript
173 lines
6.4 KiB
TypeScript
import type { TerminalSession, TerminalSettings } from "./models/terminal";
|
|
|
|
/** Compile-time kill switch for terminal hibernate (Settings > Terminal still controls user default). */
|
|
export const TERMINAL_HIBERNATE_ENABLED = true;
|
|
|
|
/** Default delay before hibernating a hidden tab (seconds). */
|
|
export const TERMINAL_HIBERNATE_DELAY_SEC_DEFAULT = 5;
|
|
|
|
export const TERMINAL_HIBERNATE_DELAY_SEC_MIN = 5;
|
|
|
|
export const TERMINAL_HIBERNATE_DELAY_SEC_MAX = 600;
|
|
|
|
/** Default delay in milliseconds (legacy constant for tests). */
|
|
export const TERMINAL_HIBERNATE_DELAY_MS = TERMINAL_HIBERNATE_DELAY_SEC_DEFAULT * 1000;
|
|
|
|
export function normalizeHibernateHiddenTabsDelaySec(value: unknown): number {
|
|
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
return TERMINAL_HIBERNATE_DELAY_SEC_DEFAULT;
|
|
}
|
|
return Math.min(
|
|
TERMINAL_HIBERNATE_DELAY_SEC_MAX,
|
|
Math.max(TERMINAL_HIBERNATE_DELAY_SEC_MIN, Math.round(value)),
|
|
);
|
|
}
|
|
|
|
export function resolveTerminalHibernateDelayMs(
|
|
settings?: Pick<TerminalSettings, "hibernateHiddenTabsDelaySec"> | null,
|
|
): number {
|
|
return normalizeHibernateHiddenTabsDelaySec(settings?.hibernateHiddenTabsDelaySec) * 1000;
|
|
}
|
|
|
|
export function resolveTerminalHibernateEnabled(
|
|
settings?: Pick<TerminalSettings, "hibernateHiddenTabs"> | null,
|
|
): boolean {
|
|
if (!TERMINAL_HIBERNATE_ENABLED) return false;
|
|
return settings?.hibernateHiddenTabs === true;
|
|
}
|
|
|
|
export function resolveTerminalHibernateEnabledForProtocol(
|
|
settings: Pick<TerminalSettings, "hibernateHiddenTabs"> | null | undefined,
|
|
protocol: string | null | undefined,
|
|
): boolean {
|
|
return protocol !== "local" && resolveTerminalHibernateEnabled(settings);
|
|
}
|
|
|
|
/** Hidden connected and ended remote terminals can both release their xterm runtime. */
|
|
export function canHibernateTerminalRuntimeStatus(
|
|
status: TerminalSession["status"],
|
|
): boolean {
|
|
return status === "connected" || status === "disconnected";
|
|
}
|
|
|
|
export function canHibernateTerminalRuntimeSession(
|
|
status: TerminalSession["status"],
|
|
backendSessionId: string | null | undefined,
|
|
): boolean {
|
|
if (!canHibernateTerminalRuntimeStatus(status)) return false;
|
|
return status === "disconnected" || Boolean(backendSessionId);
|
|
}
|
|
|
|
export function shouldKeepTerminalBackgroundWorkActive(
|
|
settings: Pick<TerminalSettings, "hibernateHiddenTabs"> | null | undefined,
|
|
protocol: string | null | undefined,
|
|
isVisible: boolean,
|
|
): boolean {
|
|
return isVisible || !resolveTerminalHibernateEnabledForProtocol(settings, protocol);
|
|
}
|
|
|
|
/** Block hibernate while a file transfer or drag-drop session is in progress. */
|
|
export function isTerminalFileTransferActive(options: {
|
|
zmodemActive: boolean;
|
|
ymodemInProgress: boolean;
|
|
isDraggingOver: boolean;
|
|
}): boolean {
|
|
return options.zmodemActive || options.ymodemInProgress || options.isDraggingOver;
|
|
}
|
|
|
|
/** Lines kept when snapshotting xterm scrollback before hibernate. */
|
|
export const TERMINAL_HIBERNATE_SNAPSHOT_MAX_LINES = 3_000;
|
|
|
|
/** Default chunk size for replaying hibernate snapshots without blocking the main thread. */
|
|
export const TERMINAL_HIBERNATE_REPLAY_CHUNK_BYTES_DEFAULT = 16 * 1024;
|
|
|
|
export const TERMINAL_HIBERNATE_REPLAY_CHUNK_BYTES_MIN = 4 * 1024;
|
|
|
|
export const TERMINAL_HIBERNATE_REPLAY_CHUNK_BYTES_MAX = 64 * 1024;
|
|
|
|
/** Hidden tabs whose xterm runtime is kept alive (WebGL suspended) before full hibernate. */
|
|
export const TERMINAL_HIBERNATE_KEEP_RENDERER_COUNT_DEFAULT = 2;
|
|
|
|
export const TERMINAL_HIBERNATE_KEEP_RENDERER_COUNT_MIN = 0;
|
|
|
|
export const TERMINAL_HIBERNATE_KEEP_RENDERER_COUNT_MAX = 12;
|
|
|
|
/** Max chars buffered in renderer while xterm is hibernated (post-snapshot output). */
|
|
export const TERMINAL_HIBERNATE_BUFFER_MAX_CHARS = 512 * 1024;
|
|
|
|
export function capHibernateBuffer(buffer: string, maxChars = TERMINAL_HIBERNATE_BUFFER_MAX_CHARS): string {
|
|
if (buffer.length <= maxChars) return buffer;
|
|
return buffer.slice(buffer.length - maxChars);
|
|
}
|
|
|
|
export function capHibernateBufferByLines(text: string, maxLines: number): string {
|
|
if (maxLines <= 0 || text.length === 0) return text;
|
|
const lines = text.split("\n");
|
|
if (lines.length <= maxLines) return text;
|
|
return lines.slice(lines.length - maxLines).join("\n");
|
|
}
|
|
|
|
export function normalizeHibernateReplayChunkBytes(value: unknown): number {
|
|
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
return TERMINAL_HIBERNATE_REPLAY_CHUNK_BYTES_DEFAULT;
|
|
}
|
|
return Math.min(
|
|
TERMINAL_HIBERNATE_REPLAY_CHUNK_BYTES_MAX,
|
|
Math.max(TERMINAL_HIBERNATE_REPLAY_CHUNK_BYTES_MIN, Math.round(value)),
|
|
);
|
|
}
|
|
|
|
export function normalizeHibernateKeepRendererCount(value: unknown): number {
|
|
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
return TERMINAL_HIBERNATE_KEEP_RENDERER_COUNT_DEFAULT;
|
|
}
|
|
return Math.min(
|
|
TERMINAL_HIBERNATE_KEEP_RENDERER_COUNT_MAX,
|
|
Math.max(TERMINAL_HIBERNATE_KEEP_RENDERER_COUNT_MIN, Math.round(value)),
|
|
);
|
|
}
|
|
|
|
export function resolveTerminalHibernateReplayChunkBytes(
|
|
settings?: Pick<TerminalSettings, "hibernateReplayChunkBytes"> | null,
|
|
): number {
|
|
return normalizeHibernateReplayChunkBytes(settings?.hibernateReplayChunkBytes);
|
|
}
|
|
|
|
export function resolveHibernateKeepRendererCount(
|
|
settings?: Pick<TerminalSettings, "hibernateKeepRendererCount"> | null,
|
|
): number {
|
|
return normalizeHibernateKeepRendererCount(settings?.hibernateKeepRendererCount);
|
|
}
|
|
|
|
export function resolveHibernateSkipAltScreen(
|
|
settings?: Pick<TerminalSettings, "hibernateSkipAltScreen"> | null,
|
|
): boolean {
|
|
return settings?.hibernateSkipAltScreen !== false;
|
|
}
|
|
|
|
export function resolveHibernatePreferWasmSerialize(
|
|
settings?: Pick<TerminalSettings, "hibernatePreferWasmSerialize"> | null,
|
|
): boolean {
|
|
return settings?.hibernatePreferWasmSerialize === true;
|
|
}
|
|
|
|
export function shouldSkipHibernateForAltScreen(
|
|
alternateScreen: boolean,
|
|
settings?: Pick<TerminalSettings, "hibernateSkipAltScreen"> | null,
|
|
): boolean {
|
|
return alternateScreen && resolveHibernateSkipAltScreen(settings);
|
|
}
|
|
|
|
/** Snapshot + buffered output to replay when recreating xterm after hibernate. */
|
|
export type TerminalHibernateWakePayload = {
|
|
/** Full serialized terminal state -- preferred wake history (coherent ANSI). */
|
|
snapshot: string;
|
|
/** Visible viewport rows; used with scrollback only when snapshot is empty. */
|
|
viewportSnapshot?: string;
|
|
/** Older scrollback rows; used with viewport only when snapshot is empty. */
|
|
scrollbackSnapshot?: string;
|
|
pendingBuffer: string;
|
|
/** True when the pane was hibernated while a full-screen app owned the alt buffer. */
|
|
alternateScreen: boolean;
|
|
};
|