fix(ui): surface rejected terminal actions (#124046)

Mark stale terminal tabs exited when the Gateway rejects input or resize, so operators see an actionable outcome instead of silently losing keystrokes.
This commit is contained in:
Peter Steinberger
2026-08-15 19:53:47 -07:00
committed by GitHub
parent 4b8ca98a94
commit e68f3d8ad1
2 changed files with 39 additions and 2 deletions
@@ -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[] = [];
@@ -612,11 +612,25 @@ export class TerminalConnection {
}
async input(sessionId: string, data: string): Promise<void> {
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<void> {
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<void> {
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. */