fix(core): normalize Windows tool paths (#45806)

This commit is contained in:
Aiden Cline
2026-08-27 21:36:22 -05:00
committed by GitHub
parent 09b0edd662
commit 1faba03cfe
3 changed files with 25 additions and 12 deletions
+9 -7
View File
@@ -59,16 +59,18 @@ export interface Interface {
readonly resolve: (input: ResolveInput) => Effect.Effect<Target, FSUtil.Error>
}
/** Lexical absolute path, expanding a leading `~` before resolving against `directory`. */
export const resolvePath = (directory: string, input: string, home = Global.Path.home) =>
path.resolve(
/** Lexical absolute path, normalizing Windows shell paths and expanding `~` before resolution. */
export const resolvePath = (directory: string, input: string, home = Global.Path.home) => {
const normalized = FSUtil.windowsPath(input)
return path.resolve(
directory,
input === "~"
normalized === "~"
? home
: input.startsWith("~/") || (process.platform === "win32" && input.startsWith("~\\"))
? path.join(home, input.slice(2))
: input,
: normalized.startsWith("~/") || (process.platform === "win32" && normalized.startsWith("~\\"))
? path.join(home, normalized.slice(2))
: normalized,
)
}
export class Service extends Context.Service<Service, Interface>()("@opencode/LocationMutation") {}
+4 -2
View File
@@ -1,6 +1,5 @@
export * as ShellTool from "./shell.js"
import path from "path"
import { ToolFailure } from "@opencode-ai/ai"
import type { Context as PluginContext } from "@opencode-ai/plugin/effect/plugin"
import { Deferred, Effect, Schema, Scope } from "effect"
@@ -190,7 +189,10 @@ export const Plugin = {
portable,
})
const directories = yield* Effect.forEach(parsed.directories, (directory) =>
mutation.resolve({ path: path.resolve(target.absolute, directory), kind: "directory" }),
mutation.resolve({
path: LocationMutation.resolvePath(target.absolute, directory),
kind: "directory",
}),
)
const external = [target, ...directories]
.map((item) => item.externalDirectory)
+12 -3
View File
@@ -236,9 +236,18 @@ describe("LocationMutation", () => {
expect(LocationMutation.resolvePath("/project", "~/notes.md", home)).toBe(path.resolve(home, "notes.md"))
expect(LocationMutation.resolvePath("/project", "~draft.md", home)).toBe(path.resolve("/project", "~draft.md"))
expect(LocationMutation.resolvePath("/project", "~\\notes.md", home)).toBe(
process.platform === "win32"
? path.resolve(home, "notes.md")
: path.resolve("/project", "~\\notes.md"),
process.platform === "win32" ? path.resolve(home, "notes.md") : path.resolve("/project", "~\\notes.md"),
)
})
test.each([
["/c/Users/aiden/notes.md", "C:/Users/aiden/notes.md"],
["/C:/Users/aiden/notes.md", "C:/Users/aiden/notes.md"],
["/cygdrive/c/Users/aiden/notes.md", "C:/Users/aiden/notes.md"],
["/mnt/c/Users/aiden/notes.md", "C:/Users/aiden/notes.md"],
])("normalizes Windows shell drive path %s before resolution", (input, windows) => {
expect(LocationMutation.resolvePath("/project", input)).toBe(
process.platform === "win32" ? path.resolve(windows) : path.resolve(input),
)
})