diff options
| author | Adam <[email protected]> | 2026-02-22 06:17:59 -0600 |
|---|---|---|
| committer | Adam <[email protected]> | 2026-02-22 06:17:59 -0600 |
| commit | e70d2b27de3aaed5a19b9ca2c6749ed7fce3ef93 (patch) | |
| tree | c06e2f661454b3ab1397a9cdae42f3affa29744d | |
| parent | b16f7b426c5be6feea419dca87aad2032f25462b (diff) | |
| download | opencode-e70d2b27de3aaed5a19b9ca2c6749ed7fce3ef93.tar.gz opencode-e70d2b27de3aaed5a19b9ca2c6749ed7fce3ef93.zip | |
fix(app): terminal issues
| -rw-r--r-- | packages/opencode/src/pty/index.ts | 9 | ||||
| -rw-r--r-- | packages/opencode/src/server/routes/pty.ts | 2 | ||||
| -rw-r--r-- | packages/opencode/test/pty/pty-output-isolation.test.ts | 48 |
3 files changed, 54 insertions, 5 deletions
diff --git a/packages/opencode/src/pty/index.ts b/packages/opencode/src/pty/index.ts index 33083485b..fdb46c817 100644 --- a/packages/opencode/src/pty/index.ts +++ b/packages/opencode/src/pty/index.ts @@ -39,8 +39,9 @@ export namespace Pty { return next } - const token = (ws: Socket) => { - const data = ws.data + const token = (ws: unknown) => { + if (!ws || typeof ws !== "object") return ws + const data = (ws as { data?: unknown }).data if (data === undefined) return if (data === null) return if (typeof data !== "object") return data @@ -317,7 +318,7 @@ export namespace Pty { } } - export function connect(id: string, ws: Socket, cursor?: number) { + export function connect(id: string, ws: Socket, cursor?: number, identity?: unknown) { const session = state().get(id) if (!session) { ws.close() @@ -337,7 +338,7 @@ export namespace Pty { } owners.set(ws, id) - session.subscribers.set(ws, { id: socketId, token: token(ws) }) + session.subscribers.set(ws, { id: socketId, token: token(identity ?? ws) }) const cleanup = () => { session.subscribers.delete(ws) diff --git a/packages/opencode/src/server/routes/pty.ts b/packages/opencode/src/server/routes/pty.ts index 368c9612b..640cfa333 100644 --- a/packages/opencode/src/server/routes/pty.ts +++ b/packages/opencode/src/server/routes/pty.ts @@ -182,7 +182,7 @@ export const PtyRoutes = lazy(() => ws.close() return } - handler = Pty.connect(id, socket, cursor) + handler = Pty.connect(id, socket, cursor, ws) }, onMessage(event) { if (typeof event.data !== "string") return diff --git a/packages/opencode/test/pty/pty-output-isolation.test.ts b/packages/opencode/test/pty/pty-output-isolation.test.ts index 07e86ea97..2c9cc5d92 100644 --- a/packages/opencode/test/pty/pty-output-isolation.test.ts +++ b/packages/opencode/test/pty/pty-output-isolation.test.ts @@ -98,6 +98,54 @@ describe("pty", () => { }) }) + test("does not leak when identity token is only on websocket wrapper", async () => { + await using dir = await tmpdir({ git: true }) + + await Instance.provide({ + directory: dir.path, + fn: async () => { + const a = await Pty.create({ command: "cat", title: "a" }) + try { + const outA: string[] = [] + const outB: string[] = [] + const text = (data: string | Uint8Array | ArrayBuffer) => { + if (typeof data === "string") return data + if (data instanceof ArrayBuffer) return Buffer.from(new Uint8Array(data)).toString("utf8") + return Buffer.from(data).toString("utf8") + } + + const raw: Parameters<typeof Pty.connect>[1] = { + readyState: 1, + send: (data) => { + outA.push(text(data)) + }, + close: () => { + // no-op + }, + } + + const wrap = { data: { events: { connection: "a" } } } + + Pty.connect(a.id, raw, undefined, wrap) + outA.length = 0 + + // Simulate Bun reusing the raw socket object before the next onOpen, + // while the connection token only exists on the wrapper socket. + raw.send = (data) => { + outB.push(text(data)) + } + + Pty.write(a.id, "AAA\n") + await Bun.sleep(100) + + expect(outB.join("")).not.toContain("AAA") + } finally { + await Pty.remove(a.id) + } + }, + }) + }) + test("does not leak output when socket data mutates in-place", async () => { await using dir = await tmpdir({ git: true }) |
