From bd7eb0603f920bd9a6ba585827ed479bd1263353 Mon Sep 17 00:00:00 2001 From: Luke Parker <10430890+Hona@users.noreply.github.com> Date: Sat, 6 Jun 2026 14:38:06 +1000 Subject: [PATCH] feat: desktop v2 everything WSL (#23407) --- packages/app/package.json | 1 + packages/app/src/app.tsx | 14 +- .../app/src/components/dialog-select-file.tsx | 10 +- packages/app/src/components/prompt-input.tsx | 21 +- .../prompt-input/server-attachment.test.ts | 25 + .../prompt-input/server-attachment.ts | 7 + .../src/components/settings-v2/servers.tsx | 10 +- .../src/components/status-popover-body.tsx | 17 +- packages/app/src/context/platform.tsx | 8 +- packages/app/src/context/server.test.ts | 42 +- packages/app/src/context/server.tsx | 25 +- packages/app/src/i18n/en.ts | 53 ++ packages/app/src/index.ts | 22 +- packages/app/src/pages/home.tsx | 11 +- packages/app/src/wsl/context.tsx | 36 + packages/app/src/wsl/dialog-add-server.tsx | 623 ++++++++++++++++++ packages/app/src/wsl/settings-model.test.ts | 57 ++ packages/app/src/wsl/settings-model.ts | 19 + packages/app/src/wsl/settings.tsx | 167 +++++ packages/app/src/wsl/types.ts | 87 +++ packages/desktop/src/main/apps.ts | 21 +- packages/desktop/src/main/constants.ts | 2 +- packages/desktop/src/main/index.ts | 66 +- packages/desktop/src/main/ipc.ts | 16 +- packages/desktop/src/main/server.ts | 13 +- packages/desktop/src/main/wsl/ipc.ts | 64 ++ packages/desktop/src/main/wsl/policy.ts | 26 + packages/desktop/src/main/wsl/runtime.ts | 400 +++++++++++ packages/desktop/src/main/wsl/servers.test.ts | 93 +++ packages/desktop/src/main/wsl/servers.ts | 440 +++++++++++++ packages/desktop/src/main/wsl/sidecar.ts | 129 ++++ packages/desktop/src/main/wsl/startup.ts | 31 + packages/desktop/src/preload/index.ts | 28 +- packages/desktop/src/preload/types.ts | 20 +- packages/desktop/src/renderer/index.tsx | 147 ++--- .../src/renderer/wsl/connections.test.ts | 43 ++ .../desktop/src/renderer/wsl/connections.ts | 28 + 37 files changed, 2620 insertions(+), 202 deletions(-) create mode 100644 packages/app/src/components/prompt-input/server-attachment.test.ts create mode 100644 packages/app/src/components/prompt-input/server-attachment.ts create mode 100644 packages/app/src/wsl/context.tsx create mode 100644 packages/app/src/wsl/dialog-add-server.tsx create mode 100644 packages/app/src/wsl/settings-model.test.ts create mode 100644 packages/app/src/wsl/settings-model.ts create mode 100644 packages/app/src/wsl/settings.tsx create mode 100644 packages/app/src/wsl/types.ts create mode 100644 packages/desktop/src/main/wsl/ipc.ts create mode 100644 packages/desktop/src/main/wsl/policy.ts create mode 100644 packages/desktop/src/main/wsl/runtime.ts create mode 100644 packages/desktop/src/main/wsl/servers.test.ts create mode 100644 packages/desktop/src/main/wsl/servers.ts create mode 100644 packages/desktop/src/main/wsl/sidecar.ts create mode 100644 packages/desktop/src/main/wsl/startup.ts create mode 100644 packages/desktop/src/renderer/wsl/connections.test.ts create mode 100644 packages/desktop/src/renderer/wsl/connections.ts diff --git a/packages/app/package.json b/packages/app/package.json index 943dd1439..2ac043645 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -6,6 +6,7 @@ "exports": { ".": "./src/index.ts", "./desktop-menu": "./src/desktop-menu.ts", + "./wsl/types": "./src/wsl/types.ts", "./vite": "./vite.js", "./index.css": "./src/index.css" }, diff --git a/packages/app/src/app.tsx b/packages/app/src/app.tsx index 8e5d445f4..fc1213f76 100644 --- a/packages/app/src/app.tsx +++ b/packages/app/src/app.tsx @@ -44,6 +44,7 @@ import { ServerConnection, ServerProvider, serverName, useServer } from "@/conte import { SettingsProvider, useSettings } from "@/context/settings" import { TerminalProvider } from "@/context/terminal" import { TabsProvider } from "@/context/tabs" +import { WslServersProvider } from "@/wsl/context" import DirectoryLayout from "@/pages/directory-layout" import Layout from "@/pages/layout" import { ErrorPage } from "./pages/error" @@ -71,7 +72,6 @@ declare global { __OPENCODE__?: { updaterEnabled?: boolean deepLinks?: string[] - wsl?: boolean } api?: { setTitlebar?: (theme: { mode: "light" | "dark" }) => Promise @@ -171,11 +171,13 @@ export function AppBaseProviders(props: ParentProps<{ locale?: Locale }>) { }} > - - - {props.children} - - + + + + {props.children} + + + diff --git a/packages/app/src/components/dialog-select-file.tsx b/packages/app/src/components/dialog-select-file.tsx index 5c024ec3b..abb6439d8 100644 --- a/packages/app/src/components/dialog-select-file.tsx +++ b/packages/app/src/components/dialog-select-file.tsx @@ -261,7 +261,11 @@ function createSessionEntries(props: { return { sessions } } -export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFile?: (path: string) => void }) { +export function DialogSelectFile(props: { + mode?: DialogSelectFileMode + onOpenFile?: (path: string) => void + onSelectFile?: (path: string) => void +}) { const command = useCommand() const language = useLanguage() const layout = useLayout() @@ -375,6 +379,10 @@ export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFil } if (!item.path) return + if (props.onSelectFile) { + props.onSelectFile(item.path) + return + } open(item.path) } diff --git a/packages/app/src/components/prompt-input.tsx b/packages/app/src/components/prompt-input.tsx index 8e4913b1f..5390ec7b2 100644 --- a/packages/app/src/components/prompt-input.tsx +++ b/packages/app/src/components/prompt-input.tsx @@ -52,6 +52,7 @@ import { usePermission } from "@/context/permission" import { useLanguage } from "@/context/language" import { usePlatform } from "@/context/platform" import { useSettings } from "@/context/settings" +import { serverAttachmentFile } from "./prompt-input/server-attachment" import { useSessionLayout } from "@/pages/session/session-layout" import { createSessionTabs } from "@/pages/session/helpers" import { createTextFragment, getCursorPosition, setCursorPosition, setRangeEdge } from "./prompt-input/editor-dom" @@ -465,7 +466,25 @@ export const PromptInput: Component = (props) => { const escBlur = () => platform.platform === "desktop" && platform.os === "macos" - const pick = () => fileInputRef?.click() + const pick = () => { + if (server.isLocal()) { + fileInputRef?.click() + return + } + void import("@/components/dialog-select-file").then((module) => + dialog.show(() => ( + { + void sdk.client.v2.fs + .read({ path }) + .then((response) => response.data?.data) + .then((data) => data && addAttachments([serverAttachmentFile(path, data)])) + }} + /> + )), + ) + } const setMode = (mode: "normal" | "shell") => { setStore("mode", mode) diff --git a/packages/app/src/components/prompt-input/server-attachment.test.ts b/packages/app/src/components/prompt-input/server-attachment.test.ts new file mode 100644 index 000000000..b1fc95a10 --- /dev/null +++ b/packages/app/src/components/prompt-input/server-attachment.test.ts @@ -0,0 +1,25 @@ +import { describe, expect, test } from "bun:test" +import { serverAttachmentFile } from "./server-attachment" + +describe("serverAttachmentFile", () => { + test("creates a file from server text content", async () => { + const file = serverAttachmentFile("docs/readme.txt", { type: "text", content: "hello", mime: "text/plain" }) + + expect(file.name).toBe("readme.txt") + expect(file.type).toBe("text/plain") + expect(await file.text()).toBe("hello") + }) + + test("creates a file from server base64 content", async () => { + const file = serverAttachmentFile("images/pixel.png", { + type: "binary", + content: "aGVsbG8=", + encoding: "base64", + mime: "image/png", + }) + + expect(file.name).toBe("pixel.png") + expect(file.type).toBe("image/png") + expect(await file.text()).toBe("hello") + }) +}) diff --git a/packages/app/src/components/prompt-input/server-attachment.ts b/packages/app/src/components/prompt-input/server-attachment.ts new file mode 100644 index 000000000..4980a5fcd --- /dev/null +++ b/packages/app/src/components/prompt-input/server-attachment.ts @@ -0,0 +1,7 @@ +import { getFilename } from "@opencode-ai/core/util/path" +import type { FileSystemBinaryContent, FileSystemTextContent } from "@opencode-ai/sdk/v2" + +export function serverAttachmentFile(path: string, data: FileSystemTextContent | FileSystemBinaryContent) { + const content = data.type === "text" ? data.content : Uint8Array.from(atob(data.content), (char) => char.charCodeAt(0)) + return new File([content], getFilename(path), { type: data.mime }) +} diff --git a/packages/app/src/components/settings-v2/servers.tsx b/packages/app/src/components/settings-v2/servers.tsx index 1592841dd..4ea5482c3 100644 --- a/packages/app/src/components/settings-v2/servers.tsx +++ b/packages/app/src/components/settings-v2/servers.tsx @@ -14,6 +14,7 @@ import { ServerConnection, serverName } from "@/context/server" import { useServerManagementController } from "../dialog-select-server" import { DialogServerV2 } from "./dialog-server-v2" import { SettingsListV2 } from "./parts/list" +import { isWslServer, useFilteredWslServers, WslAddServerButton, WslServerSettings } from "@/wsl/settings" import "./settings-v2.css" export const SettingsServersV2: Component = () => { @@ -21,11 +22,12 @@ export const SettingsServersV2: Component = () => { const language = useLanguage() const controller = useServerManagementController() const [store, setStore] = createStore({ filter: "" }) + const wslServers = useFilteredWslServers(() => store.filter) - const showSearch = createMemo(() => controller.sortedItems().length > 1) + const showSearch = createMemo(() => controller.sortedItems().filter((item) => !isWslServer(item)).length + wslServers().length > 1) const filtered = createMemo(() => { - const items = controller.sortedItems() + const items = controller.sortedItems().filter((item) => !isWslServer(item)) const query = store.filter.trim() if (!query) return items return fuzzysort @@ -54,6 +56,7 @@ export const SettingsServersV2: Component = () => { {language.t("dialog.server.add.button")} +