diff --git a/ui/src/components/terminal/terminal-connection.test.ts b/ui/src/components/terminal/terminal-connection.test.ts index 5092dbf9708f..708ff60b6153 100644 --- a/ui/src/components/terminal/terminal-connection.test.ts +++ b/ui/src/components/terminal/terminal-connection.test.ts @@ -545,6 +545,29 @@ describe("TerminalConnection", () => { ]); }); + it.each([ + ["terminal.input", (conn: TerminalConnection) => conn.input("s1", "echo lost\n")], + ["terminal.resize", (conn: TerminalConnection) => conn.resize("s1", 120, 40)], + ] as const)("marks the session unavailable when %s rejects its live owner", async (_, act) => { + const { client, conn } = makeHarness(); + const exits: unknown[] = []; + await openSession(conn, { onExit: (info) => exits.push(info) }); + client.nextResponse = { ok: false }; + + await act(conn); + + expect(exits).toEqual([ + { + exitCode: null, + signal: null, + reason: "disconnected", + error: "Terminal session is no longer available. Open a new terminal session.", + }, + ]); + expect(conn.size).toBe(0); + expect(client.listenerCount()).toBe(0); + }); + it("buffers output that races ahead of sink registration and replays it in order", async () => { const { client, conn } = makeHarness(); const data: string[] = []; diff --git a/ui/src/components/terminal/terminal-connection.ts b/ui/src/components/terminal/terminal-connection.ts index fb5d4b3eb00a..32767a48d54c 100644 --- a/ui/src/components/terminal/terminal-connection.ts +++ b/ui/src/components/terminal/terminal-connection.ts @@ -612,11 +612,25 @@ export class TerminalConnection { } async input(sessionId: string, data: string): Promise { - await this.client.request("terminal.input", { sessionId, data }).catch(() => undefined); + await this.requestAction("terminal.input", sessionId, { sessionId, data }); } async resize(sessionId: string, cols: number, rows: number): Promise { - await this.client.request("terminal.resize", { sessionId, cols, rows }).catch(() => undefined); + await this.requestAction("terminal.resize", sessionId, { sessionId, cols, rows }); + } + + private async requestAction(method: string, sessionId: string, params: unknown): Promise { + const stream = this.streams.get(sessionId); + const result = await this.client.request<{ ok: boolean }>(method, params).catch(() => null); + if (result?.ok !== false || !stream || this.streams.get(sessionId) !== stream) { + return; + } + this.deliverExit(sessionId, stream, { + exitCode: null, + signal: null, + reason: "disconnected", + error: "Terminal session is no longer available. Open a new terminal session.", + }); } /** Closes a session server-side and drops its local stream state. */