authorgravatar for 71498452+StiensWout@users.noreply.github.comWout Stiens <71498452+StiensWout@users.noreply.github.com> 2026-10-11 03:40:41+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-10-10 18:40:41-07:00
log7f0b789380f95032a55c777817cde8b49e2dae5a
tree7bfaf98e711474e9073e1b5cd7c31eb4526dd156
parentcf8479e605eb3cc8662dee38cbf1925020a1c1a7
signature Signed by PGP key B5690EEEBB952194

perf(server): skip parsing plain terminal history output (#17179)


2 files changed, 26 insertions(+), 0 deletions(-)

apps/server/src/terminal/Manager.test.ts+17
......@@ -1516,6 +1516,23 @@ it.layer(
15161516 }),
15171517 );
15181518
1519 it.effect("retains plain Unicode and control characters around a split query", () =>
1520 Effect.gen(function* () {
1521 const { manager, ptyAdapter } = yield* createManager();
1522 yield* manager.open(openInput());
1523 const process = ptyAdapter.processes[0]!;
1524 const before = "café\t界🙂\u0007\u009c\u0085";
1525 const after = "e\u0301\r\n";
1526
1527 process.emitData(before);
1528 process.emitData("\u001b[5");
1529 process.emitData(`n${after}`);
1530 yield* manager.close({ threadId: "thread-1" });
1531
1532 expect((yield* manager.open(openInput())).history).toBe(before + after);
1533 }),
1534 );
1535
15191536 it.effect("strips replay-unsafe terminal query and reply sequences from persisted history", () =>
15201537 Effect.gen(function* () {
15211538 const { manager, ptyAdapter } = yield* createManager();
apps/server/src/terminal/Manager.ts+9
......@@ -1115,10 +1115,19 @@ function findEscapeSequenceEndIndex(input: string, start: number): number | null
11151115 return isEscapeFinalByte(input.charCodeAt(cursor)) ? cursor + 1 : start + 1;
11161116}
11171117
1118// eslint-disable-next-line no-control-regex -- Terminal sequence introducers must reach the parser.
1119const HISTORY_SEQUENCE_START = /[\u001b\u0090\u009b\u009d\u009e\u009f]/;
1120
11181121function sanitizeTerminalHistoryChunk(
11191122 pendingControlSequence: string,
11201123 data: string,
11211124): { visibleText: string; pendingControlSequence: string } {
1125 // Plain output has nothing to filter. A pending sequence still needs the parser
1126 // even when this chunk only contains its final bytes.
1127 if (pendingControlSequence.length === 0 && !HISTORY_SEQUENCE_START.test(data)) {
1128 return { visibleText: data, pendingControlSequence: "" };
1129 }
1130
11221131 const input = `${pendingControlSequence}${data}`;
11231132 let visibleText = "";
11241133 let index = 0;