Files
oh-my-pi/packages/coding-agent/src/launch/terminal-output.ts
T
can1357 96cc9caa66 feat(launch): enabled pty terminal rendering with standardized dimensions and serialization
- 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.
2026-07-13 18:27:24 +02:00

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