Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/app/src/custom-elements.d.ts
11 changes: 8 additions & 3 deletions packages/desktop/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ const createPlatform = (password: Accessor<string | null>): Platform => ({
multiple: opts?.multiple ?? false,
title: opts?.title ?? "Choose a folder",
})
return result
if (!result) return result
if (Array.isArray(result)) return result.map((p) => p.replace(/\\/g, "/"))
return result.replace(/\\/g, "/")
},

async openFilePickerDialog(opts) {
Expand All @@ -64,15 +66,18 @@ const createPlatform = (password: Accessor<string | null>): Platform => ({
multiple: opts?.multiple ?? false,
title: opts?.title ?? "Choose a file",
})
return result
if (!result) return result
if (Array.isArray(result)) return result.map((p) => p.replace(/\\/g, "/"))
return result.replace(/\\/g, "/")
},

async saveFilePickerDialog(opts) {
const result = await save({
title: opts?.title ?? "Save file",
defaultPath: opts?.defaultPath,
})
return result
if (!result) return result
return result.replace(/\\/g, "/")
},

openLink(url: string) {
Expand Down
2 changes: 1 addition & 1 deletion packages/enterprise/src/custom-elements.d.ts
8 changes: 4 additions & 4 deletions packages/opencode/src/agent/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import PROMPT_TITLE from "./prompt/title.txt"
import { PermissionNext } from "@/permission/next"
import { mergeDeep, pipe, sortBy, values } from "remeda"
import { Global } from "@/global"
import path from "path"
import { Filesystem } from "@/util/filesystem"

export namespace Agent {
export const Info = z
Expand Down Expand Up @@ -91,12 +91,12 @@ export namespace Agent {
question: "allow",
plan_exit: "allow",
external_directory: {
[path.join(Global.Path.data, "plans", "*")]: "allow",
[Filesystem.join(Global.Path.data, "plans", "*")]: "allow",
},
edit: {
"*": "deny",
[path.join(".opencode", "plans", "*.md")]: "allow",
[path.relative(Instance.worktree, path.join(Global.Path.data, path.join("plans", "*.md")))]: "allow",
[Filesystem.join(".opencode", "plans", "*.md")]: "allow",
[Filesystem.relative(Instance.worktree, Filesystem.join(Global.Path.data, Filesystem.join("plans", "*.md")))]: "allow",
},
}),
user,
Expand Down
8 changes: 4 additions & 4 deletions packages/opencode/src/cli/cmd/tui/routes/session/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1607,7 +1607,7 @@ function Write(props: ToolProps<typeof WriteTool>) {
})

const diagnostics = createMemo(() => {
const filePath = Filesystem.normalizePath(props.input.filePath ?? "")
const filePath = Filesystem.realpath(props.input.filePath ?? "")
return props.metadata.diagnostics?.[filePath] ?? []
})

Expand Down Expand Up @@ -1782,7 +1782,7 @@ function Edit(props: ToolProps<typeof EditTool>) {
const diffContent = createMemo(() => props.metadata.diff)

const diagnostics = createMemo(() => {
const filePath = Filesystem.normalizePath(props.input.filePath ?? "")
const filePath = Filesystem.realpath(props.input.filePath ?? "")
const arr = props.metadata.diagnostics?.[filePath] ?? []
return arr.filter((x) => x.severity === 1).slice(0, 3)
})
Expand Down Expand Up @@ -1913,9 +1913,9 @@ function Question(props: ToolProps<typeof QuestionTool>) {
function normalizePath(input?: string) {
if (!input) return ""
if (path.isAbsolute(input)) {
return path.relative(process.cwd(), input) || "."
return Filesystem.relative(process.cwd(), input) || "."
}
return input
return Filesystem.normalize(input)
}

function input(input: Record<string, any>, omit?: string[]): string {
Expand Down
5 changes: 3 additions & 2 deletions packages/opencode/src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,10 +211,11 @@ export namespace Config {
}

function rel(item: string, patterns: string[]) {
const normalized = Filesystem.normalize(item)
for (const pattern of patterns) {
const index = item.indexOf(pattern)
const index = normalized.indexOf(pattern)
if (index === -1) continue
return item.slice(index + pattern.length)
return normalized.slice(index + pattern.length)
}
}

Expand Down
9 changes: 5 additions & 4 deletions packages/opencode/src/file/ignore.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { sep } from "node:path"
import { Filesystem } from "../util/filesystem"

export namespace FileIgnore {
const FOLDERS = new Set([
Expand Down Expand Up @@ -64,18 +64,19 @@ export namespace FileIgnore {
whitelist?: Bun.Glob[]
},
) {
const normalized = Filesystem.normalize(filepath)
for (const glob of opts?.whitelist || []) {
if (glob.match(filepath)) return false
if (glob.match(normalized)) return false
}

const parts = filepath.split(sep)
const parts = normalized.split("/")
for (let i = 0; i < parts.length; i++) {
if (FOLDERS.has(parts[i])) return true
}

const extra = opts?.extra || []
for (const glob of [...FILE_GLOBS, ...extra]) {
if (glob.match(filepath)) return true
if (glob.match(normalized)) return true
}

return false
Expand Down
4 changes: 2 additions & 2 deletions packages/opencode/src/lsp/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export namespace LSPClient {

const diagnostics = new Map<string, Diagnostic[]>()
connection.onNotification("textDocument/publishDiagnostics", (params) => {
const filePath = Filesystem.normalizePath(fileURLToPath(params.uri))
const filePath = Filesystem.realpath(fileURLToPath(params.uri))
l.info("textDocument/publishDiagnostics", {
path: filePath,
count: params.diagnostics.length,
Expand Down Expand Up @@ -208,7 +208,7 @@ export namespace LSPClient {
return diagnostics
},
async waitForDiagnostics(input: { path: string }) {
const normalizedPath = Filesystem.normalizePath(
const normalizedPath = Filesystem.realpath(
path.isAbsolute(input.path) ? input.path : path.resolve(Instance.directory, input.path),
)
log.info("waiting for diagnostics", { path: normalizedPath })
Expand Down
9 changes: 5 additions & 4 deletions packages/opencode/src/patch/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,26 +75,27 @@ export namespace Patch {
lines: string[],
startIdx: number,
): { filePath: string; movePath?: string; nextIdx: number } | null {
if (startIdx >= lines.length) return null
const line = lines[startIdx]

if (line.startsWith("*** Add File:")) {
const filePath = line.split(":", 2)[1]?.trim()
const filePath = line.substring("*** Add File:".length).trim()
return filePath ? { filePath, nextIdx: startIdx + 1 } : null
}

if (line.startsWith("*** Delete File:")) {
const filePath = line.split(":", 2)[1]?.trim()
const filePath = line.substring("*** Delete File:".length).trim()
return filePath ? { filePath, nextIdx: startIdx + 1 } : null
}

if (line.startsWith("*** Update File:")) {
const filePath = line.split(":", 2)[1]?.trim()
const filePath = line.substring("*** Update File:".length).trim()
let movePath: string | undefined
let nextIdx = startIdx + 1

// Check for move directive
if (nextIdx < lines.length && lines[nextIdx].startsWith("*** Move to:")) {
movePath = lines[nextIdx].split(":", 2)[1]?.trim()
movePath = lines[nextIdx].substring("*** Move to:".length).trim()
nextIdx++
}

Expand Down
11 changes: 6 additions & 5 deletions packages/opencode/src/project/instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@ const cache = new Map<string, Promise<Context>>()

export const Instance = {
async provide<R>(input: { directory: string; init?: () => Promise<any>; fn: () => R }): Promise<R> {
let existing = cache.get(input.directory)
const directory = Filesystem.normalize(input.directory)
let existing = cache.get(directory)
if (!existing) {
Log.Default.info("creating instance", { directory: input.directory })
Log.Default.info("creating instance", { directory })
existing = iife(async () => {
const { project, sandbox } = await Project.fromDirectory(input.directory)
const { project, sandbox } = await Project.fromDirectory(directory)
const ctx = {
directory: input.directory,
directory: directory,
worktree: sandbox,
project,
}
Expand All @@ -31,7 +32,7 @@ export const Instance = {
})
return ctx
})
cache.set(input.directory, existing)
cache.set(directory, existing)
}
const ctx = await existing
return context.provide(ctx, async () => {
Expand Down
6 changes: 3 additions & 3 deletions packages/opencode/src/project/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export namespace Project {
const git = await matches.next().then((x) => x.value)
await matches.return()
if (git) {
let sandbox = path.dirname(git)
let sandbox = Filesystem.dirname(git)

const gitBinary = Bun.which("git")

Expand Down Expand Up @@ -118,7 +118,7 @@ export namespace Project {
.nothrow()
.cwd(sandbox)
.text()
.then((x) => path.resolve(sandbox, x.trim()))
.then((x) => Filesystem.resolve(sandbox, x.trim()))
.catch(() => undefined)

if (!top) {
Expand All @@ -138,7 +138,7 @@ export namespace Project {
.cwd(sandbox)
.text()
.then((x) => {
const dirname = path.dirname(x.trim())
const dirname = Filesystem.dirname(x.trim())
if (dirname === ".") return sandbox
return dirname
})
Expand Down
5 changes: 4 additions & 1 deletion packages/opencode/src/server/routes/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { Log } from "../../util/log"
import { PermissionNext } from "@/permission/next"
import { errors } from "../error"
import { lazy } from "../../util/lazy"
import { Filesystem } from "../../util/filesystem"

const log = Log.create({ service: "server" })

Expand Down Expand Up @@ -55,8 +56,10 @@ export const SessionRoutes = lazy(() =>
const query = c.req.valid("query")
const term = query.search?.toLowerCase()
const sessions: Session.Info[] = []
const normalizedQueryDir = query.directory ? Filesystem.normalize(query.directory) : undefined
for await (const session of Session.list()) {
if (query.directory !== undefined && session.directory !== query.directory) continue
if (normalizedQueryDir !== undefined && Filesystem.normalize(session.directory) !== normalizedQueryDir)
continue
if (query.roots && session.parentID) continue
if (query.start !== undefined && session.time.updated < query.start) continue
if (term !== undefined && !session.title.toLowerCase().includes(term)) continue
Expand Down
3 changes: 2 additions & 1 deletion packages/opencode/src/session/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { Snapshot } from "@/snapshot"
import type { Provider } from "@/provider/provider"
import { PermissionNext } from "@/permission/next"
import { Global } from "@/global"
import { Filesystem } from "@/util/filesystem"

export namespace Session {
const log = Log.create({ service: "session" })
Expand Down Expand Up @@ -236,7 +237,7 @@ export namespace Session {
const base = Instance.project.vcs
? path.join(Instance.worktree, ".opencode", "plans")
: path.join(Global.Path.data, "plans")
return path.join(base, [input.time.created, input.slug].join("-") + ".md")
return Filesystem.join(base, [input.time.created, input.slug].join("-") + ".md")
}

export const get = fn(Identifier.schema("session"), async (id) => {
Expand Down
2 changes: 1 addition & 1 deletion packages/opencode/src/skill/skill.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export namespace Skill {
skills[parsed.data.name] = {
name: parsed.data.name,
description: parsed.data.description,
location: match,
location: Filesystem.normalize(match),
}
}

Expand Down
3 changes: 2 additions & 1 deletion packages/opencode/src/snapshot/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Global } from "../global"
import z from "zod"
import { Config } from "../config/config"
import { Instance } from "../project/instance"
import { Filesystem } from "../util/filesystem"

export namespace Snapshot {
const log = Log.create({ service: "snapshot" })
Expand Down Expand Up @@ -67,7 +68,7 @@ export namespace Snapshot {
.split("\n")
.map((x) => x.trim())
.filter(Boolean)
.map((x) => path.join(Instance.worktree, x)),
.map((x) => Filesystem.join(Instance.worktree, x)),
}
}

Expand Down
17 changes: 4 additions & 13 deletions packages/opencode/src/tool/bash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,20 +111,11 @@ export const BashTool = Tool.define("bash", async () => {
if (["cd", "rm", "cp", "mv", "mkdir", "touch", "chmod", "chown"].includes(command[0])) {
for (const arg of command.slice(1)) {
if (arg.startsWith("-") || (command[0] === "chmod" && arg.startsWith("+"))) continue
const resolved = await $`realpath ${arg}`
.cwd(cwd)
.quiet()
.nothrow()
.text()
.then((x) => x.trim())
const resolved = Filesystem.resolve(cwd, arg)
log.info("resolved path", { arg, resolved })
if (resolved) {
// Git Bash on Windows returns Unix-style paths like /c/Users/...
const normalized =
process.platform === "win32" && resolved.match(/^\/[a-z]\//)
? resolved.replace(/^\/([a-z])\//, (_, drive) => `${drive.toUpperCase()}:\\`).replace(/\//g, "\\")
: resolved
if (!Instance.containsPath(normalized)) directories.add(normalized)
const normalized = Filesystem.normalize(resolved)
if (!Filesystem.contains(Instance.directory, normalized)) directories.add(normalized)
}
}
}
Expand All @@ -140,7 +131,7 @@ export const BashTool = Tool.define("bash", async () => {
await ctx.ask({
permission: "external_directory",
patterns: Array.from(directories),
always: Array.from(directories).map((x) => path.dirname(x) + "*"),
always: Array.from(directories).map((x) => Filesystem.dirname(x) + "*"),
metadata: {},
})
}
Expand Down
11 changes: 6 additions & 5 deletions packages/opencode/src/tool/edit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ export const EditTool = Tool.define("edit", {
throw new Error("oldString and newString must be different")
}

const filePath = path.isAbsolute(params.filePath) ? params.filePath : path.join(Instance.directory, params.filePath)
const normalized = Filesystem.normalize(params.filePath)
const filePath = path.isAbsolute(normalized) ? normalized : Filesystem.join(Instance.directory, normalized)
await assertExternalDirectory(ctx, filePath)

let diff = ""
Expand All @@ -52,7 +53,7 @@ export const EditTool = Tool.define("edit", {
diff = trimDiff(createTwoFilesPatch(filePath, filePath, contentOld, contentNew))
await ctx.ask({
permission: "edit",
patterns: [path.relative(Instance.worktree, filePath)],
patterns: [Filesystem.relative(Instance.worktree, filePath)],
always: ["*"],
metadata: {
filepath: filePath,
Expand Down Expand Up @@ -80,7 +81,7 @@ export const EditTool = Tool.define("edit", {
)
await ctx.ask({
permission: "edit",
patterns: [path.relative(Instance.worktree, filePath)],
patterns: [Filesystem.relative(Instance.worktree, filePath)],
always: ["*"],
metadata: {
filepath: filePath,
Expand Down Expand Up @@ -122,7 +123,7 @@ export const EditTool = Tool.define("edit", {
let output = "Edit applied successfully."
await LSP.touchFile(filePath, true)
const diagnostics = await LSP.diagnostics()
const normalizedFilePath = Filesystem.normalizePath(filePath)
const normalizedFilePath = Filesystem.realpath(filePath)
const issues = diagnostics[normalizedFilePath] ?? []
const errors = issues.filter((item) => item.severity === 1)
if (errors.length > 0) {
Expand All @@ -138,7 +139,7 @@ export const EditTool = Tool.define("edit", {
diff,
filediff,
},
title: `${path.relative(Instance.worktree, filePath)}`,
title: `${Filesystem.relative(Instance.worktree, filePath)}`,
output,
}
},
Expand Down
Loading