core: make InstanceBootstrap into an effect (#22274)

Co-authored-by: Kit Langton <kit.langton@gmail.com>
This commit is contained in:
Brendan Allan
2026-04-13 22:16:40 +08:00
committed by GitHub
parent 3eb6508a64
commit 94f71f59a3
10 changed files with 52 additions and 28 deletions

View File

@@ -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()

View File

@@ -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()

View File

@@ -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(() => {})
},

View File

@@ -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 })

View File

@@ -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 })

View File

@@ -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"))

View File

@@ -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()
},

View File

@@ -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)
},

View File

@@ -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)

View File

@@ -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,
)