From c36dbf3190bf883356f61f74d4864e2c7a63d70c Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Wed, 29 Apr 2026 16:38:03 -0700 Subject: [PATCH 1/5] fix terminal dimension validation Co-authored-by: codex --- packages/contracts/src/terminal.test.ts | 11 +++++++++++ packages/contracts/src/terminal.ts | 6 ++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/packages/contracts/src/terminal.test.ts b/packages/contracts/src/terminal.test.ts index 3feae674924..31b857f083e 100644 --- a/packages/contracts/src/terminal.test.ts +++ b/packages/contracts/src/terminal.test.ts @@ -38,6 +38,17 @@ describe("TerminalOpenInput", () => { ).toBe(true); }); + it("accepts ultrawide terminal dimensions from xterm fit", () => { + expect( + decodes(TerminalOpenInput, { + threadId: "thread-1", + cwd: "/tmp/project", + cols: 423, + rows: 40, + }), + ).toBe(true); + }); + it("rejects invalid bounds", () => { expect( decodes(TerminalOpenInput, { diff --git a/packages/contracts/src/terminal.ts b/packages/contracts/src/terminal.ts index 21bd74a0999..ae8e75d3dbc 100644 --- a/packages/contracts/src/terminal.ts +++ b/packages/contracts/src/terminal.ts @@ -4,11 +4,13 @@ import { TrimmedNonEmptyString } from "./baseSchemas.ts"; export const DEFAULT_TERMINAL_ID = "default"; const TrimmedNonEmptyStringSchema = TrimmedNonEmptyString; +const MAX_TERMINAL_COLS = 1_000; +const MAX_TERMINAL_ROWS = 500; const TerminalColsSchema = Schema.Int.check(Schema.isGreaterThanOrEqualTo(20)).check( - Schema.isLessThanOrEqualTo(400), + Schema.isLessThanOrEqualTo(MAX_TERMINAL_COLS), ); const TerminalRowsSchema = Schema.Int.check(Schema.isGreaterThanOrEqualTo(5)).check( - Schema.isLessThanOrEqualTo(200), + Schema.isLessThanOrEqualTo(MAX_TERMINAL_ROWS), ); const TerminalIdSchema = TrimmedNonEmptyStringSchema.check(Schema.isMaxLength(128)); const TerminalEnvKeySchema = Schema.String.check( From 4bea7a3a7a077903b34700d627b55c4d0965bd84 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Wed, 29 Apr 2026 16:58:17 -0700 Subject: [PATCH 2/5] Relax terminal input validation bounds - Allow smaller terminal dimensions on startup and resize - Keep session/env schema limits aligned with runtime defaults --- packages/contracts/src/terminal.ts | 54 ++++++++++++++++++++---------- 1 file changed, 36 insertions(+), 18 deletions(-) diff --git a/packages/contracts/src/terminal.ts b/packages/contracts/src/terminal.ts index ae8e75d3dbc..fdb35ae0505 100644 --- a/packages/contracts/src/terminal.ts +++ b/packages/contracts/src/terminal.ts @@ -4,22 +4,23 @@ import { TrimmedNonEmptyString } from "./baseSchemas.ts"; export const DEFAULT_TERMINAL_ID = "default"; const TrimmedNonEmptyStringSchema = TrimmedNonEmptyString; -const MAX_TERMINAL_COLS = 1_000; -const MAX_TERMINAL_ROWS = 500; -const TerminalColsSchema = Schema.Int.check(Schema.isGreaterThanOrEqualTo(20)).check( - Schema.isLessThanOrEqualTo(MAX_TERMINAL_COLS), +const TerminalColsSchema = Schema.Int.check( + Schema.isGreaterThanOrEqualTo(1), +).check(Schema.isLessThanOrEqualTo(1000)); +const TerminalRowsSchema = Schema.Int.check( + Schema.isGreaterThanOrEqualTo(1), +).check(Schema.isLessThanOrEqualTo(500)); +const TerminalIdSchema = TrimmedNonEmptyStringSchema.check( + Schema.isMaxLength(128), ); -const TerminalRowsSchema = Schema.Int.check(Schema.isGreaterThanOrEqualTo(5)).check( - Schema.isLessThanOrEqualTo(MAX_TERMINAL_ROWS), -); -const TerminalIdSchema = TrimmedNonEmptyStringSchema.check(Schema.isMaxLength(128)); const TerminalEnvKeySchema = Schema.String.check( Schema.isPattern(/^[A-Za-z_][A-Za-z0-9_]*$/), ).check(Schema.isMaxLength(128)); const TerminalEnvValueSchema = Schema.String.check(Schema.isMaxLength(8_192)); -const TerminalEnvSchema = Schema.Record(TerminalEnvKeySchema, TerminalEnvValueSchema).check( - Schema.isMaxProperties(128), -); +const TerminalEnvSchema = Schema.Record( + TerminalEnvKeySchema, + TerminalEnvValueSchema, +).check(Schema.isMaxProperties(128)); const TerminalIdWithDefaultSchema = TerminalIdSchema.pipe( Schema.withDecodingDefault(Effect.succeed(DEFAULT_TERMINAL_ID)), @@ -34,7 +35,9 @@ const TerminalSessionInput = Schema.Struct({ ...TerminalThreadInput.fields, terminalId: TerminalIdWithDefaultSchema, }); -export type TerminalSessionInput = Schema.Codec.Encoded; +export type TerminalSessionInput = Schema.Codec.Encoded< + typeof TerminalSessionInput +>; export const TerminalOpenInput = Schema.Struct({ ...TerminalSessionInput.fields, @@ -48,19 +51,27 @@ export type TerminalOpenInput = Schema.Codec.Encoded; export const TerminalWriteInput = Schema.Struct({ ...TerminalSessionInput.fields, - data: Schema.String.check(Schema.isNonEmpty()).check(Schema.isMaxLength(65_536)), + data: Schema.String.check(Schema.isNonEmpty()).check( + Schema.isMaxLength(65_536), + ), }); -export type TerminalWriteInput = Schema.Codec.Encoded; +export type TerminalWriteInput = Schema.Codec.Encoded< + typeof TerminalWriteInput +>; export const TerminalResizeInput = Schema.Struct({ ...TerminalSessionInput.fields, cols: TerminalColsSchema, rows: TerminalRowsSchema, }); -export type TerminalResizeInput = Schema.Codec.Encoded; +export type TerminalResizeInput = Schema.Codec.Encoded< + typeof TerminalResizeInput +>; export const TerminalClearInput = TerminalSessionInput; -export type TerminalClearInput = Schema.Codec.Encoded; +export type TerminalClearInput = Schema.Codec.Encoded< + typeof TerminalClearInput +>; export const TerminalRestartInput = Schema.Struct({ ...TerminalSessionInput.fields, @@ -70,7 +81,9 @@ export const TerminalRestartInput = Schema.Struct({ rows: TerminalRowsSchema, env: Schema.optional(TerminalEnvSchema), }); -export type TerminalRestartInput = Schema.Codec.Encoded; +export type TerminalRestartInput = Schema.Codec.Encoded< + typeof TerminalRestartInput +>; export const TerminalCloseInput = Schema.Struct({ ...TerminalThreadInput.fields, @@ -79,7 +92,12 @@ export const TerminalCloseInput = Schema.Struct({ }); export type TerminalCloseInput = typeof TerminalCloseInput.Type; -export const TerminalSessionStatus = Schema.Literals(["starting", "running", "exited", "error"]); +export const TerminalSessionStatus = Schema.Literals([ + "starting", + "running", + "exited", + "error", +]); export type TerminalSessionStatus = typeof TerminalSessionStatus.Type; export const TerminalSessionSnapshot = Schema.Struct({ From 1a657f0f7d3220450460d047b8f0f3373014cbd9 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Wed, 29 Apr 2026 16:58:52 -0700 Subject: [PATCH 3/5] Format terminal contract schemas - Reflow terminal schema definitions - Keep contract types and defaults unchanged --- packages/contracts/src/terminal.ts | 52 +++++++++--------------------- 1 file changed, 16 insertions(+), 36 deletions(-) diff --git a/packages/contracts/src/terminal.ts b/packages/contracts/src/terminal.ts index fdb35ae0505..c4d9972d8c3 100644 --- a/packages/contracts/src/terminal.ts +++ b/packages/contracts/src/terminal.ts @@ -4,23 +4,20 @@ import { TrimmedNonEmptyString } from "./baseSchemas.ts"; export const DEFAULT_TERMINAL_ID = "default"; const TrimmedNonEmptyStringSchema = TrimmedNonEmptyString; -const TerminalColsSchema = Schema.Int.check( - Schema.isGreaterThanOrEqualTo(1), -).check(Schema.isLessThanOrEqualTo(1000)); -const TerminalRowsSchema = Schema.Int.check( - Schema.isGreaterThanOrEqualTo(1), -).check(Schema.isLessThanOrEqualTo(500)); -const TerminalIdSchema = TrimmedNonEmptyStringSchema.check( - Schema.isMaxLength(128), +const TerminalColsSchema = Schema.Int.check(Schema.isGreaterThanOrEqualTo(1)).check( + Schema.isLessThanOrEqualTo(1000), ); +const TerminalRowsSchema = Schema.Int.check(Schema.isGreaterThanOrEqualTo(1)).check( + Schema.isLessThanOrEqualTo(500), +); +const TerminalIdSchema = TrimmedNonEmptyStringSchema.check(Schema.isMaxLength(128)); const TerminalEnvKeySchema = Schema.String.check( Schema.isPattern(/^[A-Za-z_][A-Za-z0-9_]*$/), ).check(Schema.isMaxLength(128)); const TerminalEnvValueSchema = Schema.String.check(Schema.isMaxLength(8_192)); -const TerminalEnvSchema = Schema.Record( - TerminalEnvKeySchema, - TerminalEnvValueSchema, -).check(Schema.isMaxProperties(128)); +const TerminalEnvSchema = Schema.Record(TerminalEnvKeySchema, TerminalEnvValueSchema).check( + Schema.isMaxProperties(128), +); const TerminalIdWithDefaultSchema = TerminalIdSchema.pipe( Schema.withDecodingDefault(Effect.succeed(DEFAULT_TERMINAL_ID)), @@ -35,9 +32,7 @@ const TerminalSessionInput = Schema.Struct({ ...TerminalThreadInput.fields, terminalId: TerminalIdWithDefaultSchema, }); -export type TerminalSessionInput = Schema.Codec.Encoded< - typeof TerminalSessionInput ->; +export type TerminalSessionInput = Schema.Codec.Encoded; export const TerminalOpenInput = Schema.Struct({ ...TerminalSessionInput.fields, @@ -51,27 +46,19 @@ export type TerminalOpenInput = Schema.Codec.Encoded; export const TerminalWriteInput = Schema.Struct({ ...TerminalSessionInput.fields, - data: Schema.String.check(Schema.isNonEmpty()).check( - Schema.isMaxLength(65_536), - ), + data: Schema.String.check(Schema.isNonEmpty()).check(Schema.isMaxLength(65_536)), }); -export type TerminalWriteInput = Schema.Codec.Encoded< - typeof TerminalWriteInput ->; +export type TerminalWriteInput = Schema.Codec.Encoded; export const TerminalResizeInput = Schema.Struct({ ...TerminalSessionInput.fields, cols: TerminalColsSchema, rows: TerminalRowsSchema, }); -export type TerminalResizeInput = Schema.Codec.Encoded< - typeof TerminalResizeInput ->; +export type TerminalResizeInput = Schema.Codec.Encoded; export const TerminalClearInput = TerminalSessionInput; -export type TerminalClearInput = Schema.Codec.Encoded< - typeof TerminalClearInput ->; +export type TerminalClearInput = Schema.Codec.Encoded; export const TerminalRestartInput = Schema.Struct({ ...TerminalSessionInput.fields, @@ -81,9 +68,7 @@ export const TerminalRestartInput = Schema.Struct({ rows: TerminalRowsSchema, env: Schema.optional(TerminalEnvSchema), }); -export type TerminalRestartInput = Schema.Codec.Encoded< - typeof TerminalRestartInput ->; +export type TerminalRestartInput = Schema.Codec.Encoded; export const TerminalCloseInput = Schema.Struct({ ...TerminalThreadInput.fields, @@ -92,12 +77,7 @@ export const TerminalCloseInput = Schema.Struct({ }); export type TerminalCloseInput = typeof TerminalCloseInput.Type; -export const TerminalSessionStatus = Schema.Literals([ - "starting", - "running", - "exited", - "error", -]); +export const TerminalSessionStatus = Schema.Literals(["starting", "running", "exited", "error"]); export type TerminalSessionStatus = typeof TerminalSessionStatus.Type; export const TerminalSessionSnapshot = Schema.Struct({ From 51e2c4a868993ee9224fc13786593baf165bdec9 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Wed, 29 Apr 2026 17:04:04 -0700 Subject: [PATCH 4/5] Reject zero-row terminal open inputs - Tighten terminal open validation in tests - Cover startup bug where rows=0 should be invalid --- packages/contracts/src/terminal.test.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/contracts/src/terminal.test.ts b/packages/contracts/src/terminal.test.ts index 31b857f083e..2914a456eb3 100644 --- a/packages/contracts/src/terminal.test.ts +++ b/packages/contracts/src/terminal.test.ts @@ -13,8 +13,13 @@ import { TerminalWriteInput, } from "./terminal.ts"; -function decodeSync(schema: S, input: unknown): Schema.Schema.Type { - return Schema.decodeUnknownSync(schema as never)(input) as Schema.Schema.Type; +function decodeSync( + schema: S, + input: unknown, +): Schema.Schema.Type { + return Schema.decodeUnknownSync(schema as never)( + input, + ) as Schema.Schema.Type; } function decodes(schema: S, input: unknown): boolean { @@ -55,7 +60,7 @@ describe("TerminalOpenInput", () => { threadId: "thread-1", cwd: "/tmp/project", cols: 10, - rows: 2, + rows: 0, }), ).toBe(false); }); From 680951ee22ca720bde6bb75476184d098bfb64ae Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Wed, 29 Apr 2026 17:04:22 -0700 Subject: [PATCH 5/5] Format terminal test helper consistently - Simplify the sync decode helper signature - Keep terminal contract tests aligned with current formatting --- packages/contracts/src/terminal.test.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/packages/contracts/src/terminal.test.ts b/packages/contracts/src/terminal.test.ts index 2914a456eb3..514320f6d4d 100644 --- a/packages/contracts/src/terminal.test.ts +++ b/packages/contracts/src/terminal.test.ts @@ -13,13 +13,8 @@ import { TerminalWriteInput, } from "./terminal.ts"; -function decodeSync( - schema: S, - input: unknown, -): Schema.Schema.Type { - return Schema.decodeUnknownSync(schema as never)( - input, - ) as Schema.Schema.Type; +function decodeSync(schema: S, input: unknown): Schema.Schema.Type { + return Schema.decodeUnknownSync(schema as never)(input) as Schema.Schema.Type; } function decodes(schema: S, input: unknown): boolean {