diff --git a/packages/server/src/api-types.ts b/packages/server/src/api-types.ts index d2349cde..712d383f 100644 --- a/packages/server/src/api-types.ts +++ b/packages/server/src/api-types.ts @@ -220,6 +220,13 @@ export interface FileSystemFileContentResponse { encoding: "utf-8" | "base64" } +export interface DetectPathExistingInRecentResponse { + exists: boolean + currentPath: string + currentReal: string + foundResult: string | 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..f9f18fcd 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(), + recentPaths: z.array(z.string()).default([]), +}) + export function registerFilesystemRoutes(app: FastifyInstance, deps: RouteDeps) { app.get("/api/filesystem", async (request, reply) => { const query = FilesystemQuerySchema.parse(request.query ?? {}) @@ -66,4 +73,39 @@ 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: string | undefined + + const fn = async (path: string) => { + await fs.access(path, fs.constants.F_OK) + return currentReal === await fs.realpath(path) + } + + for (const path of query.recentPaths) { + if (currentPath === path || currentReal === path || await fn(path).catch(() => false)) { + exists = true + foundResult = path + 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..9242de14 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,9 @@ import { selectInstanceTab, 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") @@ -268,12 +271,30 @@ const App: Component = () => { if (!folderPath) { return } + + 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" recordWorkspaceLaunch(folderPath, selectedBinary) clearLaunchError() if (!options?.forceNew) { - const existingInstance = getExistingInstanceForFolder(folderPath) if (existingInstance) { setAlreadyOpenFolderChoice({ folderPath, binaryPath: selectedBinary, instanceId: existingInstance.id }) return @@ -485,7 +506,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 +548,7 @@ const App: Component = () => {
{t("app.launchError.binaryPathLabel")}
{launchErrorPath()}
- +{t("app.launchError.errorOutputLabel")}
@@ -652,7 +673,7 @@ const App: Component = () => {