From beda3de13706b503c61d180c7a6fb013e65478d3 Mon Sep 17 00:00:00 2001 From: BinBandit Date: Mon, 9 Mar 2026 13:12:58 +1100 Subject: [PATCH] fix(contracts): align terminal restart input type across IPC, WS, and server The NativeApi IPC interface and server TerminalManagerShape both typed the `restart` method with `TerminalOpenInput` (optional cols/rows, includes terminalId), while the WebSocket schema validated against `TerminalRestartInput` (required cols/rows, no terminalId). This corrects the mismatch by: 1. Adding `terminalId` (with decoding default) to `TerminalRestartInput` so it matches the server's actual usage of `input.terminalId`. 2. Updating the IPC `NativeApi.terminal.restart` to accept `TerminalRestartInput`, communicating that cols/rows are required. 3. Updating the server `TerminalManagerShape` and `TerminalManagerRuntime` to accept and decode `TerminalRestartInput` consistently. The change is backward-compatible on the wire since `terminalId` uses `withDecodingDefault` and defaults to `"default"` when omitted. --- apps/server/src/terminal/Layers/Manager.test.ts | 13 ++++++++++++- apps/server/src/terminal/Layers/Manager.ts | 6 ++++-- apps/server/src/terminal/Services/Manager.ts | 3 ++- packages/contracts/src/ipc.ts | 3 ++- packages/contracts/src/terminal.ts | 3 ++- 5 files changed, 22 insertions(+), 6 deletions(-) diff --git a/apps/server/src/terminal/Layers/Manager.test.ts b/apps/server/src/terminal/Layers/Manager.test.ts index 0d45bfcb29..825bcbded3 100644 --- a/apps/server/src/terminal/Layers/Manager.test.ts +++ b/apps/server/src/terminal/Layers/Manager.test.ts @@ -6,6 +6,7 @@ import { DEFAULT_TERMINAL_ID, type TerminalEvent, type TerminalOpenInput, + type TerminalRestartInput, } from "@t3tools/contracts"; import { afterEach, describe, expect, it } from "vitest"; @@ -134,6 +135,16 @@ function openInput(overrides: Partial = {}): TerminalOpenInpu }; } +function restartInput(overrides: Partial = {}): TerminalRestartInput { + return { + threadId: "thread-1", + cwd: process.cwd(), + cols: 100, + rows: 24, + ...overrides, + }; +} + function historyLogName(threadId: string): string { return `terminal_${Encoding.encodeBase64Url(threadId)}.log`; } @@ -361,7 +372,7 @@ describe("TerminalManager", () => { firstProcess.emitData("before restart\n"); await waitFor(() => fs.existsSync(historyLogPath(logsDir))); - const snapshot = await manager.restart(openInput()); + const snapshot = await manager.restart(restartInput()); expect(snapshot.history).toBe(""); expect(snapshot.status).toBe("running"); expect(ptyAdapter.spawnInputs).toHaveLength(2); diff --git a/apps/server/src/terminal/Layers/Manager.ts b/apps/server/src/terminal/Layers/Manager.ts index 771c56b287..f84e7b5930 100644 --- a/apps/server/src/terminal/Layers/Manager.ts +++ b/apps/server/src/terminal/Layers/Manager.ts @@ -8,6 +8,7 @@ import { TerminalCloseInput, TerminalOpenInput, TerminalResizeInput, + TerminalRestartInput, TerminalWriteInput, type TerminalEvent, type TerminalSessionSnapshot, @@ -37,6 +38,7 @@ const DEFAULT_OPEN_ROWS = 30; const TERMINAL_ENV_BLOCKLIST = new Set(["PORT", "ELECTRON_RENDERER_PORT", "ELECTRON_RUN_AS_NODE"]); const decodeTerminalOpenInput = Schema.decodeUnknownSync(TerminalOpenInput); +const decodeTerminalRestartInput = Schema.decodeUnknownSync(TerminalRestartInput); const decodeTerminalWriteInput = Schema.decodeUnknownSync(TerminalWriteInput); const decodeTerminalResizeInput = Schema.decodeUnknownSync(TerminalResizeInput); const decodeTerminalClearInput = Schema.decodeUnknownSync(TerminalClearInput); @@ -478,8 +480,8 @@ export class TerminalManagerRuntime extends EventEmitter }); } - async restart(raw: TerminalOpenInput): Promise { - const input = decodeTerminalOpenInput(raw); + async restart(raw: TerminalRestartInput): Promise { + const input = decodeTerminalRestartInput(raw); return this.runWithThreadLock(input.threadId, async () => { await this.assertValidCwd(input.cwd); diff --git a/apps/server/src/terminal/Services/Manager.ts b/apps/server/src/terminal/Services/Manager.ts index 9cff323991..8d8398c7ad 100644 --- a/apps/server/src/terminal/Services/Manager.ts +++ b/apps/server/src/terminal/Services/Manager.ts @@ -12,6 +12,7 @@ import { TerminalEvent, TerminalOpenInput, TerminalResizeInput, + TerminalRestartInput, TerminalSessionSnapshot, TerminalSessionStatus, TerminalWriteInput, @@ -88,7 +89,7 @@ export interface TerminalManagerShape { * Always resets history before spawning the new process. */ readonly restart: ( - input: TerminalOpenInput, + input: TerminalRestartInput, ) => Effect.Effect; /** diff --git a/packages/contracts/src/ipc.ts b/packages/contracts/src/ipc.ts index 92f5b502c9..7a8ff7fb67 100644 --- a/packages/contracts/src/ipc.ts +++ b/packages/contracts/src/ipc.ts @@ -27,6 +27,7 @@ import type { TerminalEvent, TerminalOpenInput, TerminalResizeInput, + TerminalRestartInput, TerminalSessionSnapshot, TerminalWriteInput, } from "./terminal"; @@ -103,7 +104,7 @@ export interface NativeApi { write: (input: TerminalWriteInput) => Promise; resize: (input: TerminalResizeInput) => Promise; clear: (input: TerminalClearInput) => Promise; - restart: (input: TerminalOpenInput) => Promise; + restart: (input: TerminalRestartInput) => Promise; close: (input: TerminalCloseInput) => Promise; onEvent: (callback: (event: TerminalEvent) => void) => () => void; }; diff --git a/packages/contracts/src/terminal.ts b/packages/contracts/src/terminal.ts index e7c20242b1..b0493d95c2 100644 --- a/packages/contracts/src/terminal.ts +++ b/packages/contracts/src/terminal.ts @@ -60,12 +60,13 @@ export const TerminalClearInput = TerminalSessionInput; export type TerminalClearInput = Schema.Codec.Encoded; export const TerminalRestartInput = Schema.Struct({ - ...TerminalThreadInput.fields, + ...TerminalSessionInput.fields, cwd: TrimmedNonEmptyStringSchema, cols: TerminalColsSchema, rows: TerminalRowsSchema, env: Schema.optional(TerminalEnvSchema), }); +export type TerminalRestartInput = Schema.Codec.Encoded; export const TerminalCloseInput = Schema.Struct({ ...TerminalThreadInput.fields,