diff --git a/apps/desktop/src/main/lib/terminal-host/headless-emulator.ts b/apps/desktop/src/main/lib/terminal-host/headless-emulator.ts index 02522ad4f54..99ce2e15791 100644 --- a/apps/desktop/src/main/lib/terminal-host/headless-emulator.ts +++ b/apps/desktop/src/main/lib/terminal-host/headless-emulator.ts @@ -9,6 +9,7 @@ import { SerializeAddon } from "@xterm/addon-serialize"; import { Terminal } from "@xterm/headless"; +import { DEFAULT_TERMINAL_SCROLLBACK } from "shared/constants"; import { DEFAULT_MODES, type TerminalModes, @@ -75,7 +76,11 @@ export class HeadlessEmulator { private static readonly MAX_ESCAPE_BUFFER_SIZE = 1024; constructor(options: HeadlessEmulatorOptions = {}) { - const { cols = 80, rows = 24, scrollback = 10000 } = options; + const { + cols = 80, + rows = 24, + scrollback = DEFAULT_TERMINAL_SCROLLBACK, + } = options; this.terminal = new Terminal({ cols, @@ -224,7 +229,8 @@ export class HeadlessEmulator { */ getSnapshot(): TerminalSnapshot { const snapshotAnsi = this.serializeAddon.serialize({ - scrollback: this.terminal.options.scrollback ?? 10000, + scrollback: + this.terminal.options.scrollback ?? DEFAULT_TERMINAL_SCROLLBACK, }); const rehydrateSequences = this.generateRehydrateSequences(); @@ -323,6 +329,9 @@ export class HeadlessEmulator { dispose(): void { if (this.disposed) return; this.disposed = true; + // Clear scrollback buffer before disposing to release memory immediately + // rather than waiting for GC to collect the terminal instance. + this.terminal.clear(); this.terminal.dispose(); } diff --git a/apps/desktop/src/main/lib/terminal/session.ts b/apps/desktop/src/main/lib/terminal/session.ts index d1f5f23da61..2082893e2e6 100644 --- a/apps/desktop/src/main/lib/terminal/session.ts +++ b/apps/desktop/src/main/lib/terminal/session.ts @@ -2,6 +2,7 @@ import os from "node:os"; import { SerializeAddon } from "@xterm/addon-serialize"; import { Terminal as HeadlessTerminal } from "@xterm/headless"; import * as pty from "node-pty"; +import { DEFAULT_TERMINAL_SCROLLBACK } from "shared/constants"; import { getShellArgs } from "../agent-setup"; import { DataBatcher } from "../data-batcher"; import { @@ -14,7 +15,6 @@ import type { InternalCreateSessionParams, TerminalSession } from "./types"; const DEFAULT_COLS = 80; const DEFAULT_ROWS = 24; -const DEFAULT_SCROLLBACK = 10000; /** Max time to wait for agent hooks before running initial commands */ const AGENT_HOOKS_TIMEOUT_MS = 2000; const DEBUG_TERMINAL = process.env.SUPERSET_TERMINAL_DEBUG === "1"; @@ -24,7 +24,7 @@ export function createHeadlessTerminal(params: { rows: number; scrollback?: number; }): { headless: HeadlessTerminal; serializer: SerializeAddon } { - const { cols, rows, scrollback = DEFAULT_SCROLLBACK } = params; + const { cols, rows, scrollback = DEFAULT_TERMINAL_SCROLLBACK } = params; const headless = new HeadlessTerminal({ cols, diff --git a/apps/desktop/src/main/terminal-host/session.ts b/apps/desktop/src/main/terminal-host/session.ts index fd0bf30143f..3d5703270eb 100644 --- a/apps/desktop/src/main/terminal-host/session.ts +++ b/apps/desktop/src/main/terminal-host/session.ts @@ -11,6 +11,7 @@ import { type ChildProcess, spawn } from "node:child_process"; import type { Socket } from "node:net"; import * as path from "node:path"; +import { DEFAULT_TERMINAL_SCROLLBACK } from "shared/constants"; import { buildSafeEnv } from "../lib/terminal/env"; import { HeadlessEmulator } from "../lib/terminal-host/headless-emulator"; import type { @@ -136,7 +137,7 @@ export class Session { this.emulator = new HeadlessEmulator({ cols: options.cols, rows: options.rows, - scrollback: options.scrollbackLines ?? 10000, + scrollback: options.scrollbackLines ?? DEFAULT_TERMINAL_SCROLLBACK, }); // Set initial CWD diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/config.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/config.ts index a9fed0033fe..5ed1f7c772e 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/config.ts +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/config.ts @@ -1,4 +1,5 @@ import type { ITerminalOptions } from "@xterm/xterm"; +import { DEFAULT_TERMINAL_SCROLLBACK } from "shared/constants"; // Use user's theme export const TERMINAL_THEME: ITerminalOptions["theme"] = undefined; @@ -39,7 +40,7 @@ export const TERMINAL_OPTIONS: ITerminalOptions = { fontFamily: DEFAULT_TERMINAL_FONT_FAMILY, theme: TERMINAL_THEME, allowProposedApi: true, - scrollback: 10000, + scrollback: DEFAULT_TERMINAL_SCROLLBACK, // Allow Option+key to type special characters on international keyboards (e.g., Option+2 = @) macOptionIsMeta: false, cursorStyle: "block", diff --git a/apps/desktop/src/shared/constants.ts b/apps/desktop/src/shared/constants.ts index 5b87096daf7..6c6aa8485bf 100644 --- a/apps/desktop/src/shared/constants.ts +++ b/apps/desktop/src/shared/constants.ts @@ -42,6 +42,7 @@ export const DEFAULT_FILE_OPEN_MODE = "split-pane" as const; export const DEFAULT_AUTO_APPLY_DEFAULT_PRESET = true; export const DEFAULT_SHOW_PRESETS_BAR = false; export const DEFAULT_TELEMETRY_ENABLED = true; +export const DEFAULT_TERMINAL_SCROLLBACK = 5000; // External links (documentation, help resources, etc.) export const EXTERNAL_LINKS = {