From 5b07dfe323f76da03cba1328f521f159ccde9db6 Mon Sep 17 00:00:00 2001 From: vigdorov Date: Sun, 22 Mar 2026 01:41:47 +0300 Subject: [PATCH] feat: add ensure_pty for deferred PTY creation with client dimensions New ensure_pty message: creates PTY if not running, or resizes and sends replay buffer if already active. This allows the frontend to request PTY creation with actual terminal dimensions after xterm.js FitAddon calculates the correct cols/rows for the container. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/connection/protocol.ts | 2 ++ src/main.ts | 32 +++++++++++++++++++++----------- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/src/connection/protocol.ts b/src/connection/protocol.ts index 718a789..b753602 100644 --- a/src/connection/protocol.ts +++ b/src/connection/protocol.ts @@ -29,6 +29,8 @@ export type BackendMessageType = | 'create_pty' | 'kill_pty' | 'resize' + | 'ensure_pty' + | 'request_replay' | 'list_directories' | 'list_sessions'; diff --git a/src/main.ts b/src/main.ts index 007ac36..514c4bd 100644 --- a/src/main.ts +++ b/src/main.ts @@ -81,13 +81,16 @@ class ClaudeCliProxy { case 'create_pty': this.handleCreatePty(msg.payload as CreatePtyPayload); break; + case 'ensure_pty': + this.handleEnsurePty(msg.payload as CreatePtyPayload); + break; case 'kill_pty': this.handleKillPty(msg.payload as KillPtyPayload); break; case 'resize': this.handleResize(msg.payload as ResizePayload); break; - case 'request_replay' as any: { + case 'request_replay': { const { chatId } = msg.payload as { chatId: string }; const replay = this.pty.getReplayBuffer(chatId); if (replay) { @@ -116,23 +119,15 @@ class ClaudeCliProxy { const activePtys = new Set(this.pty.listPtys()); const chatIds = new Set(payload.chats.map((c) => c.id)); + // Send replay + pty_ready for already running PTYs + // PTYs for new chats will be created via ensure_pty from frontend for (const chat of payload.chats) { if (activePtys.has(chat.id)) { - // PTY exists — send replay + ready const replay = this.pty.getReplayBuffer(chat.id); if (replay) { this.ws.sendBinary(encodeBinaryFrame(DIR_PTY_OUTPUT, chat.id, replay)); } this.ws.send({ type: 'pty_ready', payload: { chatId: chat.id } }); - } else if (chat.workDir) { - // PTY not running — create it - this.handleCreatePty({ - chatId: chat.id, - dir: chat.workDir, - resumeSessionId: chat.sessionId ?? undefined, - cols: chat.cols ?? 120, - rows: chat.rows ?? 40, - }); } } @@ -166,6 +161,21 @@ class ClaudeCliProxy { this.ws.send({ type: 'pty_ready', payload: { chatId: payload.chatId } }); } + private handleEnsurePty(payload: CreatePtyPayload): void { + if (this.pty.hasPty(payload.chatId)) { + // PTY already exists — resize to match client, send replay + ready + this.pty.resizePty(payload.chatId, payload.cols, payload.rows); + const replay = this.pty.getReplayBuffer(payload.chatId); + if (replay) { + this.ws.sendBinary(encodeBinaryFrame(DIR_PTY_OUTPUT, payload.chatId, replay)); + } + this.ws.send({ type: 'pty_ready', payload: { chatId: payload.chatId } }); + } else { + // PTY doesn't exist — create with client's dimensions + this.handleCreatePty(payload); + } + } + private handleKillPty(payload: KillPtyPayload): void { this.pty.killPty(payload.chatId); this.ws.send({ type: 'pty_closed', payload: { chatId: payload.chatId, exitCode: 0 } });