Explorar o código

core: make InstanceBootstrap into an effect (#22274)

Co-authored-by: Kit Langton <[email protected]>
Brendan Allan hai 4 días
pai
achega
94f71f59a3

+ 3 - 1
packages/opencode/script/seed-e2e.ts

@@ -1,3 +1,5 @@
+import { AppRuntime } from "@/effect/app-runtime"
+
 const dir = process.env.OPENCODE_E2E_PROJECT_DIR ?? process.cwd()
 const title = process.env.OPENCODE_E2E_SESSION_TITLE ?? "E2E Session"
 const text = process.env.OPENCODE_E2E_MESSAGE ?? "Seeded for UI e2e"
@@ -20,7 +22,7 @@ const seed = async () => {
   try {
     await Instance.provide({
       directory: dir,
-      init: InstanceBootstrap,
+      init: () => AppRuntime.runPromise(InstanceBootstrap),
       fn: async () => {
         await Config.waitForDependencies()
         await ToolRegistry.ids()

+ 2 - 1
packages/opencode/src/cli/bootstrap.ts

@@ -1,10 +1,11 @@
+import { AppRuntime } from "@/effect/app-runtime"
 import { InstanceBootstrap } from "../project/bootstrap"
 import { Instance } from "../project/instance"
 
 export async function bootstrap<T>(directory: string, cb: () => Promise<T>) {
   return Instance.provide({
     directory,
-    init: InstanceBootstrap,
+    init: () => AppRuntime.runPromise(InstanceBootstrap),
     fn: async () => {
       try {
         const result = await cb()

+ 2 - 2
packages/opencode/src/cli/cmd/tui/worker.ts

@@ -7,10 +7,10 @@ import { Rpc } from "@/util/rpc"
 import { upgrade } from "@/cli/upgrade"
 import { Config } from "@/config/config"
 import { GlobalBus } from "@/bus/global"
-import type { GlobalEvent } from "@opencode-ai/sdk/v2"
 import { Flag } from "@/flag/flag"
 import { writeHeapSnapshot } from "node:v8"
 import { Heap } from "@/cli/heap"
+import { AppRuntime } from "@/effect/app-runtime"
 
 await Log.init({
   print: process.argv.includes("--print-logs"),
@@ -74,7 +74,7 @@ export const rpc = {
   async checkUpgrade(input: { directory: string }) {
     await Instance.provide({
       directory: input.directory,
-      init: InstanceBootstrap,
+      init: () => AppRuntime.runPromise(InstanceBootstrap),
       fn: async () => {
         await upgrade().catch(() => {})
       },

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

@@ -49,7 +49,7 @@ import { ShareNext } from "@/share/share-next"
 import { SessionShare } from "@/share/session"
 
 export const AppLayer = Layer.mergeAll(
-  Observability.layer,
+  // Observability.layer,
   AppFileSystem.defaultLayer,
   Bus.defaultLayer,
   Auth.defaultLayer,
@@ -95,6 +95,6 @@ export const AppLayer = Layer.mergeAll(
   Installation.defaultLayer,
   ShareNext.defaultLayer,
   SessionShare.defaultLayer,
-)
+).pipe(Layer.provide(Observability.layer))
 
 export const AppRuntime = ManagedRuntime.make(AppLayer, { memoMap })

+ 18 - 1
packages/opencode/src/effect/bootstrap-runtime.ts

@@ -1,10 +1,27 @@
 import { Layer, ManagedRuntime } from "effect"
 import { memoMap } from "./run-service"
 
+import { Plugin } from "@/plugin"
+import { LSP } from "@/lsp"
 import { FileWatcher } from "@/file/watcher"
 import { Format } from "@/format"
 import { ShareNext } from "@/share/share-next"
+import { File } from "@/file"
+import { Vcs } from "@/project/vcs"
+import { Snapshot } from "@/snapshot"
+import { Bus } from "@/bus"
+import { Observability } from "./oltp"
 
-export const BootstrapLayer = Layer.mergeAll(Format.defaultLayer, ShareNext.defaultLayer, FileWatcher.defaultLayer)
+export const BootstrapLayer = Layer.mergeAll(
+  Plugin.defaultLayer,
+  ShareNext.defaultLayer,
+  Format.defaultLayer,
+  LSP.defaultLayer,
+  File.defaultLayer,
+  FileWatcher.defaultLayer,
+  Vcs.defaultLayer,
+  Snapshot.defaultLayer,
+  Bus.defaultLayer,
+).pipe(Layer.provide(Observability.layer))
 
 export const BootstrapRuntime = ManagedRuntime.make(BootstrapLayer, { memoMap })

+ 18 - 16
packages/opencode/src/project/bootstrap.ts

@@ -9,24 +9,26 @@ import { Bus } from "../bus"
 import { Command } from "../command"
 import { Instance } from "./instance"
 import { Log } from "@/util/log"
-import { BootstrapRuntime } from "@/effect/bootstrap-runtime"
 import { FileWatcher } from "@/file/watcher"
 import { ShareNext } from "@/share/share-next"
+import * as Effect from "effect/Effect"
 
-export async function InstanceBootstrap() {
+export const InstanceBootstrap = Effect.gen(function* () {
   Log.Default.info("bootstrapping", { directory: Instance.directory })
-  await Plugin.init()
-  void BootstrapRuntime.runPromise(ShareNext.Service.use((svc) => svc.init()))
-  void BootstrapRuntime.runPromise(Format.Service.use((svc) => svc.init()))
-  await LSP.init()
-  File.init()
-  void BootstrapRuntime.runPromise(FileWatcher.Service.use((svc) => svc.init()))
-  Vcs.init()
-  Snapshot.init()
+  yield* Plugin.Service.use((svc) => svc.init())
+  yield* ShareNext.Service.use((svc) => svc.init()).pipe(Effect.forkDetach)
+  yield* Format.Service.use((svc) => svc.init()).pipe(Effect.forkDetach)
+  yield* LSP.Service.use((svc) => svc.init())
+  yield* File.Service.use((svc) => svc.init()).pipe(Effect.forkDetach)
+  yield* FileWatcher.Service.use((svc) => svc.init()).pipe(Effect.forkDetach)
+  yield* Vcs.Service.use((svc) => svc.init()).pipe(Effect.forkDetach)
+  yield* Snapshot.Service.use((svc) => svc.init()).pipe(Effect.forkDetach)
 
-  Bus.subscribe(Command.Event.Executed, async (payload) => {
-    if (payload.properties.name === Command.Default.INIT) {
-      Project.setInitialized(Instance.project.id)
-    }
-  })
-}
+  yield* Bus.Service.use((svc) =>
+    svc.subscribeCallback(Command.Event.Executed, async (payload) => {
+      if (payload.properties.name === Command.Default.INIT) {
+        Project.setInitialized(Instance.project.id)
+      }
+    }),
+  )
+}).pipe(Effect.withSpan("InstanceBootstrap"))

+ 3 - 2
packages/opencode/src/server/instance/middleware.ts

@@ -10,6 +10,7 @@ import { InstanceBootstrap } from "@/project/bootstrap"
 import { Session } from "@/session"
 import { SessionID } from "@/session/schema"
 import { WorkspaceContext } from "@/control-plane/workspace-context"
+import { AppRuntime } from "@/effect/app-runtime"
 
 type Rule = { method?: string; path: string; exact?: boolean; action: "local" | "forward" }
 
@@ -66,7 +67,7 @@ export function WorkspaceRouterMiddleware(upgrade: UpgradeWebSocket): Middleware
     if (!workspaceID) {
       return Instance.provide({
         directory,
-        init: InstanceBootstrap,
+        init: () => AppRuntime.runPromise(InstanceBootstrap),
         async fn() {
           return next()
         },
@@ -103,7 +104,7 @@ export function WorkspaceRouterMiddleware(upgrade: UpgradeWebSocket): Middleware
         fn: () =>
           Instance.provide({
             directory: target.directory,
-            init: InstanceBootstrap,
+            init: () => AppRuntime.runPromise(InstanceBootstrap),
             async fn() {
               return next()
             },

+ 2 - 1
packages/opencode/src/server/instance/project.ts

@@ -8,6 +8,7 @@ import { ProjectID } from "../../project/schema"
 import { errors } from "../error"
 import { lazy } from "../../util/lazy"
 import { InstanceBootstrap } from "../../project/bootstrap"
+import { AppRuntime } from "@/effect/app-runtime"
 
 export const ProjectRoutes = lazy(() =>
   new Hono()
@@ -83,7 +84,7 @@ export const ProjectRoutes = lazy(() =>
           directory: dir,
           worktree: dir,
           project: next,
-          init: InstanceBootstrap,
+          init: () => AppRuntime.runPromise(InstanceBootstrap),
         })
         return c.json(next)
       },

+ 2 - 1
packages/opencode/src/worktree/index.ts

@@ -20,6 +20,7 @@ import { AppFileSystem } from "@/filesystem"
 import { makeRuntime } from "@/effect/run-service"
 import * as CrossSpawnSpawner from "@/effect/cross-spawn-spawner"
 import { InstanceState } from "@/effect/instance-state"
+import { AppRuntime } from "@/effect/app-runtime"
 
 export namespace Worktree {
   const log = Log.create({ service: "worktree" })
@@ -266,7 +267,7 @@ export namespace Worktree {
         const booted = yield* Effect.promise(() =>
           Instance.provide({
             directory: info.directory,
-            init: InstanceBootstrap,
+            init: () => AppRuntime.runPromise(InstanceBootstrap),
             fn: () => undefined,
           })
             .then(() => true)

+ 0 - 1
packages/opencode/test/server/project-init-git.test.ts

@@ -43,7 +43,6 @@ describe("project.initGit endpoint", () => {
         worktree: tmp.path,
       })
       expect(reloadSpy).toHaveBeenCalledTimes(1)
-      expect(reloadSpy.mock.calls[0]?.[0]?.init).toBe(InstanceBootstrap)
       expect(seen.some((evt) => evt.directory === tmp.path && evt.payload.type === "server.instance.disposed")).toBe(
         true,
       )