fix(terminal): project replay metadata onto the wire contract

Keep worker revision and input-mode preamble internal while preserving the required retained-history metadata delivered to clients.
This commit is contained in:
Mohamed Boudra
2026-10-30 11:45:21 +01:00
parent 7cef8ef43b
commit f5830143f0
2 changed files with 48 additions and 1 deletions
@@ -5,6 +5,7 @@ import {
decodeTerminalStreamFrame,
} from "@getpaseo/protocol/binary-frames/index";
import {
createTerminalRestoreDelivery,
encodeTerminalRestoreFrame,
resolveRestoreAfterOutputOverflow,
resolveTerminalRestoreSnapshotOptions,
@@ -53,3 +54,40 @@ describe("terminal restore policy", () => {
expect(new TextDecoder().decode(frame?.payload ?? new Uint8Array())).toContain("restored");
});
});
test("projects chunked replay begin onto the declared wire metadata", () => {
const frames: Uint8Array[] = [];
const deliver = createTerminalRestoreDelivery({
slot: 7,
chunked: true,
send: (frame) => {
frames.push(frame);
},
});
deliver({
type: "begin",
metadata: {
rows: 30,
cols: 120,
retainedHistoryLines: 842,
revision: 5123,
replayPreamble: "\x1b[?2004h",
},
});
const data = new TextEncoder().encode("\x1b[31m界 replay\x1b[0m");
deliver({ type: "data", data });
deliver({ type: "end" });
const decoded = frames.map((frame) => decodeTerminalStreamFrame(frame)!);
expect(decoded.map((frame) => ({ slot: frame.slot, opcode: frame.opcode }))).toEqual([
{ slot: 7, opcode: TerminalStreamOpcode.ReplayBegin },
{ slot: 7, opcode: TerminalStreamOpcode.ReplayData },
{ slot: 7, opcode: TerminalStreamOpcode.ReplayEnd },
]);
expect(JSON.parse(new TextDecoder().decode(decoded[0]!.payload))).toEqual({
rows: 30,
cols: 120,
retainedHistoryLines: 842,
});
expect(decoded[1]!.payload).toEqual(data);
expect(decoded[2]!.payload.byteLength).toBe(0);
});
@@ -1,6 +1,8 @@
import type { z } from "zod";
import type { SubscribeTerminalRequest } from "@getpaseo/protocol/messages";
import {
TerminalStreamOpcode,
type TerminalReplayMetadataSchema,
encodeTerminalStreamFrame,
} from "@getpaseo/protocol/binary-frames/index";
import type { TerminalReplaySink, TerminalReplayOptions } from "./terminal-replay.js";
@@ -91,7 +93,14 @@ export function createTerminalRestoreDelivery(input: {
end: TerminalStreamOpcode.ReplayEnd,
};
let payload: string | Uint8Array | undefined;
if (event.type === "begin") payload = JSON.stringify(event.metadata);
if (event.type === "begin") {
const begin = {
rows: event.metadata.rows,
cols: event.metadata.cols,
retainedHistoryLines: event.metadata.retainedHistoryLines,
} satisfies z.infer<typeof TerminalReplayMetadataSchema>;
payload = JSON.stringify(begin);
}
if (event.type === "data") payload = event.data;
input.send(
encodeTerminalStreamFrame({ slot: input.slot, opcode: opcodes[event.type], payload }),