Fix terminal snapshots reflowing after resize

Terminal snapshots now carry soft-wrap row metadata only to clients that advertise support, so restored output can resize like live output without breaking older clients.
This commit is contained in:
Mohamed Boudra
2026-05-31 19:40:15 +07:00
parent 794cbcef63
commit 4cc72a84e8
18 changed files with 538 additions and 146 deletions
+43 -45
View File
@@ -485,15 +485,13 @@ const ToolCallDetailPayloadSchema: z.ZodType<ToolCallDetail, z.ZodTypeDef, unkno
}),
]);
const ToolCallBasePayloadSchema = z
.object({
type: z.literal("tool_call"),
callId: z.string(),
name: z.string(),
detail: ToolCallDetailPayloadSchema,
metadata: z.record(z.string(), z.unknown()).optional(),
})
.strict();
const ToolCallBasePayloadSchema = z.object({
type: z.literal("tool_call"),
callId: z.string(),
name: z.string(),
detail: ToolCallDetailPayloadSchema,
metadata: z.record(z.string(), z.unknown()).optional(),
});
const ToolCallRunningPayloadSchema = ToolCallBasePayloadSchema.extend({
status: z.literal("running"),
@@ -1808,10 +1806,8 @@ export const SubscribeTerminalRequestSchema = z.object({
rows: z.number().int().positive(),
cols: z.number().int().positive(),
})
.strict()
.optional(),
})
.strict()
.optional(),
});
@@ -3540,44 +3536,45 @@ const TerminalInfoSchema = z.object({
title: z.string().optional(),
});
export const TerminalCellSchema = z
.object({
char: z.string(),
fg: z.number().optional(),
bg: z.number().optional(),
fgMode: z.number().optional(),
bgMode: z.number().optional(),
bold: z.boolean().optional(),
italic: z.boolean().optional(),
underline: z.boolean().optional(),
dim: z.boolean().optional(),
inverse: z.boolean().optional(),
strikethrough: z.boolean().optional(),
})
.strict();
export const TerminalCellSchema = z.object({
char: z.string(),
fg: z.number().optional(),
bg: z.number().optional(),
fgMode: z.number().optional(),
bgMode: z.number().optional(),
bold: z.boolean().optional(),
italic: z.boolean().optional(),
underline: z.boolean().optional(),
dim: z.boolean().optional(),
inverse: z.boolean().optional(),
strikethrough: z.boolean().optional(),
});
export const TerminalCursorStyleSchema = z.enum(["block", "underline", "bar"]);
export const TerminalCursorSchema = z
.object({
row: z.number(),
col: z.number(),
hidden: z.boolean().optional(),
style: TerminalCursorStyleSchema.optional(),
blink: z.boolean().optional(),
})
.strict();
export const TerminalCursorSchema = z.object({
row: z.number(),
col: z.number(),
hidden: z.boolean().optional(),
style: TerminalCursorStyleSchema.optional(),
blink: z.boolean().optional(),
});
export const TerminalStateSchema = z
.object({
rows: z.number(),
cols: z.number(),
grid: z.array(z.array(TerminalCellSchema)),
scrollback: z.array(z.array(TerminalCellSchema)),
cursor: TerminalCursorSchema,
title: z.string().optional(),
})
.strict();
export const TerminalStateSchema = z.object({
rows: z.number(),
cols: z.number(),
grid: z.array(z.array(TerminalCellSchema)),
scrollback: z.array(z.array(TerminalCellSchema)),
cursor: TerminalCursorSchema,
title: z.string().optional(),
// Per-row soft-wrap flags aligned 1:1 with `grid` / `scrollback`. `true` means
// the row continued onto the next row (xterm's GRID_LINE_WRAPPED equivalent),
// so the client can re-wrap the logical line on resize instead of freezing it
// at the snapshot width. Optional: only sent to clients that advertise the
// `terminalReflowableSnapshot` capability, so old daemons/clients are unaffected.
gridWrapped: z.array(z.boolean()).optional(),
scrollbackWrapped: z.array(z.boolean()).optional(),
});
export const ListTerminalsResponseSchema = z.object({
type: z.literal("list_terminals_response"),
@@ -4108,6 +4105,7 @@ export const WSHelloMessageSchema = z.object({
pushNotifications: z.boolean().optional(),
[CLIENT_CAPS.reasoningMergeEnum]: z.boolean().optional(),
[CLIENT_CAPS.customModeIcons]: z.boolean().optional(),
[CLIENT_CAPS.terminalReflowableSnapshot]: z.boolean().optional(),
})
.passthrough()
.optional(),