From 5504a0ce3ba47d1078e487bfd9c05c0c4e4a4815 Mon Sep 17 00:00:00 2001 From: bluelovers Date: Tue, 19 May 2026 07:48:43 +0800 Subject: [PATCH 1/3] feat: detect realpath when open workspace --- packages/server/src/api-types.ts | 12 ++++++ .../server/src/server/routes/filesystem.ts | 41 +++++++++++++++++++ packages/ui/src/App.tsx | 18 +++++--- packages/ui/src/lib/api-client.ts | 8 ++++ 4 files changed, 74 insertions(+), 5 deletions(-) diff --git a/packages/server/src/api-types.ts b/packages/server/src/api-types.ts index d2349cde..53b9192b 100644 --- a/packages/server/src/api-types.ts +++ b/packages/server/src/api-types.ts @@ -220,6 +220,18 @@ export interface FileSystemFileContentResponse { encoding: "utf-8" | "base64" } +export interface DetectPathExistingInRecentRequest { + currentPath: string + recentPaths: RecentFolder[] +} + +export interface DetectPathExistingInRecentResponse { + exists: boolean + currentPath: string + currentReal: string + foundResult: RecentFolder | undefined +} + export interface ConfigFileDescriptor { id: string label: string diff --git a/packages/server/src/server/routes/filesystem.ts b/packages/server/src/server/routes/filesystem.ts index 41815720..ea023169 100644 --- a/packages/server/src/server/routes/filesystem.ts +++ b/packages/server/src/server/routes/filesystem.ts @@ -1,6 +1,8 @@ import { FastifyInstance } from "fastify" import { z } from "zod" +import fs from "node:fs/promises" import { FileSystemBrowser } from "../../filesystem/browser" +import { RecentFolder, RecentFolderSchema } from '../../config/schema' interface RouteDeps { fileSystemBrowser: FileSystemBrowser @@ -21,6 +23,11 @@ const FilesystemFileContentQuerySchema = z.object({ encoding: z.enum(["utf-8", "base64"]).optional(), }) +const FilesystemFileRealpathQuerySchema = z.object({ + currentPath: z.string(), + recentFolders: z.array(RecentFolderSchema).default([]), +}) + export function registerFilesystemRoutes(app: FastifyInstance, deps: RouteDeps) { app.get("/api/filesystem", async (request, reply) => { const query = FilesystemQuerySchema.parse(request.query ?? {}) @@ -66,4 +73,38 @@ export function registerFilesystemRoutes(app: FastifyInstance, deps: RouteDeps) reply.code(400).type("text/plain").send((error as Error).message) } }) + + app.post("/api/filesystem/detect-path-existing-in-recent", async (request, reply) => { + const query = FilesystemFileRealpathQuerySchema.parse(request.body ?? {}) + + try { + const currentPath = query.currentPath + const currentReal = await fs.realpath(currentPath) + + let exists = false + let foundResult: RecentFolder | undefined + + const fn = async (folder: RecentFolder) => { + return (await fs.exists(folder.path)) && currentReal === await fs.realpath(folder.path) + } + + for (const folder of query.recentFolders) { + if (currentPath === folder.path || currentReal === folder.path || await fn(folder).catch(() => false)) { + exists = true + foundResult = folder + break + } + } + + return { + exists, + currentPath, + currentReal, + foundResult + } + } catch (error) { + reply.code(400).type("text/plain").send((error as Error).message) + } + }) + } diff --git a/packages/ui/src/App.tsx b/packages/ui/src/App.tsx index 2aa430fc..a0973b3c 100644 --- a/packages/ui/src/App.tsx +++ b/packages/ui/src/App.tsx @@ -31,7 +31,7 @@ import { showFolderSelection, setShowFolderSelection, } from "./stores/ui" -import { useConfig } from "./stores/preferences" +import { recentFolders, useConfig } from "./stores/preferences" import { createInstance, getExistingInstanceForFolder, @@ -70,6 +70,7 @@ import { selectInstanceTab, selectSidecarTab, } from "./stores/app-tabs" +import { serverApi } from './lib/api-client' const log = getLogger("actions") @@ -268,6 +269,13 @@ const App: Component = () => { if (!folderPath) { return } + + const detectResult = await serverApi.detectPathExistingInRecent(folderPath, recentFolders()) + + if (detectResult?.exists) { + folderPath = detectResult.foundResult.path + } + const selectedBinary = binaryPath || serverSettings().opencodeBinary || "opencode" recordWorkspaceLaunch(folderPath, selectedBinary) clearLaunchError() @@ -485,7 +493,7 @@ const App: Component = () => { const tauriBridge = (window as { __TAURI__?: { event?: { listen: (event: string, handler: (event: { payload: unknown }) => void) => Promise<() => void> } } }).__TAURI__ if (tauriBridge?.event) { let unlistenMenu: (() => void) | null = null - + tauriBridge.event.listen("menu:newInstance", () => { handleNewInstanceRequest() }).then((unlisten) => { @@ -527,7 +535,7 @@ const App: Component = () => {

{t("app.launchError.binaryPathLabel")}

{launchErrorPath()}

- +

{t("app.launchError.errorOutputLabel")}

@@ -652,7 +660,7 @@ const App: Component = () => {
- + setSidecarPickerOpen(false)} onOpenSidecar={handleOpenSidecar} /> @@ -679,7 +687,7 @@ const App: Component = () => { - + (`/api/filesystem/files/content?${params.toString()}`) }, + detectPathExistingInRecent(currentPath: string, recentFolders: RecentFolder[]): Promise { + return request(`/api/filesystem/detect-path-existing-in-recent`, { + method: "POST", + body: JSON.stringify({ currentPath, recentFolders }), + }) + }, readInstanceData(id: string): Promise { return request(`/api/storage/instances/${encodeURIComponent(id)}`) }, From 0d6e5b94513f8724e302d2f3876152e158a59a8d Mon Sep 17 00:00:00 2001 From: bluelovers Date: Tue, 19 May 2026 08:22:57 +0800 Subject: [PATCH 2/3] . --- packages/server/src/server/routes/filesystem.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/server/src/server/routes/filesystem.ts b/packages/server/src/server/routes/filesystem.ts index ea023169..75bcf1df 100644 --- a/packages/server/src/server/routes/filesystem.ts +++ b/packages/server/src/server/routes/filesystem.ts @@ -85,7 +85,8 @@ export function registerFilesystemRoutes(app: FastifyInstance, deps: RouteDeps) let foundResult: RecentFolder | undefined const fn = async (folder: RecentFolder) => { - return (await fs.exists(folder.path)) && currentReal === await fs.realpath(folder.path) + await fs.access(folder.path, fs.constants.F_OK) + return currentReal === await fs.realpath(folder.path) } for (const folder of query.recentFolders) { From c8a704d7299c51a47b4dd6081c26951c909485fa Mon Sep 17 00:00:00 2001 From: bluelovers Date: Thu, 21 May 2026 10:27:58 +0800 Subject: [PATCH 3/3] refactor(api): simplify detectPathExistingInRecent to use string paths Change the API to accept and return string paths instead of RecentFolder objects. This simplifies the interface by: - Using recentPaths (string[]) instead of recentFolders (RecentFolder[]) - Returning foundResult as string instead of RecentFolder object - Reducing coupling between API types and UI types --- packages/server/src/api-types.ts | 7 +----- .../server/src/server/routes/filesystem.ts | 16 ++++++------- packages/ui/src/App.tsx | 23 +++++++++++++++---- packages/ui/src/lib/api-client.ts | 4 ++-- 4 files changed, 29 insertions(+), 21 deletions(-) diff --git a/packages/server/src/api-types.ts b/packages/server/src/api-types.ts index 53b9192b..712d383f 100644 --- a/packages/server/src/api-types.ts +++ b/packages/server/src/api-types.ts @@ -220,16 +220,11 @@ export interface FileSystemFileContentResponse { encoding: "utf-8" | "base64" } -export interface DetectPathExistingInRecentRequest { - currentPath: string - recentPaths: RecentFolder[] -} - export interface DetectPathExistingInRecentResponse { exists: boolean currentPath: string currentReal: string - foundResult: RecentFolder | undefined + foundResult: string | undefined } export interface ConfigFileDescriptor { diff --git a/packages/server/src/server/routes/filesystem.ts b/packages/server/src/server/routes/filesystem.ts index 75bcf1df..f9f18fcd 100644 --- a/packages/server/src/server/routes/filesystem.ts +++ b/packages/server/src/server/routes/filesystem.ts @@ -25,7 +25,7 @@ const FilesystemFileContentQuerySchema = z.object({ const FilesystemFileRealpathQuerySchema = z.object({ currentPath: z.string(), - recentFolders: z.array(RecentFolderSchema).default([]), + recentPaths: z.array(z.string()).default([]), }) export function registerFilesystemRoutes(app: FastifyInstance, deps: RouteDeps) { @@ -82,17 +82,17 @@ export function registerFilesystemRoutes(app: FastifyInstance, deps: RouteDeps) const currentReal = await fs.realpath(currentPath) let exists = false - let foundResult: RecentFolder | undefined + let foundResult: string | undefined - const fn = async (folder: RecentFolder) => { - await fs.access(folder.path, fs.constants.F_OK) - return currentReal === await fs.realpath(folder.path) + const fn = async (path: string) => { + await fs.access(path, fs.constants.F_OK) + return currentReal === await fs.realpath(path) } - for (const folder of query.recentFolders) { - if (currentPath === folder.path || currentReal === folder.path || await fn(folder).catch(() => false)) { + for (const path of query.recentPaths) { + if (currentPath === path || currentReal === path || await fn(path).catch(() => false)) { exists = true - foundResult = folder + foundResult = path break } } diff --git a/packages/ui/src/App.tsx b/packages/ui/src/App.tsx index a0973b3c..9242de14 100644 --- a/packages/ui/src/App.tsx +++ b/packages/ui/src/App.tsx @@ -71,6 +71,8 @@ import { selectSidecarTab, } from "./stores/app-tabs" import { serverApi } from './lib/api-client' +import { RecentFolder } from '../../server/src/api-types' +import { Instance } from './types/instance' const log = getLogger("actions") @@ -270,10 +272,22 @@ const App: Component = () => { return } - const detectResult = await serverApi.detectPathExistingInRecent(folderPath, recentFolders()) - - if (detectResult?.exists) { - folderPath = detectResult.foundResult.path + let existingInstance = getExistingInstanceForFolder(folderPath) + + if (!existingInstance) { + const detectResult = await serverApi.detectPathExistingInRecent(folderPath, [ + ...(Array.from(instances().values()) as Instance[]).reduce((acc: string[], instance: Instance) => { + if (instance.status === "stopped") return acc + acc.push(instance.folder) + return acc + }, [] as string[]), + ...recentFolders().map((folder: RecentFolder) => folder.path), + ]).catch(() => null) + + if (detectResult?.exists) { + folderPath = detectResult.foundResult! + existingInstance = getExistingInstanceForFolder(folderPath) + } } const selectedBinary = binaryPath || serverSettings().opencodeBinary || "opencode" @@ -281,7 +295,6 @@ const App: Component = () => { clearLaunchError() if (!options?.forceNew) { - const existingInstance = getExistingInstanceForFolder(folderPath) if (existingInstance) { setAlreadyOpenFolderChoice({ folderPath, binaryPath: selectedBinary, instanceId: existingInstance.id }) return diff --git a/packages/ui/src/lib/api-client.ts b/packages/ui/src/lib/api-client.ts index c8db5fa6..914d6bb2 100644 --- a/packages/ui/src/lib/api-client.ts +++ b/packages/ui/src/lib/api-client.ts @@ -483,10 +483,10 @@ export const serverApi = { } return request(`/api/filesystem/files/content?${params.toString()}`) }, - detectPathExistingInRecent(currentPath: string, recentFolders: RecentFolder[]): Promise { + detectPathExistingInRecent(currentPath: string, recentPaths: string[]): Promise { return request(`/api/filesystem/detect-path-existing-in-recent`, { method: "POST", - body: JSON.stringify({ currentPath, recentFolders }), + body: JSON.stringify({ currentPath, recentPaths }), }) }, readInstanceData(id: string): Promise {