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
9 changes: 7 additions & 2 deletions packages/opencode/src/config/paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ import { Flag } from "@/flag/flag"
import { Global } from "@/global"

export namespace ConfigPaths {
export function globalDirectories() {
return [Global.Path.config, ...(Flag.OPENCODE_CONFIG_DIR ? [Flag.OPENCODE_CONFIG_DIR] : [])]
}

export async function projectFiles(name: string, directory: string, worktree: string) {
const files: string[] = []
for (const file of [`${name}.jsonc`, `${name}.json`]) {
Expand All @@ -20,8 +24,9 @@ export namespace ConfigPaths {
}

export async function directories(directory: string, worktree: string) {
const global = globalDirectories()
return [
Global.Path.config,
global[0],
...(!Flag.OPENCODE_DISABLE_PROJECT_CONFIG
? await Array.fromAsync(
Filesystem.up({
Expand All @@ -38,7 +43,7 @@ export namespace ConfigPaths {
stop: Global.Path.home,
}),
)),
...(Flag.OPENCODE_CONFIG_DIR ? [Flag.OPENCODE_CONFIG_DIR] : []),
...global.slice(1),
]
}

Expand Down
10 changes: 9 additions & 1 deletion packages/opencode/src/tool/external-directory.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import path from "path"
import type { Tool } from "./tool"
import { ConfigPaths } from "../config/paths"
import { Instance } from "../project/instance"
import { Filesystem } from "../util/filesystem"

type Kind = "file" | "directory"

Expand All @@ -9,12 +11,18 @@ type Options = {
kind?: Kind
}

function internal(target: string) {
const file = path.isAbsolute(target) ? target : path.join(Instance.directory, target)
if (Instance.containsPath(file)) return true
return ConfigPaths.globalDirectories().some((dir) => Filesystem.contains(path.resolve(dir), file))
}

export async function assertExternalDirectory(ctx: Tool.Context, target?: string, options?: Options) {
if (!target) return

if (options?.bypass) return

if (Instance.containsPath(target)) return
if (internal(target)) return

const kind = options?.kind ?? "file"
const parentDir = kind === "directory" ? target : path.dirname(target)
Expand Down
54 changes: 54 additions & 0 deletions packages/opencode/test/tool/external-directory.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { describe, expect, test } from "bun:test"
import path from "path"
import { Global } from "../../src/global"
import type { Tool } from "../../src/tool/tool"
import { Instance } from "../../src/project/instance"
import { assertExternalDirectory } from "../../src/tool/external-directory"
Expand Down Expand Up @@ -55,6 +56,59 @@ describe("tool.assertExternalDirectory", () => {
expect(requests.length).toBe(0)
})

test("no-ops for AGENTS.md inside Global.Path.config", async () => {
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
const ctx: Tool.Context = {
...baseCtx,
ask: async (req) => {
requests.push(req)
},
}

const prev = Global.Path.config
;(Global.Path as { config: string }).config = "/tmp/opencode-config"

try {
await Instance.provide({
directory: "/tmp/project",
fn: async () => {
await assertExternalDirectory(ctx, path.join(Global.Path.config, "AGENTS.md"))
},
})
} finally {
;(Global.Path as { config: string }).config = prev
}

expect(requests.length).toBe(0)
})

test("no-ops for AGENTS.md inside OPENCODE_CONFIG_DIR", async () => {
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
const ctx: Tool.Context = {
...baseCtx,
ask: async (req) => {
requests.push(req)
},
}

const prev = process.env["OPENCODE_CONFIG_DIR"]
process.env["OPENCODE_CONFIG_DIR"] = "/tmp/opencode-profile"

try {
await Instance.provide({
directory: "/tmp/project",
fn: async () => {
await assertExternalDirectory(ctx, path.join(process.env["OPENCODE_CONFIG_DIR"]!, "AGENTS.md"))
},
})
} finally {
if (prev === undefined) delete process.env["OPENCODE_CONFIG_DIR"]
else process.env["OPENCODE_CONFIG_DIR"] = prev
}

expect(requests.length).toBe(0)
})

test("asks with a single canonical glob", async () => {
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
const ctx: Tool.Context = {
Expand Down
Loading