import path from "node:path"; import { DEFAULT_TERMINAL_ID, type TerminalEvent, type TerminalSessionSnapshot, type TerminalSessionStatus,} from "@t3tools/contracts";import { makeKeyedCoalescingWorker } from "@t3tools/shared/KeyedCoalescingWorker";import { Effect, Encoding, Equal, Exit, Fiber, FileSystem, Layer, Option, Schema, Scope, Semaphore, SynchronizedRef,} from "effect"; import { ServerConfig } from "../../config.ts";import { increment, terminalRestartsTotal, terminalSessionsTotal,} from "../../observability/Metrics.ts";import { runProcess } from "../../processRunner.ts";import { TerminalCwdError, TerminalHistoryError, TerminalManager, TerminalNotRunningError, TerminalSessionLookupError, type TerminalManagerShape,} from "../Services/Manager.ts";import { PtyAdapter, PtySpawnError, type PtyAdapterShape, type PtyExitEvent, type PtyProcess,} from "../Services/PTY.ts"; const DEFAULT_HISTORY_LINE_LIMIT = 5_000;const DEFAULT_PERSIST_DEBOUNCE_MS = 40;const DEFAULT_SUBPROCESS_POLL_INTERVAL_MS = 1_000;const DEFAULT_PROCESS_KILL_GRACE_MS = 1_000;const DEFAULT_MAX_RETAINED_INACTIVE_SESSIONS = 128;const DEFAULT_OPEN_COLS = 120;const DEFAULT_OPEN_ROWS = 30;const TERMINAL_ENV_BLOCKLIST = new Set(["PORT", "ELECTRON_RENDERER_PORT", "ELECTRON_RUN_AS_NODE"]); class TerminalSubprocessCheckError extends Schema.TaggedErrorClass<TerminalSubprocessCheckError>()( "TerminalSubprocessCheckError", { message: Schema.String, cause: Schema.optional(Schema.Defect), terminalPid: Schema.Number, command: Schema.Literals(["powershell", "pgrep", "ps"]), },) {} class TerminalProcessSignalError extends Schema.TaggedErrorClass<TerminalProcessSignalError>()( "TerminalProcessSignalError", { message: Schema.String, cause: Schema.optional(Schema.Defect), signal: Schema.Literals(["SIGTERM", "SIGKILL"]), },) {} interface TerminalSubprocessChecker { (terminalPid: number): Effect.Effect<boolean, TerminalSubprocessCheckError>;} interface ShellCandidate { shell: string; args?: string[];} interface TerminalStartInput { threadId: string; terminalId: string; cwd: string; worktreePath?: string | null; cols: number; rows: number; env?: Record<string, string>;} interface TerminalSessionState { threadId: string; terminalId: string; cwd: string; worktreePath: string | null; status: TerminalSessionStatus; pid: number | null; history: string; pendingHistoryControlSequence: string; pendingProcessEvents: Array<PendingProcessEvent>; pendingProcessEventIndex: number; processEventDrainRunning: boolean; exitCode: number | null; exitSignal: number | null; updatedAt: string; cols: number; rows: number; process: PtyProcess | null; unsubscribeData: (() => void) | null; unsubscribeExit: (() => void) | null; hasRunningSubprocess: boolean; runtimeEnv: Record<string, string> | null;} interface PersistHistoryRequest { history: string; immediate: boolean;} type PendingProcessEvent = { type: "output"; data: string } | { type: "exit"; event: PtyExitEvent }; type DrainProcessEventAction = | { type: "idle" } | { type: "output"; threadId: string; terminalId: string; history: string | null; data: string; } | { type: "exit"; process: PtyProcess | null; threadId: string; terminalId: string; exitCode: number | null; exitSignal: number | null; }; interface TerminalManagerState { sessions: Map<string, TerminalSessionState>; killFibers: Map<PtyProcess, Fiber.Fiber<void, never>>;} function snapshot(session: TerminalSessionState): TerminalSessionSnapshot { return { threadId: session.threadId, terminalId: session.terminalId, cwd: session.cwd, worktreePath: session.worktreePath, status: session.status, pid: session.pid, history: session.history, exitCode: session.exitCode, exitSignal: session.exitSignal, updatedAt: session.updatedAt, };} function cleanupProcessHandles(session: TerminalSessionState): void { session.unsubscribeData?.(); session.unsubscribeData = null; session.unsubscribeExit?.(); session.unsubscribeExit = null;} function enqueueProcessEvent( session: TerminalSessionState, expectedPid: number, event: PendingProcessEvent,): boolean { if (!session.process || session.status !== "running" || session.pid !== expectedPid) { return false; } session.pendingProcessEvents.push(event); if (session.processEventDrainRunning) { return false; } session.processEventDrainRunning = true; return true;} function defaultShellResolver( platform: NodeJS.Platform = process.platform, env: NodeJS.ProcessEnv = process.env,): string { if (platform === "win32") { return "pwsh.exe"; } return env.SHELL ?? "bash";} function normalizeShellCommand( value: string | undefined, platform: NodeJS.Platform = process.platform,): string | null { if (!value) return null; const trimmed = value.trim(); if (trimmed.length === 0) return null; if (platform === "win32") { return trimmed; } const firstToken = trimmed.split(/\s+/g)[0]?.trim(); if (!firstToken) return null; return firstToken.replace(/^['"]|['"]$/g, "");} function shellCandidateFromCommand( command: string | null, platform: NodeJS.Platform = process.platform,): ShellCandidate | null { if (!command || command.length === 0) return null; const shellName = platform === "win32" ? path.win32.basename(command).toLowerCase() : path.basename(command).toLowerCase(); if (platform === "win32" && (shellName === "pwsh.exe" || shellName === "powershell.exe")) { return { shell: command, args: ["-NoLogo"] }; } if (platform !== "win32" && shellName === "zsh") { return { shell: command, args: ["-o", "nopromptsp"] }; } return { shell: command };} function windowsSystemRoot(env: NodeJS.ProcessEnv): string { return env.SystemRoot?.trim() || env.windir?.trim() || "C:\\Windows";} function windowsPowerShellPath(env: NodeJS.ProcessEnv): string { return path.win32.join( windowsSystemRoot(env), "System32", "WindowsPowerShell", "v1.0", "powershell.exe", );} function windowsCmdPath(env: NodeJS.ProcessEnv): string { return path.win32.join(windowsSystemRoot(env), "System32", "cmd.exe");} function formatShellCandidate(candidate: ShellCandidate): string { if (!candidate.args || candidate.args.length === 0) return candidate.shell; return `${candidate.shell} ${candidate.args.join(" ")}`;} function uniqueShellCandidates(candidates: Array<ShellCandidate | null>): ShellCandidate[] { const seen = new Set<string>(); const ordered: ShellCandidate[] = []; for (const candidate of candidates) { if (!candidate) continue; const key = formatShellCandidate(candidate); if (seen.has(key)) continue; seen.add(key); ordered.push(candidate); } return ordered;} function resolveShellCandidates( shellResolver: () => string, platform: NodeJS.Platform = process.platform, env: NodeJS.ProcessEnv = process.env,): ShellCandidate[] { const requested = shellCandidateFromCommand( normalizeShellCommand(shellResolver(), platform), platform, ); if (platform === "win32") { return uniqueShellCandidates([ requested, shellCandidateFromCommand("pwsh.exe", platform), shellCandidateFromCommand(windowsPowerShellPath(env), platform), shellCandidateFromCommand("powershell.exe", platform), shellCandidateFromCommand(env.ComSpec ?? null, platform), shellCandidateFromCommand(windowsCmdPath(env), platform), shellCandidateFromCommand("cmd.exe", platform), ]); } return uniqueShellCandidates([ requested, shellCandidateFromCommand(normalizeShellCommand(env.SHELL, platform), platform), shellCandidateFromCommand("/bin/zsh", platform), shellCandidateFromCommand("/bin/bash", platform), shellCandidateFromCommand("/bin/sh", platform), shellCandidateFromCommand("zsh", platform), shellCandidateFromCommand("bash", platform), shellCandidateFromCommand("sh", platform), ]);} function isRetryableShellSpawnError(error: PtySpawnError): boolean { const queue: unknown[] = [error]; const seen = new Set<unknown>(); const messages: string[] = []; while (queue.length > 0) { const current = queue.shift(); if (!current || seen.has(current)) { continue; } seen.add(current); if (typeof current === "string") { messages.push(current); continue; } if (current instanceof Error) { messages.push(current.message); if (current.cause) { queue.push(current.cause); } continue; } if (typeof current === "object") { const value = current as { message?: unknown; cause?: unknown }; if (typeof value.message === "string") { messages.push(value.message); } if (value.cause) { queue.push(value.cause); } } } const message = messages.join(" ").toLowerCase(); return ( message.includes("posix_spawnp failed") || message.includes("enoent") || message.includes("not found") || message.includes("file not found") || message.includes("no such file") );} function checkWindowsSubprocessActivity( terminalPid: number,): Effect.Effect<boolean, TerminalSubprocessCheckError> { const command = [ `$children = Get-CimInstance Win32_Process -Filter "ParentProcessId = ${terminalPid}" -ErrorAction SilentlyContinue`, "if ($children) { exit 0 }", "exit 1", ].join("; "); return Effect.tryPromise({ try: () => runProcess("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", command], { timeoutMs: 1_500, allowNonZeroExit: true, maxBufferBytes: 32_768, outputMode: "truncate", }), catch: (cause) => new TerminalSubprocessCheckError({ message: "Failed to check Windows terminal subprocess activity.", cause, terminalPid, command: "powershell", }), }).pipe(Effect.map((result) => result.code === 0));} const checkPosixSubprocessActivity = Effect.fn("terminal.checkPosixSubprocessActivity")(function* ( terminalPid: number,): Effect.fn.Return<boolean, TerminalSubprocessCheckError> { const runPgrep = Effect.tryPromise({ try: () => runProcess("pgrep", ["-P", String(terminalPid)], { timeoutMs: 1_000, allowNonZeroExit: true, maxBufferBytes: 32_768, outputMode: "truncate", }), catch: (cause) => new TerminalSubprocessCheckError({ message: "Failed to inspect terminal subprocesses with pgrep.", cause, terminalPid, command: "pgrep", }), }); const runPs = Effect.tryPromise({ try: () => runProcess("ps", ["-eo", "pid=,ppid="], { timeoutMs: 1_000, allowNonZeroExit: true, maxBufferBytes: 262_144, outputMode: "truncate", }), catch: (cause) => new TerminalSubprocessCheckError({ message: "Failed to inspect terminal subprocesses with ps.", cause, terminalPid, command: "ps", }), }); const pgrepResult = yield* Effect.exit(runPgrep); if (pgrepResult._tag === "Success") { if (pgrepResult.value.code === 0) { return pgrepResult.value.stdout.trim().length > 0; } if (pgrepResult.value.code === 1) { return false; } } const psResult = yield* Effect.exit(runPs); if (psResult._tag === "Failure" || psResult.value.code !== 0) { return false; } for (const line of psResult.value.stdout.split(/\r?\n/g)) { const [pidRaw, ppidRaw] = line.trim().split(/\s+/g); const pid = Number(pidRaw); const ppid = Number(ppidRaw); if (!Number.isInteger(pid) || !Number.isInteger(ppid)) continue; if (ppid === terminalPid) { return true; } } return false;}); const defaultSubprocessChecker = Effect.fn("terminal.defaultSubprocessChecker")(function* ( terminalPid: number,): Effect.fn.Return<boolean, TerminalSubprocessCheckError> { if (!Number.isInteger(terminalPid) || terminalPid <= 0) { return false; } if (process.platform === "win32") { return yield* checkWindowsSubprocessActivity(terminalPid); } return yield* checkPosixSubprocessActivity(terminalPid);}); function capHistory(history: string, maxLines: number): string { if (history.length === 0) return history; const hasTrailingNewline = history.endsWith("\n"); const lines = history.split("\n"); if (hasTrailingNewline) { lines.pop(); } if (lines.length <= maxLines) return history; const capped = lines.slice(lines.length - maxLines).join("\n"); return hasTrailingNewline ? `${capped}\n` : capped;} function isCsiFinalByte(codePoint: number): boolean { return codePoint >= 0x40 && codePoint <= 0x7e;} function shouldStripCsiSequence(body: string, finalByte: string): boolean { if (finalByte === "n") { return true; } if (finalByte === "R" && /^[0-9;?]*$/.test(body)) { return true; } if (finalByte === "c" && /^[>0-9;?]*$/.test(body)) { return true; } return false;} function shouldStripOscSequence(content: string): boolean { return /^(10|11|12);(?:\?|rgb:)/.test(content);} function stripStringTerminator(value: string): string { if (value.endsWith("\u001b\\")) { return value.slice(0, -2); } const lastCharacter = value.at(-1); if (lastCharacter === "\u0007" || lastCharacter === "\u009c") { return value.slice(0, -1); } return value;} function findStringTerminatorIndex(input: string, start: number): number | null { for (let index = start; index < input.length; index += 1) { const codePoint = input.charCodeAt(index); if (codePoint === 0x07 || codePoint === 0x9c) { return index + 1;Lines 1–500 of 1915. Load more
import path from "node:path"; import { DEFAULT_TERMINAL_ID, type TerminalEvent, type TerminalSessionSnapshot, type TerminalSessionStatus, } from "@t3tools/contracts"; import { makeKeyedCoalescingWorker } from "@t3tools/shared/KeyedCoalescingWorker"; import { Effect, Encoding, Equal, Exit, Fiber, FileSystem, Layer, Option, Schema, Scope, Semaphore, SynchronizedRef, } from "effect"; import { ServerConfig } from "../../config.ts"; import { increment, terminalRestartsTotal, terminalSessionsTotal, } from "../../observability/Metrics.ts"; import { runProcess } from "../../processRunner.ts"; import { TerminalCwdError, TerminalHistoryError, TerminalManager, TerminalNotRunningError, TerminalSessionLookupError, type TerminalManagerShape, } from "../Services/Manager.ts"; import { PtyAdapter, PtySpawnError, type PtyAdapterShape, type PtyExitEvent, type PtyProcess, } from "../Services/PTY.ts"; const DEFAULT_HISTORY_LINE_LIMIT = 5_000; const DEFAULT_PERSIST_DEBOUNCE_MS = 40; const DEFAULT_SUBPROCESS_POLL_INTERVAL_MS = 1_000; const DEFAULT_PROCESS_KILL_GRACE_MS = 1_000; const DEFAULT_MAX_RETAINED_INACTIVE_SESSIONS = 128; const DEFAULT_OPEN_COLS = 120; const DEFAULT_OPEN_ROWS = 30; const TERMINAL_ENV_BLOCKLIST = new Set(["PORT", "ELECTRON_RENDERER_PORT", "ELECTRON_RUN_AS_NODE"]); class TerminalSubprocessCheckError extends Schema.TaggedErrorClass<TerminalSubprocessCheckError>()( "TerminalSubprocessCheckError", { message: Schema.String, cause: Schema.optional(Schema.Defect), terminalPid: Schema.Number, command: Schema.Literals(["powershell", "pgrep", "ps"]), }, ) {} class TerminalProcessSignalError extends Schema.TaggedErrorClass<TerminalProcessSignalError>()( "TerminalProcessSignalError", { message: Schema.String, cause: Schema.optional(Schema.Defect), signal: Schema.Literals(["SIGTERM", "SIGKILL"]), }, ) {} interface TerminalSubprocessChecker { (terminalPid: number): Effect.Effect<boolean, TerminalSubprocessCheckError>; } interface ShellCandidate { shell: string; args?: string[]; } interface TerminalStartInput { threadId: string; terminalId: string; cwd: string; worktreePath?: string | null; cols: number; rows: number; env?: Record<string, string>; } interface TerminalSessionState { threadId: string; terminalId: string; cwd: string; worktreePath: string | null; status: TerminalSessionStatus; pid: number | null; history: string; pendingHistoryControlSequence: string; pendingProcessEvents: Array<PendingProcessEvent>; pendingProcessEventIndex: number; processEventDrainRunning: boolean; exitCode: number | null; exitSignal: number | null; updatedAt: string; cols: number; rows: number; process: PtyProcess | null; unsubscribeData: (() => void) | null; unsubscribeExit: (() => void) | null; hasRunningSubprocess: boolean; runtimeEnv: Record<string, string> | null; } interface PersistHistoryRequest { history: string; immediate: boolean; } type PendingProcessEvent = { type: "output"; data: string } | { type: "exit"; event: PtyExitEvent }; type DrainProcessEventAction = | { type: "idle" } | { type: "output"; threadId: string; terminalId: string; history: string | null; data: string; } | { type: "exit"; process: PtyProcess | null; threadId: string; terminalId: string; exitCode: number | null; exitSignal: number | null; }; interface TerminalManagerState { sessions: Map<string, TerminalSessionState>; killFibers: Map<PtyProcess, Fiber.Fiber<void, never>>; } function snapshot(session: TerminalSessionState): TerminalSessionSnapshot { return { threadId: session.threadId, terminalId: session.terminalId, cwd: session.cwd, worktreePath: session.worktreePath, status: session.status, pid: session.pid, history: session.history, exitCode: session.exitCode, exitSignal: session.exitSignal, updatedAt: session.updatedAt, }; } function cleanupProcessHandles(session: TerminalSessionState): void { session.unsubscribeData?.(); session.unsubscribeData = null; session.unsubscribeExit?.(); session.unsubscribeExit = null; } function enqueueProcessEvent( session: TerminalSessionState, expectedPid: number, event: PendingProcessEvent, ): boolean { if (!session.process || session.status !== "running" || session.pid !== expectedPid) { return false; } session.pendingProcessEvents.push(event); if (session.processEventDrainRunning) { return false; } session.processEventDrainRunning = true; return true; } function defaultShellResolver( platform: NodeJS.Platform = process.platform, env: NodeJS.ProcessEnv = process.env, ): string { if (platform === "win32") { return "pwsh.exe"; } return env.SHELL ?? "bash"; } function normalizeShellCommand( value: string | undefined, platform: NodeJS.Platform = process.platform, ): string | null { if (!value) return null; const trimmed = value.trim(); if (trimmed.length === 0) return null; if (platform === "win32") { return trimmed; } const firstToken = trimmed.split(/\s+/g)[0]?.trim(); if (!firstToken) return null; return firstToken.replace(/^['"]|['"]$/g, ""); } function shellCandidateFromCommand( command: string | null, platform: NodeJS.Platform = process.platform, ): ShellCandidate | null { if (!command || command.length === 0) return null; const shellName = platform === "win32" ? path.win32.basename(command).toLowerCase() : path.basename(command).toLowerCase(); if (platform === "win32" && (shellName === "pwsh.exe" || shellName === "powershell.exe")) { return { shell: command, args: ["-NoLogo"] }; } if (platform !== "win32" && shellName === "zsh") { return { shell: command, args: ["-o", "nopromptsp"] }; } return { shell: command }; } function windowsSystemRoot(env: NodeJS.ProcessEnv): string { return env.SystemRoot?.trim() || env.windir?.trim() || "C:\\Windows"; } function windowsPowerShellPath(env: NodeJS.ProcessEnv): string { return path.win32.join( windowsSystemRoot(env), "System32", "WindowsPowerShell", "v1.0", "powershell.exe", ); } function windowsCmdPath(env: NodeJS.ProcessEnv): string { return path.win32.join(windowsSystemRoot(env), "System32", "cmd.exe"); } function formatShellCandidate(candidate: ShellCandidate): string { if (!candidate.args || candidate.args.length === 0) return candidate.shell; return `${candidate.shell} ${candidate.args.join(" ")}`; } function uniqueShellCandidates(candidates: Array<ShellCandidate | null>): ShellCandidate[] { const seen = new Set<string>(); const ordered: ShellCandidate[] = []; for (const candidate of candidates) { if (!candidate) continue; const key = formatShellCandidate(candidate); if (seen.has(key)) continue; seen.add(key); ordered.push(candidate); } return ordered; } function resolveShellCandidates( shellResolver: () => string, platform: NodeJS.Platform = process.platform, env: NodeJS.ProcessEnv = process.env, ): ShellCandidate[] { const requested = shellCandidateFromCommand( normalizeShellCommand(shellResolver(), platform), platform, ); if (platform === "win32") { return uniqueShellCandidates([ requested, shellCandidateFromCommand("pwsh.exe", platform), shellCandidateFromCommand(windowsPowerShellPath(env), platform), shellCandidateFromCommand("powershell.exe", platform), shellCandidateFromCommand(env.ComSpec ?? null, platform), shellCandidateFromCommand(windowsCmdPath(env), platform), shellCandidateFromCommand("cmd.exe", platform), ]); } return uniqueShellCandidates([ requested, shellCandidateFromCommand(normalizeShellCommand(env.SHELL, platform), platform), shellCandidateFromCommand("/bin/zsh", platform), shellCandidateFromCommand("/bin/bash", platform), shellCandidateFromCommand("/bin/sh", platform), shellCandidateFromCommand("zsh", platform), shellCandidateFromCommand("bash", platform), shellCandidateFromCommand("sh", platform), ]); } function isRetryableShellSpawnError(error: PtySpawnError): boolean { const queue: unknown[] = [error]; const seen = new Set<unknown>(); const messages: string[] = []; while (queue.length > 0) { const current = queue.shift(); if (!current || seen.has(current)) { continue; } seen.add(current); if (typeof current === "string") { messages.push(current); continue; } if (current instanceof Error) { messages.push(current.message); if (current.cause) { queue.push(current.cause); } continue; } if (typeof current === "object") { const value = current as { message?: unknown; cause?: unknown }; if (typeof value.message === "string") { messages.push(value.message); } if (value.cause) { queue.push(value.cause); } } } const message = messages.join(" ").toLowerCase(); return ( message.includes("posix_spawnp failed") || message.includes("enoent") || message.includes("not found") || message.includes("file not found") || message.includes("no such file") ); } function checkWindowsSubprocessActivity( terminalPid: number, ): Effect.Effect<boolean, TerminalSubprocessCheckError> { const command = [ `$children = Get-CimInstance Win32_Process -Filter "ParentProcessId = ${terminalPid}" -ErrorAction SilentlyContinue`, "if ($children) { exit 0 }", "exit 1", ].join("; "); return Effect.tryPromise({ try: () => runProcess("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", command], { timeoutMs: 1_500, allowNonZeroExit: true, maxBufferBytes: 32_768, outputMode: "truncate", }), catch: (cause) => new TerminalSubprocessCheckError({ message: "Failed to check Windows terminal subprocess activity.", cause, terminalPid, command: "powershell", }), }).pipe(Effect.map((result) => result.code === 0)); } const checkPosixSubprocessActivity = Effect.fn("terminal.checkPosixSubprocessActivity")(function* ( terminalPid: number, ): Effect.fn.Return<boolean, TerminalSubprocessCheckError> { const runPgrep = Effect.tryPromise({ try: () => runProcess("pgrep", ["-P", String(terminalPid)], { timeoutMs: 1_000, allowNonZeroExit: true, maxBufferBytes: 32_768, outputMode: "truncate", }), catch: (cause) => new TerminalSubprocessCheckError({ message: "Failed to inspect terminal subprocesses with pgrep.", cause, terminalPid, command: "pgrep", }), }); const runPs = Effect.tryPromise({ try: () => runProcess("ps", ["-eo", "pid=,ppid="], { timeoutMs: 1_000, allowNonZeroExit: true, maxBufferBytes: 262_144, outputMode: "truncate", }), catch: (cause) => new TerminalSubprocessCheckError({ message: "Failed to inspect terminal subprocesses with ps.", cause, terminalPid, command: "ps", }), }); const pgrepResult = yield* Effect.exit(runPgrep); if (pgrepResult._tag === "Success") { if (pgrepResult.value.code === 0) { return pgrepResult.value.stdout.trim().length > 0; } if (pgrepResult.value.code === 1) { return false; } } const psResult = yield* Effect.exit(runPs); if (psResult._tag === "Failure" || psResult.value.code !== 0) { return false; } for (const line of psResult.value.stdout.split(/\r?\n/g)) { const [pidRaw, ppidRaw] = line.trim().split(/\s+/g); const pid = Number(pidRaw); const ppid = Number(ppidRaw); if (!Number.isInteger(pid) || !Number.isInteger(ppid)) continue; if (ppid === terminalPid) { return true; } } return false; }); const defaultSubprocessChecker = Effect.fn("terminal.defaultSubprocessChecker")(function* ( terminalPid: number, ): Effect.fn.Return<boolean, TerminalSubprocessCheckError> { if (!Number.isInteger(terminalPid) || terminalPid <= 0) { return false; } if (process.platform === "win32") { return yield* checkWindowsSubprocessActivity(terminalPid); } return yield* checkPosixSubprocessActivity(terminalPid); }); function capHistory(history: string, maxLines: number): string { if (history.length === 0) return history; const hasTrailingNewline = history.endsWith("\n"); const lines = history.split("\n"); if (hasTrailingNewline) { lines.pop(); } if (lines.length <= maxLines) return history; const capped = lines.slice(lines.length - maxLines).join("\n"); return hasTrailingNewline ? `${capped}\n` : capped; } function isCsiFinalByte(codePoint: number): boolean { return codePoint >= 0x40 && codePoint <= 0x7e; } function shouldStripCsiSequence(body: string, finalByte: string): boolean { if (finalByte === "n") { return true; } if (finalByte === "R" && /^[0-9;?]*$/.test(body)) { return true; } if (finalByte === "c" && /^[>0-9;?]*$/.test(body)) { return true; } return false; } function shouldStripOscSequence(content: string): boolean { return /^(10|11|12);(?:\?|rgb:)/.test(content); } function stripStringTerminator(value: string): string { if (value.endsWith("\u001b\\")) { return value.slice(0, -2); } const lastCharacter = value.at(-1); if (lastCharacter === "\u0007" || lastCharacter === "\u009c") { return value.slice(0, -1); } return value; } function findStringTerminatorIndex(input: string, start: number): number | null { for (let index = start; index < input.length; index += 1) { const codePoint = input.charCodeAt(index); if (codePoint === 0x07 || codePoint === 0x9c) { return index + 1;