Files
paseo/packages/server/src/terminal/terminal-worker-protocol.ts
T
Christoph LeiterandClaude Opus 4.8 2658132384 fix(terminal): create PTYs at the client viewport size, not 80x24 (#2023)
New terminals were always spawned by the daemon at the hardcoded 80x24
default and only resized once the client's first resize arrived. On a
slow first mount that leaves a window -- or a stuck state -- where a new
terminal renders far smaller than its pane (e.g. vim in a corner).

Seed the PTY with the client's measured viewport size at creation time
so it is born at the right size and the race window is gone.

- protocol: optional `size` on create_terminal_request. Old daemons
  ignore it and keep 80x24; old clients send nothing. Fully backward
  compatible, no capability gate.
- client: a small last-measured-size cache keyed by serverId+cwd (every
  terminal in a workspace shares the pane) with a most-recent fallback;
  written on fit, read at create.
- server: thread rows/cols through the controller, terminal manager, and
  the worker-thread boundary into the existing size-aware createTerminal.

Tests: cache logic, protocol back-compat parse, controller forwarding.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 03:26:52 +08:00

155 lines
3.4 KiB
TypeScript

import type {
TerminalExitInfo,
ServerMessage,
ClientMessage,
TerminalStateSnapshot,
TerminalStateSnapshotOptions,
} from "./terminal.js";
import type { TerminalState } from "@getpaseo/protocol/messages";
import type { TerminalActivity, TerminalActivityState } from "@getpaseo/protocol/terminal-activity";
import type { CaptureTerminalLinesResult } from "./terminal-capture.js";
export interface WorkerTerminalInfo {
id: string;
name: string;
cwd: string;
workspaceId?: string;
title?: string;
activity: TerminalActivity | null;
}
export interface WorkerCreateTerminalOptions {
id?: string;
cwd: string;
workspaceId?: string;
name?: string;
title?: string;
env?: Record<string, string>;
command?: string;
args?: string[];
rows?: number;
cols?: number;
activityToken?: string;
activityUrl?: string | null;
}
export interface WorkerKillAndWaitOptions {
gracefulTimeoutMs?: number;
forceTimeoutMs?: number;
}
export type TerminalWorkerRequest =
| {
type: "createTerminal";
requestId: string;
options: WorkerCreateTerminalOptions;
}
| {
type: "registerCwdEnv";
requestId: string;
cwd: string;
env: Record<string, string>;
}
| {
type: "setActivity";
requestId: string;
terminalId: string;
state: TerminalActivityState;
}
| {
type: "clearAttention";
requestId: string;
terminalId: string;
}
| {
type: "killTerminal";
requestId: string;
terminalId: string;
}
| {
type: "killTerminalAndWait";
requestId: string;
terminalId: string;
options?: WorkerKillAndWaitOptions;
}
| {
type: "getTerminalState";
requestId: string;
terminalId: string;
options?: TerminalStateSnapshotOptions;
}
| {
type: "captureTerminal";
requestId: string;
terminalId: string;
start?: number;
end?: number;
stripAnsi?: boolean;
}
| {
type: "killAll";
requestId: string;
}
| {
type: "send";
requestId: string;
terminalId: string;
message: ClientMessage;
};
export type TerminalWorkerResponse =
| {
type: "response";
requestId: string;
ok: true;
result?: unknown;
}
| {
type: "response";
requestId: string;
ok: false;
error: string;
};
export type TerminalWorkerEvent =
| {
type: "terminalCreated";
terminal: WorkerTerminalInfo;
state: TerminalState;
}
| {
type: "terminalMessage";
terminalId: string;
message: ServerMessage;
}
| {
type: "terminalExit";
terminalId: string;
info: TerminalExitInfo;
}
| {
type: "terminalTitleChange";
terminalId: string;
title?: string;
}
| {
type: "terminalCommandFinished";
terminalId: string;
info: {
exitCode: number | null;
};
}
| {
type: "terminalActivityChange";
terminalId: string;
activity: TerminalActivity | null;
previous: TerminalActivity | null;
};
export type TerminalWorkerToParentMessage = TerminalWorkerResponse | TerminalWorkerEvent;
export type TerminalWorkerCaptureResult = CaptureTerminalLinesResult;
// The worker fills TerminalStateSnapshot.replayPreamble on getTerminalState so
// the parent can cache the input-mode preamble instead of re-deriving it.
export type TerminalWorkerStateResult = TerminalStateSnapshot | null;