diff --git a/packages/opencode/src/filesystem/index.ts b/packages/opencode/src/filesystem/index.ts index 45231d43f6..01fdcd2e5e 100644 --- a/packages/opencode/src/filesystem/index.ts +++ b/packages/opencode/src/filesystem/index.ts @@ -188,13 +188,23 @@ export namespace AppFileSystem { export function normalizePath(p: string): string { if (process.platform !== "win32") return p + const resolved = pathResolve(windowsPath(p)) try { - return realpathSync.native(p) + return realpathSync.native(resolved) } catch { - return p + return resolved } } + export function normalizePathPattern(p: string): string { + if (process.platform !== "win32") return p + if (p === "*") return p + const match = p.match(/^(.*)[\\/]\*$/) + if (!match) return normalizePath(p) + const dir = /^[A-Za-z]:$/.test(match[1]) ? match[1] + "\\" : match[1] + return join(normalizePath(dir), "*") + } + export function resolve(p: string): string { const resolved = pathResolve(windowsPath(p)) try { diff --git a/packages/opencode/src/tool/external-directory.ts b/packages/opencode/src/tool/external-directory.ts index 66eba438bc..f11455cf59 100644 --- a/packages/opencode/src/tool/external-directory.ts +++ b/packages/opencode/src/tool/external-directory.ts @@ -1,7 +1,8 @@ import path from "path" +import { Effect } from "effect" import type { Tool } from "./tool" import { Instance } from "../project/instance" -import { Filesystem } from "@/util/filesystem" +import { AppFileSystem } from "../filesystem" type Kind = "file" | "directory" @@ -15,14 +16,14 @@ export async function assertExternalDirectory(ctx: Tool.Context, target?: string if (options?.bypass) return - const full = process.platform === "win32" ? Filesystem.normalizePath(target) : target + const full = process.platform === "win32" ? AppFileSystem.normalizePath(target) : target if (Instance.containsPath(full)) return const kind = options?.kind ?? "file" const dir = kind === "directory" ? full : path.dirname(full) const glob = process.platform === "win32" - ? Filesystem.normalizePathPattern(path.join(dir, "*")) + ? AppFileSystem.normalizePathPattern(path.join(dir, "*")) : path.join(dir, "*").replaceAll("\\", "/") await ctx.ask({ @@ -35,3 +36,11 @@ export async function assertExternalDirectory(ctx: Tool.Context, target?: string }, }) } + +export const assertExternalDirectoryEffect = Effect.fn("Tool.assertExternalDirectory")(function* ( + ctx: Tool.Context, + target?: string, + options?: Options, +) { + yield* Effect.promise(() => assertExternalDirectory(ctx, target, options)) +}) diff --git a/packages/opencode/src/tool/read.ts b/packages/opencode/src/tool/read.ts index a6cb725bea..366993020b 100644 --- a/packages/opencode/src/tool/read.ts +++ b/packages/opencode/src/tool/read.ts @@ -10,7 +10,7 @@ import { LSP } from "../lsp" import { FileTime } from "../file/time" import DESCRIPTION from "./read.txt" import { Instance } from "../project/instance" -import { assertExternalDirectory } from "./external-directory" +import { assertExternalDirectoryEffect } from "./external-directory" import { Instruction } from "../session/instruction" const DEFAULT_READ_LIMIT = 2000 @@ -96,15 +96,18 @@ export const ReadTool = Tool.defineEffect( } const title = path.relative(Instance.worktree, filepath) - const stat = yield* fs.stat(filepath).pipe(Effect.catch(() => Effect.succeed(undefined))) - - yield* Effect.promise(() => - assertExternalDirectory(ctx, filepath, { - bypass: Boolean(ctx.extra?.["bypassCwdCheck"]), - kind: stat?.type === "Directory" ? "directory" : "file", - }), + const stat = yield* fs.stat(filepath).pipe( + Effect.catchIf( + (err) => "reason" in err && err.reason._tag === "NotFound", + () => Effect.succeed(undefined), + ), ) + yield* assertExternalDirectoryEffect(ctx, filepath, { + bypass: Boolean(ctx.extra?.["bypassCwdCheck"]), + kind: stat?.type === "Directory" ? "directory" : "file", + }) + yield* Effect.promise(() => ctx.ask({ permission: "read", @@ -218,15 +221,7 @@ export const ReadTool = Tool.defineEffect( description: DESCRIPTION, parameters, async execute(params: z.infer, ctx) { - return Effect.runPromise( - run(params, ctx).pipe( - Effect.catch((err) => - Effect.sync(() => { - throw err - }), - ), - ), - ) + return Effect.runPromise(run(params, ctx).pipe(Effect.orDie)) }, } }),