Преглед изворни кода

fix(desktop): clone pty session on reconnect

Adam пре 2 месеци
родитељ
комит
cfbaf81ef8

+ 5 - 4
packages/desktop/src/components/terminal.tsx

@@ -1,6 +1,5 @@
 import { init, Terminal as Term, FitAddon } from "ghostty-web"
 import { ComponentProps, onCleanup, onMount, splitProps } from "solid-js"
-import { createReconnectingWS, ReconnectingWebSocket } from "@solid-primitives/websocket"
 import { useSDK } from "@/context/sdk"
 import { SerializeAddon } from "@/addons/serialize"
 import { LocalPTY } from "@/context/session"
@@ -11,19 +10,20 @@ export interface TerminalProps extends ComponentProps<"div"> {
   pty: LocalPTY
   onSubmit?: () => void
   onCleanup?: (pty: LocalPTY) => void
+  onConnectError?: (error: unknown) => void
 }
 
 export const Terminal = (props: TerminalProps) => {
   const sdk = useSDK()
   let container!: HTMLDivElement
-  const [local, others] = splitProps(props, ["pty", "class", "classList"])
-  let ws: ReconnectingWebSocket
+  const [local, others] = splitProps(props, ["pty", "class", "classList", "onConnectError"])
+  let ws: WebSocket
   let term: Term
   let serializeAddon: SerializeAddon
   let fitAddon: FitAddon
 
   onMount(async () => {
-    ws = createReconnectingWS(sdk.url + `/pty/${local.pty.id}/connect?directory=${encodeURIComponent(sdk.directory)}`)
+    ws = new WebSocket(sdk.url + `/pty/${local.pty.id}/connect?directory=${encodeURIComponent(sdk.directory)}`)
     term = new Term({
       cursorBlink: true,
       fontSize: 14,
@@ -115,6 +115,7 @@ export const Terminal = (props: TerminalProps) => {
     })
     ws.addEventListener("error", (error) => {
       console.error("WebSocket error:", error)
+      props.onConnectError?.(error)
     })
     ws.addEventListener("close", () => {
       console.log("WebSocket disconnected")

+ 4 - 1
packages/desktop/src/context/session.tsx

@@ -26,7 +26,7 @@ export const { use: useSession, provider: SessionProvider } = createSimpleContex
     const params = useParams()
     const sync = useSync()
     const name = createMemo(
-      () => `______${base64Encode(sync.data.project.worktree)}/session${params.id ? "/" + params.id : ""}`,
+      () => `${base64Encode(sync.data.project.worktree)}/session${params.id ? "/" + params.id : ""}.v1`,
     )
 
     const [store, setStore] = makePersisted(
@@ -232,6 +232,9 @@ export const { use: useSession, provider: SessionProvider } = createSimpleContex
             ...pty,
             ...clone.data,
           })
+          if (store.terminals.active === pty.id) {
+            setStore("terminals", "active", clone.data.id)
+          }
         },
         open(id: string) {
           setStore("terminals", "active", id)

+ 9 - 1
packages/desktop/src/pages/session.tsx

@@ -84,6 +84,10 @@ export default function Page() {
     }
     if (event.ctrlKey && event.key.toLowerCase() === "`") {
       event.preventDefault()
+      if (event.shiftKey) {
+        session.terminal.new()
+        return
+      }
       layout.terminal.toggle()
       return
     }
@@ -663,7 +667,11 @@ export default function Page() {
             <For each={session.terminal.all()}>
               {(terminal) => (
                 <Tabs.Content value={terminal.id}>
-                  <Terminal pty={terminal} onCleanup={session.terminal.update} />
+                  <Terminal
+                    pty={terminal}
+                    onCleanup={session.terminal.update}
+                    onConnectError={() => session.terminal.clone(terminal.id)}
+                  />
                 </Tabs.Content>
               )}
             </For>

+ 18 - 24
packages/opencode/src/server/server.ts

@@ -208,53 +208,53 @@ export namespace Server {
           return c.json(info)
         },
       )
-      .put(
+      .get(
         "/pty/:id",
         describeRoute({
-          description: "Update PTY session",
-          operationId: "pty.update",
+          description: "Get PTY session info",
+          operationId: "pty.get",
           responses: {
             200: {
-              description: "Updated session",
+              description: "Session info",
               content: {
                 "application/json": {
                   schema: resolver(Pty.Info),
                 },
               },
             },
-            ...errors(400),
+            ...errors(404),
           },
         }),
         validator("param", z.object({ id: z.string() })),
-        validator("json", Pty.UpdateInput),
         async (c) => {
-          const info = await Pty.update(c.req.valid("param").id, c.req.valid("json"))
+          const info = Pty.get(c.req.valid("param").id)
+          if (!info) {
+            throw new Storage.NotFoundError({ message: "Session not found" })
+          }
           return c.json(info)
         },
       )
-      .get(
+      .put(
         "/pty/:id",
         describeRoute({
-          description: "Get PTY session info",
-          operationId: "pty.get",
+          description: "Update PTY session",
+          operationId: "pty.update",
           responses: {
             200: {
-              description: "Session info",
+              description: "Updated session",
               content: {
                 "application/json": {
                   schema: resolver(Pty.Info),
                 },
               },
             },
-            ...errors(404),
+            ...errors(400),
           },
         }),
         validator("param", z.object({ id: z.string() })),
+        validator("json", Pty.UpdateInput),
         async (c) => {
-          const info = Pty.get(c.req.valid("param").id)
-          if (!info) {
-            throw new Storage.NotFoundError({ message: "Session not found" })
-          }
+          const info = await Pty.update(c.req.valid("param").id, c.req.valid("json"))
           return c.json(info)
         },
       )
@@ -295,20 +295,14 @@ export namespace Server {
                 },
               },
             },
-            404: {
-              description: "Session not found",
-              content: {
-                "application/json": {
-                  schema: resolver(z.boolean()),
-                },
-              },
-            },
+            ...errors(404),
           },
         }),
         validator("param", z.object({ id: z.string() })),
         upgradeWebSocket((c) => {
           const id = c.req.param("id")
           let handler: ReturnType<typeof Pty.connect>
+          if (!Pty.get(id)) throw new Error("Session not found")
           return {
             onOpen(_event, ws) {
               handler = Pty.connect(id, ws)