diff --git a/packages/server/src/terminal/terminal-restore.test.ts b/packages/server/src/terminal/terminal-restore.test.ts index adf29cb726..a290ac3794 100644 --- a/packages/server/src/terminal/terminal-restore.test.ts +++ b/packages/server/src/terminal/terminal-restore.test.ts @@ -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); +}); diff --git a/packages/server/src/terminal/terminal-restore.ts b/packages/server/src/terminal/terminal-restore.ts index dd453eef42..5a76899a38 100644 --- a/packages/server/src/terminal/terminal-restore.ts +++ b/packages/server/src/terminal/terminal-restore.ts @@ -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; + payload = JSON.stringify(begin); + } if (event.type === "data") payload = event.data; input.send( encodeTerminalStreamFrame({ slot: input.slot, opcode: opcodes[event.type], payload }),