diff --git a/packages/opencode/src/cli/cmd/serve.ts b/packages/opencode/src/cli/cmd/serve.ts index 032b8f7f25..3310c0a443 100644 --- a/packages/opencode/src/cli/cmd/serve.ts +++ b/packages/opencode/src/cli/cmd/serve.ts @@ -7,6 +7,7 @@ import { Server } from "../../server/server" import { cmd } from "./cmd" import { withNetworkOptions, resolveNetworkOptions } from "../network" import { Flag } from "@opencode-ai/core/flag/flag" +import { bootstrap } from "../bootstrap" import { Workspace } from "../../control-plane/workspace" import { Project } from "@/project/project" import { Installation } from "../../installation" @@ -217,7 +218,7 @@ export const ServeCommand = cmd({ }), describe: "starts a headless opencode server", handler: async (args) => { - const opts = await resolveNetworkOptions(args) + const opts = await bootstrap(process.cwd(), () => resolveNetworkOptions(args)) const relayURL = ( args["relay-url"] ?? process.env.OPENCODE_EXPERIMENTAL_PUSH_RELAY_URL ?? diff --git a/packages/opencode/src/cli/cmd/tui/thread.ts b/packages/opencode/src/cli/cmd/tui/thread.ts index 07f9107b61..04aae8fe80 100644 --- a/packages/opencode/src/cli/cmd/tui/thread.ts +++ b/packages/opencode/src/cli/cmd/tui/thread.ts @@ -8,6 +8,7 @@ import { UI } from "@/cli/ui" import * as Log from "@opencode-ai/core/util/log" import { errorMessage } from "@/util/error" import { withTimeout } from "@/util/timeout" +import { Instance } from "@/project/instance" import { withNetworkOptions, resolveNetworkOptionsNoConfig } from "@/cli/network" import { Filesystem } from "@/util/filesystem" import type { GlobalEvent } from "@opencode-ai/sdk/v2" @@ -187,7 +188,11 @@ export const TuiThreadCommand = cmd({ const prompt = await input(args.prompt) const config = await TuiConfig.get() - const network = resolveNetworkOptionsNoConfig(args) + const network = await Instance.provide({ + directory: cwd, + fn: () => resolveNetworkOptionsNoConfig(args), + }) + const external = process.argv.includes("--port") || process.argv.includes("--hostname") || diff --git a/packages/opencode/src/cli/cmd/web.ts b/packages/opencode/src/cli/cmd/web.ts index 19ee38ff53..0b39975e2a 100644 --- a/packages/opencode/src/cli/cmd/web.ts +++ b/packages/opencode/src/cli/cmd/web.ts @@ -5,6 +5,7 @@ import { withNetworkOptions, resolveNetworkOptions } from "../network" import { Flag } from "@opencode-ai/core/flag/flag" import open from "open" import { networkInterfaces } from "os" +import { bootstrap } from "../bootstrap" function getNetworkIPs() { const nets = networkInterfaces() @@ -36,7 +37,7 @@ export const WebCommand = cmd({ if (!Flag.OPENCODE_SERVER_PASSWORD) { UI.println(UI.Style.TEXT_WARNING_BOLD + "! OPENCODE_SERVER_PASSWORD is not set; server is unsecured.") } - const opts = await resolveNetworkOptions(args) + const opts = await bootstrap(process.cwd(), () => resolveNetworkOptions(args)) const server = await Server.listen(opts) UI.empty() UI.println(UI.logo(" ")) diff --git a/packages/opencode/src/effect/app-runtime.ts b/packages/opencode/src/effect/app-runtime.ts index fdd3053622..11bd45b450 100644 --- a/packages/opencode/src/effect/app-runtime.ts +++ b/packages/opencode/src/effect/app-runtime.ts @@ -46,16 +46,34 @@ import { Pty } from "@/pty" import { Installation } from "@/installation" import { ShareNext } from "@/share/share-next" import { SessionShare } from "@/share/session" +import * as Effect from "effect/Effect" import { Npm } from "@opencode-ai/core/npm" import { memoMap } from "@opencode-ai/core/effect/memo-map" +// Adjusts the default Config layer to ensure that plugins are always initialised before +// any other layers read the current config +const ConfigWithPluginPriority = Layer.effect( + Config.Service, + Effect.gen(function* () { + const config = yield* Config.Service + const plugin = yield* Plugin.Service + + return { + ...config, + get: () => Effect.andThen(plugin.init(), config.get), + getGlobal: () => Effect.andThen(plugin.init(), config.getGlobal), + getConsoleState: () => Effect.andThen(plugin.init(), config.getConsoleState), + } + }), +).pipe(Layer.provide(Layer.merge(Plugin.defaultLayer, Config.defaultLayer))) + export const AppLayer = Layer.mergeAll( Npm.defaultLayer, AppFileSystem.defaultLayer, Bus.defaultLayer, Auth.defaultLayer, Account.defaultLayer, - Config.defaultLayer, + ConfigWithPluginPriority, Git.defaultLayer, Ripgrep.defaultLayer, File.defaultLayer, diff --git a/packages/opencode/src/project/bootstrap.ts b/packages/opencode/src/project/bootstrap.ts index 2ea07bb8de..ce245c7351 100644 --- a/packages/opencode/src/project/bootstrap.ts +++ b/packages/opencode/src/project/bootstrap.ts @@ -1,4 +1,3 @@ -import { Plugin } from "../plugin" import { Format } from "../format" import { LSP } from "@/lsp/lsp" import { File } from "../file" @@ -16,20 +15,19 @@ import { Config } from "@/config/config" export const InstanceBootstrap = Effect.gen(function* () { Log.Default.info("bootstrapping", { directory: Instance.directory }) - // everything depends on config so eager load it for nice traces - yield* Config.Service.use((svc) => svc.get()) - // Plugin can mutate config so it has to be initialized before anything else. - yield* Plugin.Service.use((svc) => svc.init()) yield* Effect.all( [ - LSP.Service, - ShareNext.Service, - Format.Service, - File.Service, - FileWatcher.Service, - Vcs.Service, - Snapshot.Service, - ].map((s) => Effect.forkDetach(s.use((i) => i.init()))), + Config.Service.use((i) => i.get()), + ...[ + LSP.Service, + ShareNext.Service, + Format.Service, + File.Service, + FileWatcher.Service, + Vcs.Service, + Snapshot.Service, + ].map((s) => s.use((i) => i.init())), + ].map((e) => Effect.forkDetach(e)), ).pipe(Effect.withSpan("InstanceBootstrap.init")) yield* Bus.Service.use((svc) =>