fix: wire terminal events through V2 listener [LET-7999] (#1430)

Co-authored-by: Letta Code <noreply@letta.com>
This commit is contained in:
Shubham Naik
2026-03-18 12:03:43 -07:00
committed by GitHub
parent 16d060a7fa
commit 87eff23b81
3 changed files with 117 additions and 3 deletions

View File

@@ -357,11 +357,40 @@ export interface SyncCommand {
runtime: RuntimeScope; runtime: RuntimeScope;
} }
export interface TerminalSpawnCommand {
type: "terminal_spawn";
terminal_id: string;
cols: number;
rows: number;
}
export interface TerminalInputCommand {
type: "terminal_input";
terminal_id: string;
data: string;
}
export interface TerminalResizeCommand {
type: "terminal_resize";
terminal_id: string;
cols: number;
rows: number;
}
export interface TerminalKillCommand {
type: "terminal_kill";
terminal_id: string;
}
export type WsProtocolCommand = export type WsProtocolCommand =
| InputCommand | InputCommand
| ChangeDeviceStateCommand | ChangeDeviceStateCommand
| AbortMessageCommand | AbortMessageCommand
| SyncCommand; | SyncCommand
| TerminalSpawnCommand
| TerminalInputCommand
| TerminalResizeCommand
| TerminalKillCommand;
export type WsProtocolMessage = export type WsProtocolMessage =
| DeviceStatusUpdateMessage | DeviceStatusUpdateMessage

View File

@@ -17,7 +17,13 @@ import { settingsManager } from "../../settings-manager";
import { loadTools } from "../../tools/manager"; import { loadTools } from "../../tools/manager";
import type { ApprovalResponseBody } from "../../types/protocol_v2"; import type { ApprovalResponseBody } from "../../types/protocol_v2";
import { isDebugEnabled } from "../../utils/debug"; import { isDebugEnabled } from "../../utils/debug";
import { killAllTerminals } from "../terminalHandler"; import {
handleTerminalInput,
handleTerminalKill,
handleTerminalResize,
handleTerminalSpawn,
killAllTerminals,
} from "../terminalHandler";
import { import {
clearPendingApprovalBatchIds, clearPendingApprovalBatchIds,
rejectPendingApprovalResolvers, rejectPendingApprovalResolvers,
@@ -917,6 +923,27 @@ async function connectWithRetry(
scheduleQueuePump(scopedRuntime, socket, opts, processQueuedTurn); scheduleQueuePump(scopedRuntime, socket, opts, processQueuedTurn);
return; return;
} }
// ── Terminal commands (no runtime scope required) ──────────────────
if (parsed.type === "terminal_spawn") {
handleTerminalSpawn(parsed, socket, runtime.bootWorkingDirectory);
return;
}
if (parsed.type === "terminal_input") {
handleTerminalInput(parsed);
return;
}
if (parsed.type === "terminal_resize") {
handleTerminalResize(parsed);
return;
}
if (parsed.type === "terminal_kill") {
handleTerminalKill(parsed);
return;
}
}); });
socket.on("close", (code: number, reason: Buffer) => { socket.on("close", (code: number, reason: Buffer) => {

View File

@@ -5,6 +5,10 @@ import type {
InputCommand, InputCommand,
RuntimeScope, RuntimeScope,
SyncCommand, SyncCommand,
TerminalInputCommand,
TerminalKillCommand,
TerminalResizeCommand,
TerminalSpawnCommand,
WsProtocolCommand, WsProtocolCommand,
} from "../../types/protocol_v2"; } from "../../types/protocol_v2";
import { isValidApprovalResponseBody } from "./approval"; import { isValidApprovalResponseBody } from "./approval";
@@ -186,6 +190,56 @@ function isSyncCommand(value: unknown): value is SyncCommand {
return candidate.type === "sync" && isRuntimeScope(candidate.runtime); return candidate.type === "sync" && isRuntimeScope(candidate.runtime);
} }
function isTerminalSpawnCommand(value: unknown): value is TerminalSpawnCommand {
if (!value || typeof value !== "object") return false;
const c = value as {
type?: unknown;
terminal_id?: unknown;
cols?: unknown;
rows?: unknown;
};
return (
c.type === "terminal_spawn" &&
typeof c.terminal_id === "string" &&
typeof c.cols === "number" &&
typeof c.rows === "number"
);
}
function isTerminalInputCommand(value: unknown): value is TerminalInputCommand {
if (!value || typeof value !== "object") return false;
const c = value as { type?: unknown; terminal_id?: unknown; data?: unknown };
return (
c.type === "terminal_input" &&
typeof c.terminal_id === "string" &&
typeof c.data === "string"
);
}
function isTerminalResizeCommand(
value: unknown,
): value is TerminalResizeCommand {
if (!value || typeof value !== "object") return false;
const c = value as {
type?: unknown;
terminal_id?: unknown;
cols?: unknown;
rows?: unknown;
};
return (
c.type === "terminal_resize" &&
typeof c.terminal_id === "string" &&
typeof c.cols === "number" &&
typeof c.rows === "number"
);
}
function isTerminalKillCommand(value: unknown): value is TerminalKillCommand {
if (!value || typeof value !== "object") return false;
const c = value as { type?: unknown; terminal_id?: unknown };
return c.type === "terminal_kill" && typeof c.terminal_id === "string";
}
export function parseServerMessage( export function parseServerMessage(
data: WebSocket.RawData, data: WebSocket.RawData,
): ParsedServerMessage | null { ): ParsedServerMessage | null {
@@ -196,7 +250,11 @@ export function parseServerMessage(
isInputCommand(parsed) || isInputCommand(parsed) ||
isChangeDeviceStateCommand(parsed) || isChangeDeviceStateCommand(parsed) ||
isAbortMessageCommand(parsed) || isAbortMessageCommand(parsed) ||
isSyncCommand(parsed) isSyncCommand(parsed) ||
isTerminalSpawnCommand(parsed) ||
isTerminalInputCommand(parsed) ||
isTerminalResizeCommand(parsed) ||
isTerminalKillCommand(parsed)
) { ) {
return parsed as WsProtocolCommand; return parsed as WsProtocolCommand;
} }