Skip to content

Commit cfbaf81

Browse files
committed
fix(desktop): clone pty session on reconnect
1 parent 87a791f commit cfbaf81

4 files changed

Lines changed: 36 additions & 30 deletions

File tree

packages/desktop/src/components/terminal.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { init, Terminal as Term, FitAddon } from "ghostty-web"
22
import { ComponentProps, onCleanup, onMount, splitProps } from "solid-js"
3-
import { createReconnectingWS, ReconnectingWebSocket } from "@solid-primitives/websocket"
43
import { useSDK } from "@/context/sdk"
54
import { SerializeAddon } from "@/addons/serialize"
65
import { LocalPTY } from "@/context/session"
@@ -11,19 +10,20 @@ export interface TerminalProps extends ComponentProps<"div"> {
1110
pty: LocalPTY
1211
onSubmit?: () => void
1312
onCleanup?: (pty: LocalPTY) => void
13+
onConnectError?: (error: unknown) => void
1414
}
1515

1616
export const Terminal = (props: TerminalProps) => {
1717
const sdk = useSDK()
1818
let container!: HTMLDivElement
19-
const [local, others] = splitProps(props, ["pty", "class", "classList"])
20-
let ws: ReconnectingWebSocket
19+
const [local, others] = splitProps(props, ["pty", "class", "classList", "onConnectError"])
20+
let ws: WebSocket
2121
let term: Term
2222
let serializeAddon: SerializeAddon
2323
let fitAddon: FitAddon
2424

2525
onMount(async () => {
26-
ws = createReconnectingWS(sdk.url + `/pty/${local.pty.id}/connect?directory=${encodeURIComponent(sdk.directory)}`)
26+
ws = new WebSocket(sdk.url + `/pty/${local.pty.id}/connect?directory=${encodeURIComponent(sdk.directory)}`)
2727
term = new Term({
2828
cursorBlink: true,
2929
fontSize: 14,
@@ -115,6 +115,7 @@ export const Terminal = (props: TerminalProps) => {
115115
})
116116
ws.addEventListener("error", (error) => {
117117
console.error("WebSocket error:", error)
118+
props.onConnectError?.(error)
118119
})
119120
ws.addEventListener("close", () => {
120121
console.log("WebSocket disconnected")

packages/desktop/src/context/session.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export const { use: useSession, provider: SessionProvider } = createSimpleContex
2626
const params = useParams()
2727
const sync = useSync()
2828
const name = createMemo(
29-
() => `______${base64Encode(sync.data.project.worktree)}/session${params.id ? "/" + params.id : ""}`,
29+
() => `${base64Encode(sync.data.project.worktree)}/session${params.id ? "/" + params.id : ""}.v1`,
3030
)
3131

3232
const [store, setStore] = makePersisted(
@@ -232,6 +232,9 @@ export const { use: useSession, provider: SessionProvider } = createSimpleContex
232232
...pty,
233233
...clone.data,
234234
})
235+
if (store.terminals.active === pty.id) {
236+
setStore("terminals", "active", clone.data.id)
237+
}
235238
},
236239
open(id: string) {
237240
setStore("terminals", "active", id)

packages/desktop/src/pages/session.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,10 @@ export default function Page() {
8484
}
8585
if (event.ctrlKey && event.key.toLowerCase() === "`") {
8686
event.preventDefault()
87+
if (event.shiftKey) {
88+
session.terminal.new()
89+
return
90+
}
8791
layout.terminal.toggle()
8892
return
8993
}
@@ -663,7 +667,11 @@ export default function Page() {
663667
<For each={session.terminal.all()}>
664668
{(terminal) => (
665669
<Tabs.Content value={terminal.id}>
666-
<Terminal pty={terminal} onCleanup={session.terminal.update} />
670+
<Terminal
671+
pty={terminal}
672+
onCleanup={session.terminal.update}
673+
onConnectError={() => session.terminal.clone(terminal.id)}
674+
/>
667675
</Tabs.Content>
668676
)}
669677
</For>

packages/opencode/src/server/server.ts

Lines changed: 18 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -208,53 +208,53 @@ export namespace Server {
208208
return c.json(info)
209209
},
210210
)
211-
.put(
211+
.get(
212212
"/pty/:id",
213213
describeRoute({
214-
description: "Update PTY session",
215-
operationId: "pty.update",
214+
description: "Get PTY session info",
215+
operationId: "pty.get",
216216
responses: {
217217
200: {
218-
description: "Updated session",
218+
description: "Session info",
219219
content: {
220220
"application/json": {
221221
schema: resolver(Pty.Info),
222222
},
223223
},
224224
},
225-
...errors(400),
225+
...errors(404),
226226
},
227227
}),
228228
validator("param", z.object({ id: z.string() })),
229-
validator("json", Pty.UpdateInput),
230229
async (c) => {
231-
const info = await Pty.update(c.req.valid("param").id, c.req.valid("json"))
230+
const info = Pty.get(c.req.valid("param").id)
231+
if (!info) {
232+
throw new Storage.NotFoundError({ message: "Session not found" })
233+
}
232234
return c.json(info)
233235
},
234236
)
235-
.get(
237+
.put(
236238
"/pty/:id",
237239
describeRoute({
238-
description: "Get PTY session info",
239-
operationId: "pty.get",
240+
description: "Update PTY session",
241+
operationId: "pty.update",
240242
responses: {
241243
200: {
242-
description: "Session info",
244+
description: "Updated session",
243245
content: {
244246
"application/json": {
245247
schema: resolver(Pty.Info),
246248
},
247249
},
248250
},
249-
...errors(404),
251+
...errors(400),
250252
},
251253
}),
252254
validator("param", z.object({ id: z.string() })),
255+
validator("json", Pty.UpdateInput),
253256
async (c) => {
254-
const info = Pty.get(c.req.valid("param").id)
255-
if (!info) {
256-
throw new Storage.NotFoundError({ message: "Session not found" })
257-
}
257+
const info = await Pty.update(c.req.valid("param").id, c.req.valid("json"))
258258
return c.json(info)
259259
},
260260
)
@@ -295,20 +295,14 @@ export namespace Server {
295295
},
296296
},
297297
},
298-
404: {
299-
description: "Session not found",
300-
content: {
301-
"application/json": {
302-
schema: resolver(z.boolean()),
303-
},
304-
},
305-
},
298+
...errors(404),
306299
},
307300
}),
308301
validator("param", z.object({ id: z.string() })),
309302
upgradeWebSocket((c) => {
310303
const id = c.req.param("id")
311304
let handler: ReturnType<typeof Pty.connect>
305+
if (!Pty.get(id)) throw new Error("Session not found")
312306
return {
313307
onOpen(_event, ws) {
314308
handler = Pty.connect(id, ws)

0 commit comments

Comments
 (0)