From 8c484a05b894be0a88c130badb2353d02d1deb23 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Thu, 26 Feb 2026 15:50:15 -0600 Subject: [PATCH] fix(app): terminal issues --- packages/opencode/src/pty/index.ts | 104 ++++++----------------------- 1 file changed, 19 insertions(+), 85 deletions(-) diff --git a/packages/opencode/src/pty/index.ts b/packages/opencode/src/pty/index.ts index 33083485b..dee3fbc54 100644 --- a/packages/opencode/src/pty/index.ts +++ b/packages/opencode/src/pty/index.ts @@ -23,60 +23,6 @@ export namespace Pty { close: (code?: number, reason?: string) => void } - type Subscriber = { - id: number - token: unknown - } - - const sockets = new WeakMap() - const owners = new WeakMap() - let socketCounter = 0 - - const tagSocket = (ws: Socket) => { - if (!ws || typeof ws !== "object") return - const next = (socketCounter = (socketCounter + 1) % Number.MAX_SAFE_INTEGER) - sockets.set(ws, next) - return next - } - - const token = (ws: Socket) => { - const data = ws.data - if (data === undefined) return - if (data === null) return - if (typeof data !== "object") return data - - const id = (data as { connId?: unknown }).connId - if (typeof id === "number" || typeof id === "string") return id - - const href = (data as { href?: unknown }).href - if (typeof href === "string") return href - - const url = (data as { url?: unknown }).url - if (typeof url === "string") return url - if (url && typeof url === "object") { - const href = (url as { href?: unknown }).href - if (typeof href === "string") return href - return url - } - - const events = (data as { events?: unknown }).events - if (typeof events === "number" || typeof events === "string") return events - if (events && typeof events === "object") { - const id = (events as { connId?: unknown }).connId - if (typeof id === "number" || typeof id === "string") return id - - const id2 = (events as { connection?: unknown }).connection - if (typeof id2 === "number" || typeof id2 === "string") return id2 - - const id3 = (events as { id?: unknown }).id - if (typeof id3 === "number" || typeof id3 === "string") return id3 - - return events - } - - return data - } - // WebSocket control frame: 0x00 + UTF-8 JSON. const meta = (cursor: number) => { const json = JSON.stringify({ cursor }) @@ -141,7 +87,7 @@ export namespace Pty { buffer: string bufferCursor: number cursor: number - subscribers: Map + subscribers: Map } const state = Instance.state( @@ -151,9 +97,9 @@ export namespace Pty { try { session.process.kill() } catch {} - for (const ws of session.subscribers.keys()) { + for (const [key, ws] of session.subscribers.entries()) { try { - ws.close() + if (ws.data === key) ws.close() } catch { // ignore } @@ -224,26 +170,21 @@ export namespace Pty { ptyProcess.onData((chunk) => { session.cursor += chunk.length - for (const [ws, sub] of session.subscribers) { + for (const [key, ws] of session.subscribers.entries()) { if (ws.readyState !== 1) { - session.subscribers.delete(ws) + session.subscribers.delete(key) continue } - if (typeof ws === "object" && sockets.get(ws) !== sub.id) { - session.subscribers.delete(ws) - continue - } - - if (token(ws) !== sub.token) { - session.subscribers.delete(ws) + if (ws.data !== key) { + session.subscribers.delete(key) continue } try { ws.send(chunk) } catch { - session.subscribers.delete(ws) + session.subscribers.delete(key) } } @@ -256,9 +197,9 @@ export namespace Pty { ptyProcess.onExit(({ exitCode }) => { log.info("session exited", { id, exitCode }) session.info.status = "exited" - for (const ws of session.subscribers.keys()) { + for (const [key, ws] of session.subscribers.entries()) { try { - ws.close() + if (ws.data === key) ws.close() } catch { // ignore } @@ -291,9 +232,9 @@ export namespace Pty { try { session.process.kill() } catch {} - for (const ws of session.subscribers.keys()) { + for (const [key, ws] of session.subscribers.entries()) { try { - ws.close() + if (ws.data === key) ws.close() } catch { // ignore } @@ -325,23 +266,16 @@ export namespace Pty { } log.info("client connected to session", { id }) - const socketId = tagSocket(ws) - if (socketId === undefined) { - ws.close() - return - } + // Use ws.data as the unique key for this connection lifecycle. + // If ws.data is undefined, fallback to ws object. + const connectionKey = ws.data && typeof ws.data === "object" ? ws.data : ws - const previous = owners.get(ws) - if (previous && previous !== id) { - state().get(previous)?.subscribers.delete(ws) - } - - owners.set(ws, id) - session.subscribers.set(ws, { id: socketId, token: token(ws) }) + // Optionally cleanup if the key somehow exists + session.subscribers.delete(connectionKey) + session.subscribers.set(connectionKey, ws) const cleanup = () => { - session.subscribers.delete(ws) - if (owners.get(ws) === id) owners.delete(ws) + session.subscribers.delete(connectionKey) } const start = session.bufferCursor