diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index ff79b739fe..eabf971f47 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -41,6 +41,7 @@ import { Duration, Effect, Layer, Option, ServiceMap } from "effect" import { Flock } from "@/util/flock" import { isPathPluginSpec, parsePluginSpecifier, resolvePathPluginTarget } from "@/plugin/shared" import { Npm } from "@/npm" +import { InstanceRef } from "@/effect/instance-ref" export namespace Config { const ModelId = z.string().meta({ $ref: "https://models.dev/model-schema.json#/$defs/Model" }) @@ -1327,27 +1328,31 @@ export namespace Config { const consoleManagedProviders = new Set() let activeOrgName: string | undefined - const scope = (source: string): PluginScope => { + const scope = Effect.fnUntraced(function* (source: string) { if (source.startsWith("http://") || source.startsWith("https://")) return "global" if (source === "OPENCODE_CONFIG_CONTENT") return "local" - if (Instance.containsPath(source)) return "local" + if (yield* InstanceRef.use((ctx) => Effect.succeed(Instance.containsPath(source, ctx)))) return "local" return "global" - } + }) - const track = (source: string, list: PluginSpec[] | undefined, kind?: PluginScope) => { + const track = Effect.fnUntraced(function* ( + source: string, + list: PluginSpec[] | undefined, + kind?: PluginScope, + ) { if (!list?.length) return - const hit = kind ?? scope(source) + const hit = kind ?? (yield* scope(source)) const plugins = deduplicatePluginOrigins([ ...(result.plugin_origins ?? []), ...list.map((spec) => ({ spec, source, scope: hit })), ]) result.plugin = plugins.map((item) => item.spec) result.plugin_origins = plugins - } + }) const merge = (source: string, next: Info, kind?: PluginScope) => { result = mergeConfigConcatArrays(result, next) - track(source, next.plugin, kind) + return track(source, next.plugin, kind) } for (const [key, value] of Object.entries(auth)) { @@ -1367,16 +1372,16 @@ export namespace Config { dir: path.dirname(source), source, }) - merge(source, next, "global") + yield* merge(source, next, "global") log.debug("loaded remote config from well-known", { url }) } } const global = yield* getGlobal() - merge(Global.Path.config, global, "global") + yield* merge(Global.Path.config, global, "global") if (Flag.OPENCODE_CONFIG) { - merge(Flag.OPENCODE_CONFIG, yield* loadFile(Flag.OPENCODE_CONFIG)) + yield* merge(Flag.OPENCODE_CONFIG, yield* loadFile(Flag.OPENCODE_CONFIG)) log.debug("loaded custom config", { path: Flag.OPENCODE_CONFIG }) } @@ -1384,7 +1389,7 @@ export namespace Config { for (const file of yield* Effect.promise(() => ConfigPaths.projectFiles("opencode", ctx.directory, ctx.worktree), )) { - merge(file, yield* loadFile(file), "local") + yield* merge(file, yield* loadFile(file), "local") } } @@ -1405,7 +1410,7 @@ export namespace Config { for (const file of ["opencode.json", "opencode.jsonc"]) { const source = path.join(dir, file) log.debug(`loading config from ${source}`) - merge(source, yield* loadFile(source)) + yield* merge(source, yield* loadFile(source)) result.agent ??= {} result.mode ??= {} result.plugin ??= [] @@ -1424,7 +1429,7 @@ export namespace Config { result.agent = mergeDeep(result.agent, yield* Effect.promise(() => loadAgent(dir))) result.agent = mergeDeep(result.agent, yield* Effect.promise(() => loadMode(dir))) const list = yield* Effect.promise(() => loadPlugin(dir)) - track(dir, list) + yield* track(dir, list) } if (process.env.OPENCODE_CONFIG_CONTENT) { @@ -1433,7 +1438,7 @@ export namespace Config { dir: ctx.directory, source, }) - merge(source, next, "local") + yield* merge(source, next, "local") log.debug("loaded custom config from OPENCODE_CONFIG_CONTENT") } @@ -1462,7 +1467,7 @@ export namespace Config { for (const providerID of Object.keys(next.provider ?? {})) { consoleManagedProviders.add(providerID) } - merge(source, next, "global") + yield* merge(source, next, "global") } }).pipe( Effect.catch((err) => { @@ -1477,7 +1482,7 @@ export namespace Config { if (existsSync(managedDir)) { for (const file of ["opencode.json", "opencode.jsonc"]) { const source = path.join(managedDir, file) - merge(source, yield* loadFile(source), "global") + yield* merge(source, yield* loadFile(source), "global") } } diff --git a/packages/opencode/src/project/instance.ts b/packages/opencode/src/project/instance.ts index 33078183b9..60665a99a3 100644 --- a/packages/opencode/src/project/instance.ts +++ b/packages/opencode/src/project/instance.ts @@ -90,12 +90,13 @@ export const Instance = { * Returns true if path is inside Instance.directory OR Instance.worktree. * Paths within the worktree but outside the working directory should not trigger external_directory permission. */ - containsPath(filepath: string) { - if (Filesystem.contains(Instance.directory, filepath)) return true + containsPath(filepath: string, ctx?: InstanceContext) { + const instance = ctx ?? Instance + if (Filesystem.contains(instance.directory, filepath)) return true // Non-git projects set worktree to "/" which would match ANY absolute path. // Skip worktree check in this case to preserve external_directory permissions. if (Instance.worktree === "/") return false - return Filesystem.contains(Instance.worktree, filepath) + return Filesystem.contains(instance.worktree, filepath) }, /** * Captures the current instance ALS context and returns a wrapper that