Files
NetMesh/domain/terminalHibernate.ts
zhaolei 3c72efcb7f
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
[Init] Initial commit - NetMesh terminal manager
2026-09-13 18:24:01 +08:00

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;
};