Explorar o código

refactor(opencode): bind instance bootstrap node (#34502)

James Long hai 2 meses
pai
achega
7a035d7fc0
Modificáronse 26 ficheiros con 82 adicións e 57 borrados
  1. 2 1
      packages/opencode/src/acp/directory.ts
  2. 2 1
      packages/opencode/src/acp/usage.ts
  3. 8 2
      packages/opencode/src/control-plane/workspace.ts
  4. 4 2
      packages/opencode/src/effect/app-runtime.ts
  5. 2 2
      packages/opencode/src/project/bootstrap.ts
  6. 0 11
      packages/opencode/src/project/instance-layer.ts
  7. 5 3
      packages/opencode/src/project/instance-store.ts
  8. 2 1
      packages/opencode/src/server/routes/instance/httpapi/server.ts
  9. 4 2
      packages/opencode/src/worktree/index.ts
  10. 1 1
      packages/opencode/test/control-plane/workspace.test.ts
  11. 2 1
      packages/opencode/test/fixture/fixture.ts
  12. 2 2
      packages/opencode/test/fixture/workspace.ts
  13. 1 1
      packages/opencode/test/permission/next.test.ts
  14. 1 2
      packages/opencode/test/plugin/workspace-adapter.test.ts
  15. 7 2
      packages/opencode/test/project/instance-bootstrap.test.ts
  16. 1 1
      packages/opencode/test/project/instance.test.ts
  17. 3 1
      packages/opencode/test/project/worktree-remove.test.ts
  18. 7 1
      packages/opencode/test/project/worktree.test.ts
  19. 6 4
      packages/opencode/test/provider/provider.test.ts
  20. 4 2
      packages/opencode/test/server/httpapi-instance-context.test.ts
  21. 4 2
      packages/opencode/test/server/httpapi-promptasync-context.test.ts
  22. 2 1
      packages/opencode/test/server/httpapi-sdk.test.ts
  23. 7 6
      packages/opencode/test/server/httpapi-session.test.ts
  24. 3 3
      packages/opencode/test/server/httpapi-workspace.test.ts
  25. 1 1
      packages/opencode/test/server/project-copy.test.ts
  26. 1 1
      packages/opencode/test/server/project-init-git.test.ts

+ 2 - 1
packages/opencode/src/acp/directory.ts

@@ -1,6 +1,7 @@
 import { Agent } from "@/agent/agent"
 import { Command } from "@/command"
 import { InstanceRef } from "@/effect/instance-ref"
+import { InstanceBootstrap } from "@/project/bootstrap"
 import { InstanceStore } from "@/project/instance-store"
 import { LayerNode } from "@opencode-ai/core/effect/layer-node"
 import { ProviderV2 } from "@opencode-ai/core/provider"
@@ -205,7 +206,7 @@ export const defaultLayer = layer.pipe(
   Layer.provide(Provider.defaultLayer),
   Layer.provide(Agent.defaultLayer),
   Layer.provide(Command.defaultLayer),
-  Layer.provide(InstanceStore.defaultLayer),
+  Layer.provide(LayerNode.compile(InstanceStore.node, [[InstanceStore.bootstrapNode, InstanceBootstrap.node]])),
 )
 
 export const loaderNode = LayerNode.make({

+ 2 - 1
packages/opencode/src/acp/usage.ts

@@ -1,6 +1,7 @@
 import type { AgentSideConnection, Usage } from "@agentclientprotocol/sdk"
 import type { AssistantMessage as OpenCodeAssistantMessage, Message } from "@opencode-ai/sdk/v2"
 import { InstanceRef } from "@/effect/instance-ref"
+import { InstanceBootstrap } from "@/project/bootstrap"
 import { InstanceStore } from "@/project/instance-store"
 import { makeGlobalNode, Node } from "@opencode-ai/core/effect/app-node"
 import { LayerNode } from "@opencode-ai/core/effect/layer-node"
@@ -228,7 +229,7 @@ export const layer = Layer.effect(
 export const defaultLayer = layer.pipe(
   Layer.provide(contextLimitLoaderLayer),
   Layer.provide(Provider.defaultLayer),
-  Layer.provide(InstanceStore.defaultLayer),
+  Layer.provide(LayerNode.compile(InstanceStore.node, [[InstanceStore.bootstrapNode, InstanceBootstrap.node]])),
 )
 
 export const messageLoaderNode = LayerNode.unbound(MessageLoader, Node.tags.values.global)

+ 8 - 2
packages/opencode/src/control-plane/workspace.ts

@@ -601,7 +601,11 @@ export const layer = Layer.effect(
                   }),
                 fallback: "",
                 response: "text",
-              }).pipe(Effect.provide(InstanceStore.defaultLayer.pipe(Layer.provide(InstanceBootstrap.defaultLayer))))
+              }).pipe(
+                Effect.provide(
+                  LayerNode.compile(InstanceStore.node, [[InstanceStore.bootstrapNode, InstanceBootstrap.node]]),
+                ),
+              )
             : ""
 
         if (sourcePatch) {
@@ -617,7 +621,9 @@ export const layer = Layer.effect(
                 body: HttpBody.jsonUnsafe({ patch: sourcePatch }),
               }),
             fallback: { applied: false },
-          }).pipe(Effect.provide(InstanceStore.defaultLayer.pipe(Layer.provide(InstanceBootstrap.defaultLayer))))
+          }).pipe(
+            Effect.provide(LayerNode.compile(InstanceStore.node, [[InstanceStore.bootstrapNode, InstanceBootstrap.node]])),
+          )
         }
 
         if (input.workspaceID === null) {

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

@@ -38,7 +38,8 @@ import { Command } from "@/command"
 import { Truncate } from "@/tool/truncate"
 import { ToolRegistry } from "@/tool/registry"
 import { Format } from "@/format"
-import { InstanceLayer } from "@/project/instance-layer"
+import { InstanceBootstrap } from "@/project/bootstrap"
+import { InstanceStore } from "@/project/instance-store"
 import { Project } from "@/project/project"
 import { Vcs } from "@/project/vcs"
 import { Workspace } from "@/control-plane/workspace"
@@ -51,6 +52,7 @@ import { memoMap } from "@opencode-ai/core/effect/memo-map"
 import { BackgroundJob } from "@/background/job"
 import { RuntimeFlags } from "@/effect/runtime-flags"
 import { EventV2Bridge } from "@/event-v2-bridge"
+import { LayerNode } from "@opencode-ai/core/effect/layer-node"
 
 export const AppLayer = Layer.mergeAll(
   Npm.defaultLayer,
@@ -101,7 +103,7 @@ export const AppLayer = Layer.mergeAll(
   SessionShare.defaultLayer,
 ).pipe(
   Layer.provideMerge(Ripgrep.defaultLayer),
-  Layer.provideMerge(InstanceLayer.layer),
+  Layer.provideMerge(LayerNode.compile(InstanceStore.node, [[InstanceStore.bootstrapNode, InstanceBootstrap.node]])),
   Layer.provideMerge(Observability.layer),
 )
 

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

@@ -1,4 +1,4 @@
-import { LayerNode } from "@opencode-ai/core/effect/layer-node"
+import { makeGlobalNode } from "@opencode-ai/core/effect/app-node"
 import { Plugin } from "../plugin"
 import { Format } from "../format"
 import { LSP } from "@/lsp/lsp"
@@ -62,7 +62,7 @@ export const defaultLayer: Layer.Layer<Service> = layer.pipe(
   ]),
 )
 
-export const node = LayerNode.make({
+export const node = makeGlobalNode({
   service: Service,
   layer: layer,
   deps: [Config.node, Format.node, LSP.node, Plugin.node, Project.node, ShareNext.node, Snapshot.node, Vcs.node],

+ 0 - 11
packages/opencode/src/project/instance-layer.ts

@@ -1,11 +0,0 @@
-import { Effect, Layer } from "effect"
-import { InstanceStore } from "./instance-store"
-
-export const layer = Layer.unwrap(
-  Effect.promise(async () => {
-    const { InstanceBootstrap } = await import("./bootstrap")
-    return InstanceStore.defaultLayer.pipe(Layer.provide(InstanceBootstrap.defaultLayer))
-  }),
-)
-
-export * as InstanceLayer from "./instance-layer"

+ 5 - 3
packages/opencode/src/project/instance-store.ts

@@ -1,4 +1,5 @@
 import { LayerNode } from "@opencode-ai/core/effect/layer-node"
+import { makeGlobalNode, Node } from "@opencode-ai/core/effect/app-node"
 import { GlobalBus } from "@/bus/global"
 import { serviceUse } from "@opencode-ai/core/effect/service-use"
 import { WorkspaceContext } from "@/control-plane/workspace-context"
@@ -8,7 +9,6 @@ import { FSUtil } from "@opencode-ai/core/fs-util"
 import { Context, Deferred, Duration, Effect, Exit, Layer, Scope } from "effect"
 import { type InstanceContext } from "./instance-context"
 import { InstanceBootstrap } from "./bootstrap-service"
-import { InstanceBootstrap as InstanceBootstrapGraph } from "./bootstrap"
 import * as Project from "./project"
 
 export interface LoadInput {
@@ -204,10 +204,12 @@ export const layer: Layer.Layer<Service, never, Project.Service | InstanceBootst
 
 export const defaultLayer = layer.pipe(Layer.provide(Project.defaultLayer))
 
-export const node = LayerNode.make({
+export const bootstrapNode = LayerNode.unbound(InstanceBootstrap.Service, Node.tags.values.global)
+
+export const node = makeGlobalNode({
   service: Service,
   layer: layer,
-  deps: [Project.node, InstanceBootstrapGraph.node],
+  deps: [Project.node, bootstrapNode],
 })
 
 export * as InstanceStore from "./instance-store"

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

@@ -22,6 +22,7 @@ import { McpAuth } from "@/mcp/auth"
 import { Permission } from "@/permission"
 import { Plugin } from "@/plugin"
 import { PluginPtyEnvironment } from "@/plugin/pty-environment"
+import { InstanceBootstrap } from "@/project/bootstrap"
 import { InstanceStore } from "@/project/instance-store"
 import { Project } from "@/project/project"
 import { Vcs } from "@/project/vcs"
@@ -301,7 +302,7 @@ export function createRoutes(
     ),
     Layer.provide(locationServiceMapLayer),
 
-    Layer.provide(LayerNode.compile(app)),
+    Layer.provide(LayerNode.compile(app, [[InstanceStore.bootstrapNode, InstanceBootstrap.node]])),
   )
 }
 

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

@@ -1,7 +1,7 @@
 import { LayerNode } from "@opencode-ai/core/effect/layer-node"
 import { path } from "@opencode-ai/core/effect/app-node-platform"
 import { Global } from "@opencode-ai/core/global"
-import { InstanceLayer } from "@/project/instance-layer"
+import { InstanceBootstrap } from "@/project/bootstrap"
 import { InstanceStore } from "@/project/instance-store"
 import { Project } from "@/project/project"
 import { Database } from "@opencode-ai/core/database/database"
@@ -625,7 +625,9 @@ export const appLayer = layer.pipe(
   Layer.provide(NodePath.layer),
 )
 
-export const defaultLayer = appLayer.pipe(Layer.provide(InstanceLayer.layer))
+export const defaultLayer = appLayer.pipe(
+  Layer.provide(LayerNode.compile(InstanceStore.node, [[InstanceStore.bootstrapNode, InstanceBootstrap.node]])),
+)
 
 export const node = LayerNode.make({
   service: Service,

+ 1 - 1
packages/opencode/test/control-plane/workspace.test.ts

@@ -53,7 +53,7 @@ const workspaceLayer = (experimentalWorkspaces: boolean) =>
     [
       [RuntimeFlags.node, RuntimeFlags.layer({ experimentalWorkspaces })],
       [
-        InstanceBootstrap.node,
+        InstanceStore.bootstrapNode,
         Layer.succeed(InstanceBootstrap.Service, InstanceBootstrap.Service.of({ run: Effect.void })),
       ],
     ],

+ 2 - 1
packages/opencode/test/fixture/fixture.ts

@@ -9,6 +9,7 @@ import type * as Scope from "effect/Scope"
 import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
 import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"
 import type { Config } from "@/config/config"
+import { LayerNode } from "@opencode-ai/core/effect/layer-node"
 import { InstanceRef } from "../../src/effect/instance-ref"
 import { InstanceBootstrap } from "../../src/project/bootstrap-service"
 import type { InstanceContext } from "../../src/project/instance-context"
@@ -17,7 +18,7 @@ import { InstanceStore } from "../../src/project/instance-store"
 import { TestLLMServer } from "../lib/llm-server"
 
 const noopBootstrap = Layer.succeed(InstanceBootstrap.Service, InstanceBootstrap.Service.of({ run: Effect.void }))
-export const testInstanceStoreLayer = InstanceStore.defaultLayer.pipe(Layer.provide(noopBootstrap))
+export const testInstanceStoreLayer = LayerNode.compile(InstanceStore.node, [[InstanceStore.bootstrapNode, noopBootstrap]])
 
 export async function provideTestInstance<R>(input: {
   directory: string

+ 2 - 2
packages/opencode/test/fixture/workspace.ts

@@ -1,5 +1,6 @@
 import { FetchHttpClient } from "effect/unstable/http"
 import { Layer } from "effect"
+import { LayerNode } from "@opencode-ai/core/effect/layer-node"
 import { Database } from "@opencode-ai/core/database/database"
 import { FSUtil } from "@opencode-ai/core/fs-util"
 import { Auth } from "../../src/auth"
@@ -25,6 +26,5 @@ export const workspaceLayerWithRuntimeFlags = (overrides: Partial<RuntimeFlags.I
     Layer.provide(FetchHttpClient.layer),
     Layer.provide(FSUtil.defaultLayer),
     Layer.provide(RuntimeFlags.layer(overrides)),
-    Layer.provide(InstanceStore.defaultLayer),
-    Layer.provide(InstanceBootstrap.defaultLayer),
+    Layer.provide(LayerNode.compile(InstanceStore.node, [[InstanceStore.bootstrapNode, InstanceBootstrap.node]])),
   )

+ 1 - 1
packages/opencode/test/permission/next.test.ts

@@ -16,7 +16,7 @@ import { LayerNode } from "@opencode-ai/core/effect/layer-node"
 const noopBootstrap = Layer.succeed(InstanceBootstrap.Service, InstanceBootstrap.Service.of({ run: Effect.void }))
 const env = AppNodeBuilder.build(
   LayerNode.group([Permission.node, EventV2Bridge.node, CrossSpawnSpawner.node, InstanceStore.node]),
-  [[InstanceBootstrap.node, noopBootstrap]],
+  [[InstanceStore.bootstrapNode, noopBootstrap]],
 )
 const it = testEffect(env)
 

+ 1 - 2
packages/opencode/test/plugin/workspace-adapter.test.ts

@@ -21,13 +21,12 @@ import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
 import { LayerNode } from "@opencode-ai/core/effect/layer-node"
 
 const noopBootstrapLayer = Layer.succeed(InstanceBootstrap.Service, InstanceBootstrap.Service.of({ run: Effect.void }))
-
 const it = testEffect(
   AppNodeBuilder.build(LayerNode.group([Plugin.node, Workspace.node, InstanceStore.node, Ripgrep.node]), [
     [Auth.node, AuthTest.empty],
     [Account.node, AccountTest.empty],
     [Npm.node, NpmTest.noop],
-    [InstanceBootstrap.node, noopBootstrapLayer],
+    [InstanceStore.bootstrapNode, noopBootstrapLayer],
     [RuntimeFlags.node, RuntimeFlags.layer({ disableDefaultPlugins: true, experimentalWorkspaces: true })],
   ]),
 )

+ 7 - 2
packages/opencode/test/project/instance-bootstrap.test.ts

@@ -4,14 +4,19 @@ import path from "node:path"
 import { pathToFileURL } from "node:url"
 import { LayerNode } from "@opencode-ai/core/effect/layer-node"
 import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
-import { Cause, Effect, Exit, Fiber, Layer } from "effect"
+import { Cause, Effect, Exit, Fiber } from "effect"
 import { bootstrap as cliBootstrap } from "../../src/cli/bootstrap"
+import { InstanceBootstrap } from "../../src/project/bootstrap"
 import { InstanceStore } from "../../src/project/instance-store"
 import { disposeAllInstances, tmpdirScoped } from "../fixture/fixture"
 import { testEffect } from "../lib/effect"
 import { waitGlobalBusEvent } from "../server/global-bus"
 
-const it = testEffect(LayerNode.compile(LayerNode.group([InstanceStore.node, CrossSpawnSpawner.node])))
+const it = testEffect(
+  LayerNode.compile(LayerNode.group([InstanceStore.node, CrossSpawnSpawner.node]), [
+    [InstanceStore.bootstrapNode, InstanceBootstrap.node],
+  ]),
+)
 
 // InstanceBootstrap must run before any code touches the instance —
 // originally tracked by PRs #25389 and #25449, now a permanent

+ 1 - 1
packages/opencode/test/project/instance.test.ts

@@ -17,7 +17,7 @@ const noopBootstrap = Layer.succeed(
 
 const it = testEffect(
   LayerNode.compile(LayerNode.group([InstanceStore.node, CrossSpawnSpawner.node]), [
-    [InstanceBootstrap.node, noopBootstrap],
+    [InstanceStore.bootstrapNode, noopBootstrap],
   ]),
 )
 

+ 3 - 1
packages/opencode/test/project/worktree-remove.test.ts

@@ -4,11 +4,13 @@ import * as fs from "fs/promises"
 import path from "path"
 import { LayerNode } from "@opencode-ai/core/effect/layer-node"
 import { Effect } from "effect"
+import { InstanceBootstrap } from "../../src/project/bootstrap"
+import { InstanceStore } from "../../src/project/instance-store"
 import { Worktree } from "../../src/worktree"
 import { TestInstance } from "../fixture/fixture"
 import { testEffect } from "../lib/effect"
 
-const it = testEffect(LayerNode.compile(Worktree.node))
+const it = testEffect(LayerNode.compile(Worktree.node, [[InstanceStore.bootstrapNode, InstanceBootstrap.node]]))
 const wintest = process.platform === "win32" ? it.instance : it.instance.skip
 
 describe("Worktree.remove", () => {

+ 7 - 1
packages/opencode/test/project/worktree.test.ts

@@ -5,11 +5,17 @@ import { FSUtil } from "@opencode-ai/core/fs-util"
 import { Cause, Deferred, Effect, Exit, Fiber } from "effect"
 import { GlobalBus, type GlobalEvent } from "../../src/bus/global"
 import { Git } from "../../src/git"
+import { InstanceBootstrap } from "../../src/project/bootstrap"
+import { InstanceStore } from "../../src/project/instance-store"
 import { Worktree } from "../../src/worktree"
 import { disposeAllInstances, provideInstance, TestInstance } from "../fixture/fixture"
 import { testEffect } from "../lib/effect"
 
-const it = testEffect(LayerNode.compile(LayerNode.group([Worktree.node, FSUtil.node, Git.node])))
+const it = testEffect(
+  LayerNode.compile(LayerNode.group([Worktree.node, FSUtil.node, Git.node]), [
+    [InstanceStore.bootstrapNode, InstanceBootstrap.node],
+  ]),
+)
 const wintest = process.platform !== "win32" ? it.instance : it.instance.skip
 
 function normalize(input: string) {

+ 6 - 4
packages/opencode/test/provider/provider.test.ts

@@ -17,7 +17,8 @@ import { Provider } from "@/provider/provider"
 
 import { RuntimeFlags } from "@/effect/runtime-flags"
 import { Filesystem } from "@/util/filesystem"
-import { InstanceLayer } from "@/project/instance-layer"
+import { InstanceBootstrap } from "@/project/bootstrap"
+import { InstanceStore } from "@/project/instance-store"
 import { testEffect } from "../lib/effect"
 import { ProviderV2 } from "@opencode-ai/core/provider"
 import { ModelV2 } from "@opencode-ai/core/model"
@@ -1760,8 +1761,9 @@ it.instance(
 // Tests that need plugin file setup or multi-instance flows fall back to a
 // scoped tmpdir + provideInstance pattern via it.effect.
 
+const instanceStoreLayer = LayerNode.compile(InstanceStore.node, [[InstanceStore.bootstrapNode, InstanceBootstrap.node]])
 const provideMultiInstance = <A, E, R>(eff: Effect.Effect<A, E, R>) =>
-  eff.pipe(Effect.provide(InstanceLayer.layer), Effect.provide(CrossSpawnSpawner.defaultLayer))
+  eff.pipe(Effect.provide(instanceStoreLayer), Effect.provide(CrossSpawnSpawner.defaultLayer))
 
 it.effect("plugin config providers persist after instance dispose", () =>
   Effect.gen(function* () {
@@ -1864,7 +1866,7 @@ it.effect("opencode loader keeps paid models when config apiKey is present", ()
       Provider.use
         .list()
         .pipe(provideInstanceEffect(directory))
-        .pipe(Effect.provide(InstanceLayer.layer), Effect.provide(CrossSpawnSpawner.defaultLayer))
+        .pipe(Effect.provide(instanceStoreLayer), Effect.provide(CrossSpawnSpawner.defaultLayer))
 
     const none = paid(yield* listIn(noneDir))
     const keyedCount = paid(yield* listIn(keyedDir))
@@ -1883,7 +1885,7 @@ it.effect("opencode loader keeps paid models when auth exists", () =>
       Provider.use
         .list()
         .pipe(provideInstanceEffect(directory))
-        .pipe(Effect.provide(InstanceLayer.layer), Effect.provide(CrossSpawnSpawner.defaultLayer))
+        .pipe(Effect.provide(instanceStoreLayer), Effect.provide(CrossSpawnSpawner.defaultLayer))
 
     const none = paid(yield* listIn(noneDir))
 

+ 4 - 2
packages/opencode/test/server/httpapi-instance-context.test.ts

@@ -1,6 +1,7 @@
 import { NodeHttpServer, NodeServices } from "@effect/platform-node"
 import { describe, expect } from "bun:test"
 import { Effect, Fiber, Layer, Schema } from "effect"
+import { LayerNode } from "@opencode-ai/core/effect/layer-node"
 import { HttpClient, HttpClientRequest, HttpRouter } from "effect/unstable/http"
 import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup } from "effect/unstable/httpapi"
 import * as Socket from "effect/unstable/socket/Socket"
@@ -12,7 +13,8 @@ import { Ripgrep } from "@opencode-ai/core/ripgrep"
 import type { WorkspaceAdapter } from "../../src/control-plane/types"
 import { Workspace } from "../../src/control-plane/workspace"
 import { InstanceRef, WorkspaceRef } from "../../src/effect/instance-ref"
-import { InstanceLayer } from "../../src/project/instance-layer"
+import { InstanceBootstrap } from "../../src/project/bootstrap"
+import { InstanceStore } from "../../src/project/instance-store"
 import { Project } from "../../src/project/project"
 import { Session } from "../../src/session/session"
 import { disposeMiddleware, markInstanceForDisposal } from "../../src/server/routes/instance/httpapi/lifecycle"
@@ -51,7 +53,7 @@ const it = testEffect(
     testStateLayer,
     NodeHttpServer.layerTest,
     NodeServices.layer,
-    InstanceLayer.layer,
+    LayerNode.compile(InstanceStore.node, [[InstanceStore.bootstrapNode, InstanceBootstrap.node]]),
     Project.defaultLayer,
     workspaceLayer,
   ).pipe(Layer.provide(Ripgrep.defaultLayer)),

+ 4 - 2
packages/opencode/test/server/httpapi-promptasync-context.test.ts

@@ -10,6 +10,7 @@
 import { NodeHttpServer, NodeServices } from "@effect/platform-node"
 import { describe, expect } from "bun:test"
 import { Deferred, Effect, Layer, Schema, Scope } from "effect"
+import { LayerNode } from "@opencode-ai/core/effect/layer-node"
 import * as Stream from "effect/Stream"
 import { HttpClient, HttpRouter, HttpServerResponse } from "effect/unstable/http"
 import * as Socket from "effect/unstable/socket/Socket"
@@ -20,7 +21,8 @@ import { Ripgrep } from "@opencode-ai/core/ripgrep"
 import type { WorkspaceAdapter } from "../../src/control-plane/types"
 import { Workspace } from "../../src/control-plane/workspace"
 import { InstanceRef, WorkspaceRef } from "../../src/effect/instance-ref"
-import { InstanceLayer } from "../../src/project/instance-layer"
+import { InstanceBootstrap } from "../../src/project/bootstrap"
+import { InstanceStore } from "../../src/project/instance-store"
 import { Project } from "../../src/project/project"
 import { Session } from "../../src/session/session"
 import {
@@ -56,7 +58,7 @@ const it = testEffect(
     testStateLayer,
     NodeHttpServer.layerTest,
     NodeServices.layer,
-    InstanceLayer.layer,
+    LayerNode.compile(InstanceStore.node, [[InstanceStore.bootstrapNode, InstanceBootstrap.node]]),
     Project.defaultLayer,
     workspaceLayer,
   ).pipe(Layer.provide(Ripgrep.defaultLayer)),

+ 2 - 1
packages/opencode/test/server/httpapi-sdk.test.ts

@@ -7,6 +7,7 @@ import { HttpServer } from "effect/unstable/http"
 import { ChildProcessSpawner } from "effect/unstable/process"
 import { FSUtil } from "@opencode-ai/core/fs-util"
 import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
+import { LayerNode } from "@opencode-ai/core/effect/layer-node"
 import { Flag } from "@opencode-ai/core/flag/flag"
 import { createOpencodeClient } from "@opencode-ai/sdk/v2"
 import { validateSession } from "../../src/cli/tui/validate-session"
@@ -34,7 +35,7 @@ const it = testEffect(
   Layer.mergeAll(
     FSUtil.defaultLayer,
     CrossSpawnSpawner.defaultLayer,
-    InstanceStore.defaultLayer.pipe(Layer.provide(noopBootstrap)),
+    LayerNode.compile(InstanceStore.node, [[InstanceStore.bootstrapNode, noopBootstrap]]),
     Database.defaultLayer,
     httpApiLayer,
   ),

+ 7 - 6
packages/opencode/test/server/httpapi-session.test.ts

@@ -9,6 +9,7 @@ import { HttpClient, HttpClientRequest, HttpClientResponse, HttpRouter, HttpServ
 import { layerWebSocketConstructorGlobal } from "effect/unstable/socket/Socket"
 import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
 import { Flag } from "@opencode-ai/core/flag/flag"
+import { LayerNode } from "@opencode-ai/core/effect/layer-node"
 import { Ripgrep } from "@opencode-ai/core/ripgrep"
 import { registerAdapter } from "../../src/control-plane/adapters"
 import type { WorkspaceAdapter } from "../../src/control-plane/types"
@@ -38,14 +39,14 @@ import { pollWithTimeout, testEffect } from "../lib/effect"
 
 const originalWorkspaces = Flag.OPENCODE_EXPERIMENTAL_WORKSPACES
 const workspaceLayer = Workspace.defaultLayer.pipe(
-  Layer.provide(InstanceStore.defaultLayer),
-  Layer.provide(InstanceBootstrap.defaultLayer),
+  Layer.provide(LayerNode.compile(InstanceStore.node, [[InstanceStore.bootstrapNode, InstanceBootstrap.node]])),
 )
-const instanceStoreLayer = InstanceStore.defaultLayer.pipe(
-  Layer.provide(
+const instanceStoreLayer = LayerNode.compile(InstanceStore.node, [
+  [
+    InstanceStore.bootstrapNode,
     Layer.succeed(InstanceBootstrapService.Service, InstanceBootstrapService.Service.of({ run: Effect.void })),
-  ),
-)
+  ],
+])
 const servedRoutes: Layer.Layer<never, Config.ConfigError, HttpServer.HttpServer> = HttpRouter.serve(
   HttpApiApp.routes,
   {

+ 3 - 3
packages/opencode/test/server/httpapi-workspace.test.ts

@@ -3,6 +3,7 @@ import { mkdir } from "node:fs/promises"
 import path from "node:path"
 import { Effect, Layer, Stream } from "effect"
 import { Flag } from "@opencode-ai/core/flag/flag"
+import { LayerNode } from "@opencode-ai/core/effect/layer-node"
 import { registerAdapter } from "../../src/control-plane/adapters"
 import { WorkspaceV2 } from "@opencode-ai/core/workspace"
 import type { WorkspaceAdapter } from "../../src/control-plane/types"
@@ -24,15 +25,14 @@ import { httpApiLayer, requestInDirectory } from "./httpapi-layer"
 
 const originalWorkspaces = Flag.OPENCODE_EXPERIMENTAL_WORKSPACES
 const workspaceLayer = Workspace.defaultLayer.pipe(
-  Layer.provide(InstanceStore.defaultLayer),
-  Layer.provide(InstanceBootstrap.defaultLayer),
+  Layer.provide(LayerNode.compile(InstanceStore.node, [[InstanceStore.bootstrapNode, InstanceBootstrap.node]])),
 )
 const it = testEffect(
   Layer.mergeAll(
     Project.defaultLayer,
     Session.defaultLayer,
     workspaceLayer,
-    InstanceStore.defaultLayer.pipe(Layer.provide(InstanceBootstrap.defaultLayer)),
+    LayerNode.compile(InstanceStore.node, [[InstanceStore.bootstrapNode, InstanceBootstrap.node]]),
     Database.defaultLayer,
     httpApiLayer,
   ).pipe(Layer.provide(Ripgrep.defaultLayer)),

+ 1 - 1
packages/opencode/test/server/project-copy.test.ts

@@ -21,7 +21,7 @@ afterEach(async () => {
 })
 
 const noopBootstrap = Layer.succeed(InstanceBootstrap.Service, InstanceBootstrap.Service.of({ run: Effect.void }))
-const testInstanceStore = LayerNode.compile(InstanceStore.node, [[InstanceBootstrap.node, noopBootstrap]])
+const testInstanceStore = LayerNode.compile(InstanceStore.node, [[InstanceStore.bootstrapNode, noopBootstrap]])
 const it = testEffect(
   Layer.mergeAll(
     LayerNode.compile(LayerNode.group([FSUtil.node, Database.node, Snapshot.node])),

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

@@ -20,7 +20,7 @@ afterEach(async () => {
 })
 
 const noopBootstrap = Layer.succeed(InstanceBootstrap.Service, InstanceBootstrap.Service.of({ run: Effect.void }))
-const testInstanceStore = LayerNode.compile(InstanceStore.node, [[InstanceBootstrap.node, noopBootstrap]])
+const testInstanceStore = LayerNode.compile(InstanceStore.node, [[InstanceStore.bootstrapNode, noopBootstrap]])
 
 const it = testEffect(
   Layer.mergeAll(LayerNode.compile(LayerNode.group([FSUtil.node, Snapshot.node])), testInstanceStore, httpApiLayer),