96cc9caa66
- Introduce `DAEMON_PTY_COLUMNS` and `DAEMON_PTY_ROWS` to `protocol.ts` for consistent PTY dimension management. - Implement `renderTerminalOutput` in `launch/terminal-output.ts` to utilize headless xterm for accurate terminal state replay. - Add `readTerminalRows` and `styleTerminalRow` in `tools/terminal-output.ts` to serialize terminal buffers while preserving safe ANSI styles. - Update `broker.ts` to use standardized PTY dimensions and ensure terminal output is properly sanitized and well-formed during stream processing.
47 lines
1.5 KiB
TypeScript
47 lines
1.5 KiB
TypeScript
import { logger } from "@oh-my-pi/pi-utils";
|
|
import xterm, { type Terminal as XtermTerminal } from "@xterm/headless";
|
|
import { readTerminalRows } from "../tools/terminal-output";
|
|
import { DAEMON_PTY_COLUMNS, DAEMON_PTY_ROWS } from "./protocol";
|
|
|
|
const VIRTUAL_SCROLLBACK_ROWS = 4_096;
|
|
|
|
/** Controls which virtual terminal rows a launch log exposes. */
|
|
export interface TerminalOutputOptions {
|
|
head: boolean;
|
|
maxRows: number;
|
|
}
|
|
|
|
function writeTerminal(terminal: XtermTerminal, output: string): Promise<void> {
|
|
const { promise, resolve } = Promise.withResolvers<void>();
|
|
terminal.write(output, resolve);
|
|
return promise;
|
|
}
|
|
|
|
/** Replays daemon bytes with the same xterm screen renderer used by PTY mode. */
|
|
export async function renderTerminalOutput(
|
|
output: string,
|
|
options: TerminalOutputOptions,
|
|
): Promise<string[] | undefined> {
|
|
if (!output) return [];
|
|
const maxRows = Math.max(1, Math.floor(options.maxRows));
|
|
const terminal = new xterm.Terminal({
|
|
cols: DAEMON_PTY_COLUMNS,
|
|
rows: DAEMON_PTY_ROWS,
|
|
scrollback: Math.max(VIRTUAL_SCROLLBACK_ROWS, maxRows),
|
|
allowProposedApi: true,
|
|
});
|
|
try {
|
|
await writeTerminal(terminal, output);
|
|
const rows = readTerminalRows(terminal, 0, terminal.buffer.active.length);
|
|
while (rows.at(-1) === "") rows.pop();
|
|
return options.head ? rows.slice(0, maxRows) : rows.slice(-maxRows);
|
|
} catch (error) {
|
|
logger.debug("Failed to render launch terminal output", {
|
|
error: error instanceof Error ? error.message : String(error),
|
|
});
|
|
return undefined;
|
|
} finally {
|
|
terminal.dispose();
|
|
}
|
|
}
|