Browse Source

Apply PR #22753: core: move plugin intialisation to config layer override

opencode-agent[bot] 17 hours ago
parent
commit
75a4b55ddd

+ 2 - 4
packages/opencode/src/cli/cmd/serve.ts

@@ -6,10 +6,8 @@ import os from "node:os"
 import { Server } from "../../server/server"
 import { cmd } from "./cmd"
 import { withNetworkOptions, resolveNetworkOptions } from "../network"
+import { bootstrap } from "../bootstrap"
 import { Flag } from "../../flag/flag"
-import { Workspace } from "../../control-plane/workspace"
-import { Project } from "../../project"
-import { Installation } from "../../installation"
 import { PushRelay } from "../../server/push-relay"
 import { Log } from "../../util"
 import { Global } from "../../global"
@@ -215,7 +213,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 ??

+ 6 - 1
packages/opencode/src/cli/cmd/tui/thread.ts

@@ -8,6 +8,7 @@ import { UI } from "@/cli/ui"
 import { Log } from "@/util"
 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"
 import type { GlobalEvent } from "@opencode-ai/sdk/v2"
@@ -181,7 +182,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") ||

+ 2 - 1
packages/opencode/src/cli/cmd/web.ts

@@ -5,6 +5,7 @@ import { withNetworkOptions, resolveNetworkOptions } from "../network"
 import { Flag } from "../../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("  "))

+ 20 - 2
packages/opencode/src/effect/app-runtime.ts

@@ -1,6 +1,8 @@
 import { Layer, ManagedRuntime } from "effect"
+import * as Effect from "effect/Effect"
 import { attach } from "./run-service"
 import * as Observability from "./observability"
+import { memoMap } from "./memo-map"
 
 import { AppFileSystem } from "@opencode-ai/shared/filesystem"
 import { Bus } from "@/bus"
@@ -47,7 +49,23 @@ import { Installation } from "@/installation"
 import { ShareNext } from "@/share"
 import { SessionShare } from "@/share"
 import { Npm } from "@/npm"
-import { memoMap } from "./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,
@@ -55,7 +73,7 @@ export const AppLayer = Layer.mergeAll(
   Bus.defaultLayer,
   Auth.defaultLayer,
   Account.defaultLayer,
-  Config.defaultLayer,
+  ConfigWithPluginPriority,
   Git.defaultLayer,
   Ripgrep.defaultLayer,
   File.defaultLayer,

+ 13 - 13
packages/opencode/src/project/bootstrap.ts

@@ -1,4 +1,3 @@
-import { Plugin } from "../plugin"
 import { Format } from "../format"
 import { LSP } from "../lsp"
 import { File } from "../file"
@@ -8,6 +7,7 @@ import * as Vcs from "./vcs"
 import { Bus } from "../bus"
 import { Command } from "../command"
 import { Instance } from "./instance"
+import { Plugin } from "../plugin"
 import { Log } from "@/util"
 import { FileWatcher } from "@/file/watcher"
 import { ShareNext } from "@/share"
@@ -16,20 +16,20 @@ import { Config } from "@/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()),
+      ...[
+        Plugin.Service,
+        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) =>